From 4298d7f8f9b0b10755f3835aa8371dd90f5d86af Mon Sep 17 00:00:00 2001 From: Marcin Romaszewicz Date: Wed, 29 Apr 2026 21:00:56 -0700 Subject: [PATCH] Synchronize strict servers 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) --- .../test/strict-server/fiber/server.gen.go | 12 +++++++---- .../test/strict-server/iris/server.gen.go | 12 +++++++---- .../strict/strict-fiber-interface.tmpl | 18 +++++++++++++---- .../strict/strict-iris-interface.tmpl | 20 +++++++++++++------ 4 files changed, 44 insertions(+), 18 deletions(-) diff --git a/internal/test/strict-server/fiber/server.gen.go b/internal/test/strict-server/fiber/server.gen.go index caea58b285..71761a1541 100644 --- a/internal/test/strict-server/fiber/server.gen.go +++ b/internal/test/strict-server/fiber/server.gen.go @@ -945,8 +945,8 @@ type HeadersExampleResponseObject interface { type HeadersExample200ResponseHeaders struct { Header1 string Header2 int - NullableHeader string - OptionalHeader string + NullableHeader *string + OptionalHeader *string } type HeadersExample200JSONResponse struct { @@ -957,8 +957,12 @@ type HeadersExample200JSONResponse struct { func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(ctx *fiber.Ctx) error { ctx.Response().Header.Set("header1", fmt.Sprint(response.Headers.Header1)) ctx.Response().Header.Set("header2", fmt.Sprint(response.Headers.Header2)) - ctx.Response().Header.Set("nullable-header", fmt.Sprint(response.Headers.NullableHeader)) - ctx.Response().Header.Set("optional-header", fmt.Sprint(response.Headers.OptionalHeader)) + if response.Headers.NullableHeader != nil { + ctx.Response().Header.Set("nullable-header", fmt.Sprint(*response.Headers.NullableHeader)) + } + if response.Headers.OptionalHeader != nil { + ctx.Response().Header.Set("optional-header", fmt.Sprint(*response.Headers.OptionalHeader)) + } ctx.Response().Header.Set("Content-Type", "application/json") ctx.Status(200) diff --git a/internal/test/strict-server/iris/server.gen.go b/internal/test/strict-server/iris/server.gen.go index 6bef496890..5d2dce3b6c 100644 --- a/internal/test/strict-server/iris/server.gen.go +++ b/internal/test/strict-server/iris/server.gen.go @@ -779,8 +779,8 @@ type HeadersExampleResponseObject interface { type HeadersExample200ResponseHeaders struct { Header1 string Header2 int - NullableHeader string - OptionalHeader string + NullableHeader *string + OptionalHeader *string } type HeadersExample200JSONResponse struct { @@ -791,8 +791,12 @@ type HeadersExample200JSONResponse struct { func (response HeadersExample200JSONResponse) VisitHeadersExampleResponse(ctx iris.Context) error { ctx.ResponseWriter().Header().Set("header1", fmt.Sprint(response.Headers.Header1)) ctx.ResponseWriter().Header().Set("header2", fmt.Sprint(response.Headers.Header2)) - ctx.ResponseWriter().Header().Set("nullable-header", fmt.Sprint(response.Headers.NullableHeader)) - ctx.ResponseWriter().Header().Set("optional-header", fmt.Sprint(response.Headers.OptionalHeader)) + if response.Headers.NullableHeader != nil { + ctx.ResponseWriter().Header().Set("nullable-header", fmt.Sprint(*response.Headers.NullableHeader)) + } + if response.Headers.OptionalHeader != nil { + ctx.ResponseWriter().Header().Set("optional-header", fmt.Sprint(*response.Headers.OptionalHeader)) + } ctx.ResponseWriter().Header().Set("Content-Type", "application/json") ctx.StatusCode(200) diff --git a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl index 5f79c84348..9ff692e814 100644 --- a/pkg/codegen/templates/strict/strict-fiber-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-fiber-interface.tmpl @@ -26,13 +26,13 @@ {{$fixedStatusCode := .HasFixedStatusCode -}} {{$isRef := .IsRef -}} {{$isExternalRef := .IsExternalRef -}} - {{$ref := .Ref | ucFirst -}} + {{$ref := .Ref | ucFirstWithPkgName -}} {{$headers := .Headers -}} {{if (and $hasHeaders (not $isRef)) -}} type {{$opid}}{{$statusCode}}ResponseHeaders struct { {{range .Headers -}} - {{.GoName}} {{.Schema.TypeDecl}} + {{.GoName}} {{.GoTypeDef}} {{end -}} } {{end}} @@ -40,7 +40,7 @@ {{range .Contents}} {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} {{if and $fixedStatusCode $isRef -}} - {{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}} + {{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (or (eq .NameTag "Multipart") (eq .NameTag "Text")) -}} type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response {{else if $isExternalRef -}} type {{$receiverTypeName}} struct { {{$ref}} } @@ -82,7 +82,17 @@ func (response {{$receiverTypeName}}) Visit{{$opid}}Response(ctx *fiber.Ctx) error { {{range $headers -}} - ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{if .IsNullable -}} + if response.Headers.{{.GoName}}.IsSpecified() { + ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}.MustGet())) + } + {{else if .IsOptional -}} + if response.Headers.{{.GoName}} != nil { + ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(*response.Headers.{{.GoName}})) + } + {{else -}} + ctx.Response().Header.Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} {{end -}} {{if eq .NameTag "Multipart" -}} writer := multipart.NewWriter(ctx.Response().BodyWriter()) diff --git a/pkg/codegen/templates/strict/strict-iris-interface.tmpl b/pkg/codegen/templates/strict/strict-iris-interface.tmpl index 32d319ed8d..0594e5993a 100644 --- a/pkg/codegen/templates/strict/strict-iris-interface.tmpl +++ b/pkg/codegen/templates/strict/strict-iris-interface.tmpl @@ -32,17 +32,15 @@ {{if (and $hasHeaders (not $isRef)) -}} type {{$opid}}{{$statusCode}}ResponseHeaders struct { {{range .Headers -}} - {{.GoName}} {{.Schema.TypeDecl}} + {{.GoName}} {{.GoTypeDef}} {{end -}} } {{end}} {{range .Contents}} {{$receiverTypeName := printf "%s%s%s%s" $opid $statusCode .NameTagOrContentType "Response"}} - {{if eq .NameTag "Text" -}} - type {{$receiverTypeName}} string - {{else if and $fixedStatusCode $isRef -}} - {{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (eq .NameTag "Multipart") -}} + {{if and $fixedStatusCode $isRef -}} + {{ if and (not $hasHeaders) ($fixedStatusCode) (.IsSupported) (or (eq .NameTag "Multipart") (eq .NameTag "Text")) -}} type {{$receiverTypeName}} {{$ref}}{{.NameTagOrContentType}}Response {{else if $isExternalRef -}} type {{$receiverTypeName}} struct { {{$ref}} } @@ -84,7 +82,17 @@ func (response {{$receiverTypeName}}) Visit{{$opid}}Response(ctx iris.Context) error { {{range $headers -}} - ctx.ResponseWriter().Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{if .IsNullable -}} + if response.Headers.{{.GoName}}.IsSpecified() { + ctx.ResponseWriter().Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}}.MustGet())) + } + {{else if .IsOptional -}} + if response.Headers.{{.GoName}} != nil { + ctx.ResponseWriter().Header().Set("{{.Name}}", fmt.Sprint(*response.Headers.{{.GoName}})) + } + {{else -}} + ctx.ResponseWriter().Header().Set("{{.Name}}", fmt.Sprint(response.Headers.{{.GoName}})) + {{end -}} {{end -}} {{if eq .NameTag "Multipart" -}} writer := multipart.NewWriter(ctx.ResponseWriter())