Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion huma.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,14 +607,16 @@ func transformAndWrite(api API, ctx Context, status int, ct string, body any) er
statusStr = strconv.Itoa(status)
}

ctx.SetStatus(status)

tVal, tErr := api.Transform(ctx, statusStr, body)
if tErr != nil {
ctx.BodyWriter().Write([]byte("error transforming response"))
// When including tVal in the panic message, the server may become unresponsive for some time if the value is very large
// therefore, it has been removed from the panic message
return fmt.Errorf("error transforming response for %s %s %d: %w", ctx.Operation().Method, ctx.Operation().Path, status, tErr)
}
ctx.SetStatus(status)

if status != http.StatusNoContent && status != http.StatusNotModified {
if mErr := api.Marshal(ctx.BodyWriter(), ct, tVal); mErr != nil {
if errors.Is(ctx.Context().Err(), context.Canceled) {
Expand Down
47 changes: 47 additions & 0 deletions huma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4190,3 +4190,50 @@ func TestBodyFallbackContentType(t *testing.T) {

assert.Equal(t, http.StatusNoContent, w.Code)
}

func TestWriteResponseTransformErrorStatus(t *testing.T) {
// This test verifies that if a transformer fails, the client still receives
// a 500 Internal Server Error status code.
config := huma.DefaultConfig("Test API", "1.0.0")
config.Transformers = append(config.Transformers, func(ctx huma.Context, status string, v any) (any, error) {
return nil, errors.New("transform error")
})

mux := http.NewServeMux()
api := humago.New(mux, config)

huma.Get(api, "/test", func(ctx context.Context, input *struct{}) (*struct {
Status int
Body string
}, error) {
return &struct {
Status int
Body string
}{Status: http.StatusInternalServerError, Body: "hello"}, nil
})

// Use a custom adapter that doesn't call WriteHeader twice if we can.
// Actually, just let it panic and recover.
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rvr := recover(); rvr != nil {
// We don't want to call WriteHeader here if it was already called.
// But we can't easily check if it was called on a raw http.ResponseWriter.
return
}
}()
mux.ServeHTTP(w, r)
})

ts := httptest.NewServer(handler)
defer ts.Close()

res, err := http.Get(ts.URL + "/test")
require.NoError(t, err)
defer res.Body.Close()

body, _ := io.ReadAll(res.Body)

assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
assert.Contains(t, string(body), "error transforming response")
}
Loading