Skip to content

support optional/nullable response headers#2301

Merged
mromaszewicz merged 2 commits into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-2267
Apr 11, 2026
Merged

support optional/nullable response headers#2301
mromaszewicz merged 2 commits into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-2267

Conversation

@mromaszewicz
Copy link
Copy Markdown
Member

Closes: #2267

Response headers in strict server code were always generated as direct value types regardless of whether they were required. This adds support for optional and nullable response headers in the strict-interface and strict-responses templates.

Add Required and Nullable fields to ResponseHeaderDefinition, populated from the OpenAPI header object. Add GoTypeDef(), IsOptional(), and IsNullable() methods that mirror the existing Property logic for determining whether a field should be a pointer, nullable.Nullable[T], or a direct value.

Update strict-interface.tmpl and strict-responses.tmpl to use GoTypeDef for struct field types, and to conditionally guard w.Header().Set() calls with nil/IsSpecified checks for optional/nullable headers.

Note: this is a breaking change for specs where response headers lack explicit required: true, since the OpenAPI default is false. Existing headers will change from direct values to pointers. This may need to be gated behind a config option.

@mromaszewicz mromaszewicz requested a review from a team as a code owner March 23, 2026 21:33
@mromaszewicz mromaszewicz added the ☢️ breaking change This change would break existing users' code label Mar 23, 2026
@mromaszewicz mromaszewicz marked this pull request as draft March 23, 2026 22:04
Copy link
Copy Markdown
Member

@jamietanna jamietanna left a comment

Choose a reason for hiding this comment

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

Per offline discussion, this makes sense to do - we should correctly add this behaviour to make sure that we're reflecting what is the "correct" default

As we've said, I think adding a CompatibilityOption for this, so folks can revert back to the old behaviour would be best

But I'm of the opinion that moving folks over by default to the correct code path (which is technically breaking their generated code) is best

Do we know if we could use go:fix or anything to help them migrate more easily?

@jamietanna jamietanna added the notable changes Used for release notes to highlight these more highly label Mar 25, 2026
@mromaszewicz
Copy link
Copy Markdown
Member Author

I don't know how to use go:fix to fix this. I think we'll document the change and mention it in the release.

@mromaszewicz mromaszewicz removed the ☢️ breaking change This change would break existing users' code label Mar 27, 2026
@mromaszewicz mromaszewicz marked this pull request as ready for review March 27, 2026 04:52
mromaszewicz and others added 2 commits March 26, 2026 22:02
Closes: oapi-codegen#2267

Response headers in strict server code were always generated as direct
value types regardless of whether they were required. This adds support
for optional and nullable response headers in the strict-interface and
strict-responses templates.

Add Required and Nullable fields to ResponseHeaderDefinition, populated
from the OpenAPI header object. Add GoTypeDef(), IsOptional(), and
IsNullable() methods that mirror the existing Property logic for
determining whether a field should be a pointer, nullable.Nullable[T],
or a direct value.

Update strict-interface.tmpl and strict-responses.tmpl to use GoTypeDef
for struct field types, and to conditionally guard w.Header().Set()
calls with nil/IsSpecified checks for optional/nullable headers.

Note: this is a breaking change for specs where response headers lack
explicit `required: true`, since the OpenAPI default is false. Existing
headers will change from direct values to pointers. This may need to be
gated behind a config option.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Allows users to restore the pre-v2.5.0 behavior where all response
headers are treated as required, ignoring the OpenAPI `required` field.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@mromaszewicz mromaszewicz merged commit a4c36dc into oapi-codegen:main Apr 11, 2026
17 checks passed
@mromaszewicz mromaszewicz deleted the fix/issue-2267 branch April 29, 2026 21:31
mromaszewicz added a commit that referenced this pull request Apr 30, 2026
Bring the fiber and iris strict-server interface templates back into parity
with the canonical stdhttp template (`strict-interface.tmpl`). Four pieces of
drift were addressed:

1. Nullable / optional response-header serialization. Both templates now use
   the same three-way switch on `.IsNullable` / `.IsOptional` / default that
   PR #2301 introduced for stdhttp, so unspecified nullable values and nil
   optional pointers are skipped instead of being stringified into the wire
   header.

2. Response-header struct field types. The `{{$opid}}{{$statusCode}}ResponseHeaders`
   struct now uses `{{.GoTypeDef}}` (pointer/nullable-aware) rather than the
   raw `{{.Schema.TypeDecl}}`, matching the type that the typed-body Visit
   function expects.

3. `$ref` Text responses. The fixed-status-code + ref branch now matches
   Multipart and Text together (PR #2225), so `$ref` text responses alias
   directly to the component response type. Iris additionally drops its
   unconditional `type X string` short-circuit, which previously masked
   `$ref`, `$hasHeaders`, and `$fixedStatusCode` for any text response.

4. `$ref` name qualification (fiber only). Switch from `ucFirst` to
   `ucFirstWithPkgName` so external-ref response types carry their package
   qualifier, matching stdhttp and iris.

Regenerated fixtures under `internal/test/strict-server/{fiber,iris}/server.gen.go`
demonstrate the change for items (1) and (2); items (3) and (4) have no
existing fiber/iris fixture coverage.

The no-content `Visit*Response` branch still renders headers unconditionally
in all three templates (including stdhttp); that gap is tracked separately
(#2349) and not addressed here.

Fixes: #2331

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
mromaszewicz added a commit that referenced this pull request Apr 30, 2026
…2351)

The no-content branch of the three strict-server interface templates
(`strict-interface.tmpl`, `strict-fiber-interface.tmpl`,
`strict-iris-interface.tmpl`) was rendering response headers
unconditionally, missing the three-way `.IsNullable` / `.IsOptional` /
default switch that PR #2301 added to the typed-body branch.

Because the `{{$opid}}{{$statusCode}}ResponseHeaders` struct is shared
between the typed-body and no-content branches and uses
`{{.GoTypeDef}}` (since #2301 / #2331), an unset optional header field
is `*string(nil)` and an unspecified nullable header is a zero
`runtime.Nullable[T]`. The bare `fmt.Sprint(...)` in the no-content
branch was therefore stringifying these to `<nil>` / a typed
zero-value and emitting a junk header rather than omitting it. A
204-style response declaring an optional or nullable header — with
the caller leaving it unset — produced a wrong header value instead
of no header at all.

Apply the same three-way switch to the no-content branch in all
three templates so unset values are skipped, matching the typed-body
branch behavior and matching what callers expect for OpenAPI
`required: false` / `nullable: true` response headers.

Add regression coverage:

- `strict-schema.yaml` gains a `/no-content-headers` POST operation
  whose only response is 204 with `optional-header`
  (`required: false`) and `nullable-header` (`nullable: true`).
- `NoContentHeaders` handler stub returning an empty
  `NoContentHeaders204Response{}` is wired into all seven framework
  server implementations (chi, echo, fiber, gin, gorilla, iris,
  stdhttp).
- `NoContentHeadersOmitUnset` subtest added to the three `testImpl`
  copies (shared, stdhttp, fiber). Asserts both headers are absent
  from `rr.Header().Values(...)` when the response struct's header
  fields are left at their zero values.

The shared `testImpl` covers chi, echo, gin, and iris in one place;
stdhttp and fiber maintain their own copies because they live in
separate test files (stdhttp because of its own go.mod, fiber
because of its package boundary). Gorilla generates code but has no
test driver — the handler stub is still required so the generated
`StrictServerInterface` is satisfied.

Fixes: #2349

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

notable changes Used for release notes to highlight these more highly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support nullable response headers

2 participants