Skip to content

Strict server: gate no-content response headers on nullable/optional#2351

Merged
mromaszewicz merged 1 commit into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-2349
Apr 30, 2026
Merged

Strict server: gate no-content response headers on nullable/optional#2351
mromaszewicz merged 1 commit into
oapi-codegen:mainfrom
mromaszewicz:fix/issue-2349

Conversation

@mromaszewicz

Copy link
Copy Markdown
Member

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

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 oapi-codegen#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 oapi-codegen#2301 / oapi-codegen#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: oapi-codegen#2349

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mromaszewicz mromaszewicz requested a review from a team as a code owner April 30, 2026 04:23
@mromaszewicz mromaszewicz added the bug Something isn't working label Apr 30, 2026
@mromaszewicz mromaszewicz added this to the v2.7.0 milestone Apr 30, 2026
@greptile-apps

greptile-apps Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug in the strict-server code generator where the no-content (zero-body) response branch of all three strict templates (strict-interface.tmpl, strict-fiber-interface.tmpl, strict-iris-interface.tmpl) was emitting optional/nullable response headers unconditionally instead of gating them behind nil/IsSpecified() checks. The fix applies the same three-way IsNullable / IsOptional / default switch that already existed in the typed-body branch, and adds a regression test (NoContentHeadersOmitUnset) across all seven backend server implementations via a new /no-content-headers 204 endpoint in strict-schema.yaml.

Confidence Score: 5/5

Safe to merge — targeted template fix with correct generated output and comprehensive regression coverage across all backends.

The template change is minimal, surgical, and mirrors an identical fix already applied to the typed-body branch. All three strict templates are updated consistently. Generated code correctly nil-checks both optional and nullable header fields before emitting them. Regression tests are added in all three relevant test files (shared, fiber, stdhttp), handler stubs are provided for all seven framework backends, and the schema change is well-scoped. No unrelated drift found in the generated files.

No files require special attention.

Important Files Changed

Filename Overview
pkg/codegen/templates/strict/strict-interface.tmpl No-content branch now has the correct three-way IsNullable/IsOptional/default header switch, matching the typed-body branch.
pkg/codegen/templates/strict/strict-fiber-interface.tmpl Fiber no-content branch receives the same three-way header gating fix, consistent with the standard interface template.
pkg/codegen/templates/strict/strict-iris-interface.tmpl Iris no-content branch receives the same three-way header gating fix, consistent with the other templates.
internal/test/strict-server/strict-schema.yaml Adds /no-content-headers POST operation with 204 response declaring optional-header and nullable-header to drive regression coverage.
internal/test/strict-server/strict_test.go Adds NoContentHeadersOmitUnset subtest to the shared testImpl covering chi, echo, gin, and iris; asserts both headers absent when response struct fields are zero-valued.
internal/test/strict-server/fiber/fiber_strict_test.go Fiber-specific testImpl copy gains the same NoContentHeadersOmitUnset subtest.
internal/test/strict-server/stdhttp/std_strict_test.go stdhttp-specific testImpl copy gains the same NoContentHeadersOmitUnset subtest.
internal/test/strict-server/chi/server.gen.go Regenerated with NoContentHeaders operation; VisitNoContentHeadersResponse correctly gates both headers behind nil checks.
internal/test/strict-server/gorilla/server.gen.go Regenerated with NoContentHeaders stub; generated Visit function correctly nil-checks both headers before emitting.
internal/test/strict-server/gorilla/server.go Adds NoContentHeaders handler stub returning empty 204 response to satisfy the updated StrictServerInterface.

Reviews (1): Last reviewed commit: "Strict server: gate no-content response ..." | Re-trigger Greptile

@mromaszewicz mromaszewicz merged commit 3338f93 into oapi-codegen:main Apr 30, 2026
19 checks passed
@mromaszewicz mromaszewicz deleted the fix/issue-2349 branch April 30, 2026 04:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Strict-server: no-content Visit*Response skips nullable/optional header gating

1 participant