GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix: handle untyped strict response bodies by Boulea7 · Pull Request #2514 · oapi-codegen/oapi-codegen

Conversation

@Boulea7

@Boulea7 Boulea7 commented Aug 10, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Summary

Strict JSON, text, and URL-encoded form responses wrap the body only when its generated Go type is invalid for its actual role as a receiver or embedded field.

  • Classify reusable response envelopes separately: interface envelopes retain their embeddable API, while pointer envelopes use a wrapper.
  • Preserve concrete composition shapes across schema-merging modes, including v1 single-member allOf responses.
  • Keep opaque named x-go-type declarations, multipart callbacks, and unsupported-media reader responses in their existing representations.

Compatibility

Reusable pointer envelopes and receiver-incompatible text envelopes can change to a Body struct even when no fixed-status operation uses them. Callers that construct these exported types may need to update.

Testing

  • make tidy, make generate, make test, and make lint; regeneration leaves the tree unchanged.
  • Generator regressions for reusable envelopes and composition modes.
  • Generated handler wire tests for JSON, text, form, and nullable bodies; cross-package response construction coverage.
  • OpenAPI 3.1 multi-type union and type: "null" response coverage.

AI assistance

This change was developed with AI assistance.

Fixes #1328

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

greptile-apps[bot] reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@Boulea7
Boulea7 requested a review from a team as a code owner August 10, 2026 18:01
@greptile-apps

greptile-apps Bot commented Aug 10, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Code generator templates for strict server response handling.

The PR appears safe to merge; no outstanding finding or actionable new failure was established.

Summary

The PR wraps strict response bodies when their Go types cannot serve as response receivers or embedded envelopes, while retaining direct types where possible.

  • Updates the standard, Fiber, and Iris strict-response templates.
  • Adds cross-package and wire-format regression fixtures for reusable responses and external types.

Reviews (5) · Last reviewed commit: "Merge strict response compatibility fixe..."

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

greptile-apps[bot]
greptile-apps Bot reviewed Aug 10, 2026
View reviewed changes
Comment thread pkg/codegen/externalref.go Resolved

Uh oh!

There was an error while loading. Please reload this page.

@Boulea7
Boulea7 marked this pull request as draft August 11, 2026 05:38
Boulea7 added 2 commits August 11, 2026 15:45
@Boulea7
fix: wrap receiver-incompatible strict JSON responses
4520abb
@Boulea7
test: cover strict JSON response receiver compatibility
4243417
@Boulea7
Boulea7 force-pushed the fix/issue-1328-untyped-json-responses branch from 9fc54da to 4243417 Compare August 11, 2026 07:46
@Boulea7
Boulea7 marked this pull request as ready for review August 11, 2026 14:35
This was referenced Aug 19, 2026
strict-server: any-typed response bodies generate a method on an interface type, which does not compile #2525
Closed
Map OpenAPI 3.1 multi-type unions to any #2522
Merged
@bendrucker

bendrucker commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

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: schema: {}, type: "null", a 3.1 multi-type union, and a $ref to a named union component. Each one produces the Body wrapper and compiles, with the ref case classified by its underlying schema. Regenerating the committed fixtures on the merged tree produces no diff, so the output is consistent with the generator after #2522 and #2526.

Two non-blocking suggestions:

  • The test specs are all OpenAPI 3.0.1. Since Map OpenAPI 3.1 multi-type unions to any #2522 merged, 3.1 multi-type unions and type: "null" also lower to any. The classification handles them, but a 3.1 fixture would pin that behavior against regressions.
  • The new tests are compile-time checks (conversions and struct literals). A round-trip through a strict handler asserting the JSON on the wire would also catch visitor regressions, not just type-shape ones.

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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Boulea7 added 2 commits September 27, 2026 01:25
@Boulea7
test: cover strict JSON response wire values
677d966
@Boulea7
Merge main and refresh strict response fixtures
173113c
Copilot AI lite review requested due to automatic review settings September 26, 2026 19:42
Copilot started reviewing on behalf of Boulea7 September 26, 2026 19:42
View session
@Boulea7

Boulea7 commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

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 make tidy, make test, make generate, and make lint pass locally. Could the new CI run be approved, followed by a final review once it passes?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Copilot AI reviewed Sep 26, 2026
View reviewed changes

Copilot AI left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

No unresolved issues were identified, and all reviewed changes are covered by regression tests.

Review effort: Lite
Findings: None

What changed in this PR

