Bug Description
After upgrading from v2.37.2 to v2.37.3, the Link response header (e.g., </schemas/MyOutputBody.json>; rel="describedBy") is no longer present in API responses when using the Echo adapter (humaecho).
The $schema field in the response body still works correctly — only the HTTP Link header is affected.
Root Cause
In PR #997, ctx.SetStatus(status) was moved before api.Transform() in transformAndWrite() (huma.go, line 610).
In the humaecho adapter, SetStatus calls c.orig.Response().WriteHeader(code) (humaecho.go, line 109), which — per Go's net/http contract — flushes the status code and all currently-set headers to the client.
The SchemaLinkTransformer.Transform method then calls ctx.AppendHeader("Link", info.header) (transforms.go, line 190), but since WriteHeader was already called, the header addition is silently ignored.
v2.37.2 (working)
api.Transform(ctx, ...) // AppendHeader("Link", ...) — headers not yet flushed ✅
ctx.SetStatus(status) // WriteHeader flushes status + headers (including Link)
api.Marshal(...) // write body
v2.37.3 (broken)
ctx.SetStatus(status) // WriteHeader flushes status + headers (Link not yet set)
api.Transform(ctx, ...) // AppendHeader("Link", ...) — too late, headers already flushed ❌
api.Marshal(...) // write body
Reproduction
Any API endpoint using the Echo adapter with docs enabled:
conf := huma.DefaultConfig("My API", "1.0.0")
conf.SchemasPath = "/schemas"
api := humaecho.New(e, conf)
huma.Register(api, huma.Operation{
Method: http.MethodGet,
Path: "/hello",
OperationID: "get-hello",
}, func(ctx context.Context, input *struct{}) (*struct {
Body struct {
Message string `json:"message"`
}
}, error) {
resp := &struct {
Body struct {
Message string `json:"message"`
}
}{}
resp.Body.Message = "Hello"
return resp, nil
})
Expected Link header: </schemas/GetHelloOutputBody.json>; rel="describedBy"
Actual Link header: (empty)
Suggested Fix
Move ctx.SetStatus(status) back to after api.Transform() but before api.Marshal(), so that headers set during transformation are included, while still allowing the status to be changed to 500 if marshaling fails:
tVal, tErr := api.Transform(ctx, statusStr, body)
if tErr \!= nil { ... }
ctx.SetStatus(status) // Set status after transform, before marshal
if status \!= http.StatusNoContent && status \!= http.StatusNotModified {
if mErr := api.Marshal(ctx.BodyWriter(), ct, tVal); mErr \!= nil {
ctx.SetStatus(http.StatusInternalServerError) // Override on marshal failure
...
}
}
Environment
- huma v2.37.3
- Adapter: humaecho (Echo v4)
- Go 1.24
Bug Description
After upgrading from v2.37.2 to v2.37.3, the
Linkresponse header (e.g.,</schemas/MyOutputBody.json>; rel="describedBy") is no longer present in API responses when using the Echo adapter (humaecho).The
$schemafield in the response body still works correctly — only the HTTPLinkheader is affected.Root Cause
In PR #997,
ctx.SetStatus(status)was moved beforeapi.Transform()intransformAndWrite()(huma.go, line 610).In the humaecho adapter,
SetStatuscallsc.orig.Response().WriteHeader(code)(humaecho.go, line 109), which — per Go'snet/httpcontract — flushes the status code and all currently-set headers to the client.The
SchemaLinkTransformer.Transformmethod then callsctx.AppendHeader("Link", info.header)(transforms.go, line 190), but sinceWriteHeaderwas already called, the header addition is silently ignored.v2.37.2 (working)
v2.37.3 (broken)
Reproduction
Any API endpoint using the Echo adapter with docs enabled:
Expected
Linkheader:</schemas/GetHelloOutputBody.json>; rel="describedBy"Actual
Linkheader: (empty)Suggested Fix
Move
ctx.SetStatus(status)back to afterapi.Transform()but beforeapi.Marshal(), so that headers set during transformation are included, while still allowing the status to be changed to 500 if marshaling fails:Environment