GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — fix: handle untyped strict response bodies by Boulea7 · Pull Request #2514 · oapi-codegen/oapi-codegen Conversation
|
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
9fc54da to
4243417
Compare
|
This also fixes #2525, which @mromaszewicz filed during review of #2522 (same bug as #1328). I was about to start on it and found this PR instead, so I took it for a spin rather than duplicate the work. I merged the branch onto current main and generated strict-server code for every shape from #2525: Two non-blocking suggestions:
Neither should hold this up. The wrapper shape matches what #2525 proposes, and wrapping only receiver-incompatible bodies keeps the direct API for everything that compiles today. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Added a generated Chi strict-handler round-trip test for object, array, scalar, null, pointer, and header-bearing JSON responses. The branch now includes current main, and |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
No unresolved issues were identified, and all reviewed changes are covered by regression tests.
Review effort: Lite
Findings: None
Fixes strict-server generation for untyped, interface, and pointer JSON response bodies using compatible wrappers.
Changes:
| File | Reviewed change |
|---|---|
pkg/codegen/templates/strict/strict-responses.tmpl |
Adds wrappers for incompatible response types. |
pkg/codegen/templates/strict/strict-iris-interface.tmpl |
Updates Iris strict response generation. |
pkg/codegen/templates/strict/strict-interface.tmpl |
Updates standard strict response generation. |
pkg/codegen/templates/strict/strict-fiber-interface.tmpl |
Updates Fiber strict response generation. |
pkg/codegen/operations.go |
Classifies response receiver compatibility. |
pkg/codegen/operations_test.go |
Tests receiver compatibility behavior. |
internal/test/references/multipackage/response_cast/support/types.go |
Adds shared response-casting test types. |
internal/test/references/multipackage/response_cast/spec-other.yaml |
Defines additional response-casting scenarios. |
internal/test/references/multipackage/response_cast/spec-base.yaml |
Defines base response-casting scenarios. |
internal/test/references/multipackage/response_cast/issue_test.go |
Adds cross-package regression tests. |
internal/test/references/multipackage/response_cast/gen/spec_other/issue.gen.go |
Regenerated standard server output. |
internal/test/references/multipackage/response_cast/gen/spec_other_iris/issue.gen.go |
Regenerated Iris server output. |
internal/test/references/multipackage/response_cast/gen/spec_other_fiber/issue.gen.go |
Regenerated Fiber server output. |
internal/test/references/multipackage/response_cast/gen/spec_base/issue.gen.go |
Regenerated base server output. |
internal/test/references/multipackage/response_cast/doc.go |
Documents the response-casting fixture. |
internal/test/references/multipackage/response_cast/config.other-iris.yaml |
Configures Iris fixture generation. |
internal/test/references/multipackage/response_cast/config.other-fiber.yaml |
Configures Fiber fixture generation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Added an OpenAPI 3.1 fixture for multi-type unions and |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@Boulea7, I cloned your branch locally and did a bunch of testing on it, since this is a very fragile part of the code, and I found a couple of regressions. I've fixed it locally. Would you like me to open a PR against your branch for you to look over, or should I push these fixes directly to your branch, since you have granted permission to do that in the PR. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Wrapping every receiver-incompatible body changed response types that compiled before, breaking code written against them: - A components/responses envelope is never a method receiver. Each operation that reaches it through $ref embeds it in a struct, and an embedded field may be an interface, so an untyped or interface envelope compiled as a direct type. Only a pointer, which cannot be embedded, needs a Body field there. - Under schema-merging-behavior v1, an allOf of a single $ref becomes a struct that embeds the member, which is a valid receiver, whether it is inline or a component of its own. The single-member allOf shortcut assumed the member's type is always used directly. Classify a composition by a referenced member only when the generator actually emitted that member's type. That also covers a nullable union collapsed to an untyped member, and an allOf of an untyped $ref with sibling keywords under v3. Check the declared type of x-go-type-name as well. One envelope that compiled before still changes: a pointer envelope that no operation reaches through a fixed-status $ref, so that it is never embedded (only reached through default, a status range, or not at all), goes from `type FooJSONResponse *T` to a struct with a Body field. The envelope has to match what a fixed-status $ref needs, and no generated operation uses the envelope in that case. servers/strict/responsebody_v1 compiles and serves the v1 shapes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
A text/plain or application/x-www-form-urlencoded response without a
schema, or with one that lowers to any, generated a method on an
interface type, the same compile error as the JSON case:
invalid receiver type GetText200TextResponse (pointer or interface type)
The strict templates already write text and form bodies from
response.Body whenever the response has a Body field, so classify
these content types as well.
An operation that reaches a reusable text response through $ref
redeclares its envelope as the receiver type, unlike a JSON or form
envelope, which it embeds. So a text envelope is classified as a
receiver.
That changes one type that compiled before: a reusable text envelope
whose body lowers to any or a pointer, and that no operation reaches
through a fixed-status $ref (only through default, a status range, or
not at all), goes from `type FooTextResponse any` to a struct with a
Body field. A fixed-status $ref to it needs that shape to compile, and
no generated operation uses the envelope otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NeedsLocalResponseHeaders only restated `not .IsRef`, and the strict templates used it for two of their three header branches; the branch for a response without content still reads $isRef. It changes no output, so go back to $isRef throughout and keep the templates' diff to what the fix needs. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The spec headers still described only the oapi-codegen#2010 cast. Name the untyped, pointer and header-bearing responses the fixture now covers, and cite oapi-codegen#2525 alongside oapi-codegen#1328. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review follow-ups:
- responsebody_v1 only checked that the direct responses were valid
JSON, so a visitor that dropped the value would still pass. Assert
the body. v1 embeds the untyped member, and encoding/json encodes an
embedded interface as a field named after its type, so the value
has always gone out as {"Untyped": ...}. The test records that.
- Untyped ({}) already admits null, so oneOf of it and a null branch
rejects null, the value the fixtures send. Use anyOf, which the
generator lowers identically, in both fixtures. The unit test keeps
the oneOf spelling that specs commonly use, and adds the anyOf one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Brings in oapi-codegen#2587 (a reusable form response's envelope is marshalled, not its wrapper) and oapi-codegen#2589 (types from an imported document qualify only their model names). Co-Authored-By: Claude Opus 5.5 <[email protected]>
The fixture oapi-codegen#2589 added has a reusable response whose schema is `x-go-type: "*string"`, reached only from `default`. Its envelope now carries a Body field, as a pointer envelope does, so that a fixed-status $ref to it would compile. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was an error while loading. Please reload this page.
|
Thanks for the review and merge! |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Thanks for fixing bugs! |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Under schema-merging-behavior v1, an allOf member that is a $ref is
embedded in a struct, whatever its type. encoding/json flattens only an
embedded struct, and encodes an embedded field of any other type under a
key named after the type. So when the member is untyped, a primitive,
an array or a map, the value went out as {"Name": "x"} instead of "x",
and decoding the value the spec describes failed, or for a map left it
empty (oapi-codegen#2590).
- An allOf of one such $ref, next to members that add no fields (the
"decorate a $ref" idiom, with a description or nullable: true), is
now the member's type, as under v2: `type Wrap = Name`, `One *Name`.
- An untyped member places no constraint and carries no value of its
own, so it never decides the composition's type: a primitive, array
or map member next to it is the composition's type, and a
composition of only untyped members is the first one's type.
Whenever another member adds fields or brings a type of its own, an
untyped member keeps its embedded field, so code that sets it still
compiles, but is tagged json:"-": it used to add an "Untyped" key to
every object.
A member is classified by the type the generator declares for the
schema it refers to. It is generated in a context of its own, whose
merge frames are copies, so that generating it can't tell a composition
in progress that something referred back to it, and each classification
is remembered in the context: a member that is itself an allOf of a
$ref would otherwise be generated twice per link, and a chain of such
compositions took time exponential in its length. A member whose type
may carry JSON methods the struct inherits (time.Time,
openapi_types.UUID, an x-go-type naming a type or an interface with
methods) is embedded as before, and so is a member whose type would
make a cycle of aliases (`type Root = Arr` with `type Arr = []Root`),
which Go rejects.
servers/strict/responsebody_v1 and the v1 case of
TestStrictServerComposedResponseReceiverCompatibility recorded the old
shape. Those allOf responses are now the untyped member's type, which a
strict response carries in a Body field (oapi-codegen#2514), as under v2 and v3,
and they send the value itself.
The aggregate matrix harness's echo now fills a Body-wrapped strict
envelope, so a shape with an interface-typed body runs at every
position. The matrix's allof_array, allof_default and
allof_unique_items shapes no longer mark v1 broken, since it now
round-trips them. New shapes cover a map member, an untyped member,
two untyped members, an untyped member next to a map, and an untyped
member next to properties. An allOf whose member is an inline array
(allof_recursive_array) is a different v1 bug, and stays marked.
Fixes oapi-codegen#2590
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Under schema-merging-behavior v1, an allOf member that is a $ref is
embedded in a struct, whatever its type. encoding/json flattens only an
embedded struct, and encodes an embedded field of any other type under a
key named after the type. So when the member is untyped, a primitive,
an array or a map, the value went out as {"Name": "x"} instead of "x",
and decoding the value the spec describes failed, or for a map left it
empty (#2590).
- An allOf of one such $ref, next to members that add no fields (the
"decorate a $ref" idiom, with a description or nullable: true), is
now the member's type, as under v2: `type Wrap = Name`, `One *Name`.
- An untyped member places no constraint and carries no value of its
own, so it never decides the composition's type: a primitive, array
or map member next to it is the composition's type, and a
composition of only untyped members is the first one's type.
Whenever another member adds fields or brings a type of its own, an
untyped member keeps its embedded field, so code that sets it still
compiles, but is tagged json:"-": it used to add an "Untyped" key to
every object.
A member is classified by the type the generator declares for the
schema it refers to. It is generated in a context of its own, whose
merge frames are copies, so that generating it can't tell a composition
in progress that something referred back to it, and each classification
is remembered in the context: a member that is itself an allOf of a
$ref would otherwise be generated twice per link, and a chain of such
compositions took time exponential in its length. A member whose type
may carry JSON methods the struct inherits (time.Time,
openapi_types.UUID, an x-go-type naming a type or an interface with
methods) is embedded as before, and so is a member whose type would
make a cycle of aliases (`type Root = Arr` with `type Arr = []Root`),
which Go rejects.
servers/strict/responsebody_v1 and the v1 case of
TestStrictServerComposedResponseReceiverCompatibility recorded the old
shape. Those allOf responses are now the untyped member's type, which a
strict response carries in a Body field (#2514), as under v2 and v3,
and they send the value itself.
The aggregate matrix harness's echo now fills a Body-wrapped strict
envelope, so a shape with an interface-typed body runs at every
position. The matrix's allof_array, allof_default and
allof_unique_items shapes no longer mark v1 broken, since it now
round-trips them. New shapes cover a map member, an untyped member,
two untyped members, an untyped member next to a map, and an untyped
member next to properties. An allOf whose member is an inline array
(allof_recursive_array) is a different v1 bug, and stays marked.
Fixes #2590
Co-authored-by: Claude Fable 5.1 <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Empty schema in response
There was an error while loading. Please reload this page.