Skip to content

Commit

Permalink
Merge pull request #152 from danielgtaylor/resolver-custom-status
Browse files Browse the repository at this point in the history
feat: allow resolvers to set a custom response status code
  • Loading branch information
danielgtaylor authored Oct 23, 2023
2 parents 80c75e4 + 03642ab commit 5265385
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,16 @@ func (m *MyInput) Resolve(ctx huma.Context) []error {
}
```

It is also possible for resolvers to return custom HTTP status codes for the response, by returning an error which satisfies the `huma.StatusError` interface. Errors are processed in the order they are returned and the last one wins, so this feature should be used sparingly. For example:

```go
type MyInput struct{}

func (i *MyInput) Resolve(ctx huma.Context) []error {
return []error{huma.Error403Forbidden("nope")}
}
```

> :whale: Exhaustive errors lessen frustration for users. It's better to return three errors in response to one request than to have the user make three requests which each return a new different error.
#### Input Composition
Expand Down
9 changes: 9 additions & 0 deletions huma.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,15 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)
})

if len(res.Errors) > 0 {
for i := len(res.Errors) - 1; i >= 0; i-- {
// If there are errors, and they provide a status, then update the
// response status code to match. Otherwise, use the default status
// code is used. Since these run in order, the last error code wins.
if s, ok := res.Errors[i].(StatusError); ok {
errStatus = s.GetStatus()
break
}
}
WriteErr(api, ctx, errStatus, "validation failed", res.Errors...)
return
}
Expand Down
25 changes: 25 additions & 0 deletions huma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,31 @@ func TestNestedResolverWithPath(t *testing.T) {
assert.Contains(t, w.Body.String(), `"location":"body.field1.foo[0].field2"`)
}

type ResolverCustomStatus struct{}

func (r *ResolverCustomStatus) Resolve(ctx Context) []error {
return []error{Error403Forbidden("nope")}
}

func TestResolverCustomStatus(t *testing.T) {
r := chi.NewRouter()
app := NewTestAdapter(r, DefaultConfig("Test API", "1.0.0"))
Register(app, Operation{
OperationID: "test",
Method: http.MethodPut,
Path: "/test",
}, func(ctx context.Context, input *ResolverCustomStatus) (*struct{}, error) {
return nil, nil
})

req, _ := http.NewRequest(http.MethodPut, "/test", strings.NewReader(`{}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusForbidden, w.Code, w.Body.String())
assert.Contains(t, w.Body.String(), "nope")
}

func BenchmarkSecondDecode(b *testing.B) {
type MediumSized struct {
ID int `json:"id"`
Expand Down

0 comments on commit 5265385

Please sign in to comment.