Fixes strict-server generation for untyped, interface, and pointer JSON response bodies using compatible wrappers.

Changes:

  • Adds response receiver compatibility classification.
  • Updates standard, Fiber, and Iris strict templates.
  • Expands cross-package regression coverage.
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.
Files not reviewed (4)
  • internal/test/references/multipackage/response_cast/gen/spec_base/issue.gen.go: Generated file
  • internal/test/references/multipackage/response_cast/gen/spec_other/issue.gen.go: Generated file
  • internal/test/references/multipackage/response_cast/gen/spec_other_fiber/issue.gen.go: Generated file
  • internal/test/references/multipackage/response_cast/gen/spec_other_iris/issue.gen.go: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@Boulea7
test: cover OpenAPI 3.1 strict JSON responses
9f69cad
@Boulea7

Boulea7 commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Added an OpenAPI 3.1 fixture for multi-type unions and type: "null" responses that lower to any. It compiles the generated strict server and checks string, number, boolean, and null JSON output through the HTTP handler.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mromaszewicz

mromaszewicz commented Sep 27, 2026

Copy link
Copy Markdown
Member

@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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mromaszewicz and others added 4 commits September 27, 2026 15:30
@mromaszewicz @claude
fix: keep strict response shapes that already compile
e1b1e95
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]>
@mromaszewicz @claude
fix: wrap untyped text and form strict response bodies
bd3320a
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]>
@mromaszewicz @claude
refactor: drop NeedsLocalResponseHeaders
3775d7f
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]>
@mromaszewicz @claude
test: describe what the response_cast fixture now covers
9228530
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]>
This was referenced Sep 27, 2026
Keep compiling strict response shapes; wrap untyped text and form bodies Boulea7/oapi-codegen#1
Merged
strict-server: a form response reached through $ref is encoded under an empty field name #2586
Closed
Types from an import-mapped document are qualified as a whole, producing externalRef0.any, externalRef0.*string, externalRef0.[]Thing #2588
Closed
fix: qualify only the model names in types from an imported document #2589
Merged
@mromaszewicz @claude
test: pin the v1 wire bodies, and spell the nullable fixtures with anyOf
983a2b0
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]>
@mromaszewicz mromaszewicz mentioned this pull request Sep 27, 2026
schema-merging-behavior v1: an allOf of a non-object $ref serializes under the member's type name #2590
Closed
mromaszewicz and others added 3 commits September 27, 2026 16:18
@mromaszewicz @claude
Merge main into pr-2514-fixes
748b208
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]>
@mromaszewicz @claude
test: regenerate external_types for reusable pointer envelopes
3134315
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]>
@Boulea7
Merge strict response compatibility fixes
2c6f305
@Boulea7 Boulea7 changed the title fix: handle untyped strict JSON responses fix: handle untyped strict response bodies Sep 28, 2026
mromaszewicz
mromaszewicz approved these changes Sep 28, 2026
View reviewed changes
@mromaszewicz
mromaszewicz merged commit ddbdf0e into oapi-codegen:main Sep 28, 2026
15 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@mromaszewicz mromaszewicz added the bug Something isn't working label Sep 28, 2026
@Boulea7

Boulea7 commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review and merge!

mromaszewicz reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mromaszewicz

mromaszewicz commented Sep 28, 2026

Copy link
Copy Markdown
Member

Thanks for fixing bugs!

Boulea7 reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mromaszewicz mromaszewicz mentioned this pull request Sep 29, 2026
fix: lower a v1 allOf of a non-object $ref to the member's type #2591
Merged
mromaszewicz added a commit to mromaszewicz/oapi-codegen that referenced this pull request Sep 29, 2026
@mromaszewicz @claude
fix: lower a v1 allOf of a non-object $ref to the member's type
3ae2d74
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]>
mromaszewicz added a commit that referenced this pull request Sep 29, 2026
@mromaszewicz @claude
fix: lower a v1 allOf of a non-object $ref to the member's type (#2591)
a157c2a
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]>
@mromaszewicz mromaszewicz mentioned this pull request Sep 29, 2026
refactor: share the Go-type and lowered-type walks the aggregate code grew #2592
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@greptile-apps greptile-apps[bot] greptile-apps[bot] left review comments
Copilot code review Copilot
Copilot review effort
Applies to this pull request for everyone.Learn more about Copilot code review.
Copilot left review comments
@mromaszewicz mromaszewicz mromaszewicz approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

bug Something isn't working

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Empty schema in response

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@Boulea7 @bendrucker @mromaszewicz
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.