danielgtaylor / danielgtaylor/huma
[BUG] Wrong location and value in error response for invalid custom type in request body
- Dominant language
- Go
- Stars
- 4.4k
- Forks
- 285
- Avg merge
- 40m
- Merged PRs (30d)
- 1
Description
# Huma version
**v2.39.1**
# Background
When a custom type implements `UnmarshalText` and decoding the request body fails, the error response reports the location as `body` and includes the entire request body as the value.
The error should point to the field that failed to decode and include that field's value.
# Reproduction
The following is a minimal reproduction:
```go
package main
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humachi"
"github.com/danielgtaylor/huma/v2/humacli"
"github.com/go-chi/chi/v5"
_ "github.com/danielgtaylor/huma/v2/formats/cbor"
)
type Options struct {
Port int `help:"Port to listen on" short:"p" default:"8888"`
}
type TestInput struct {
Body struct {
Amount int `json:"amount"`
CustomAmount CustomType `json:"customAmount"`
}
}
type CustomType int
func (c *CustomType) UnmarshalText(text []byte) error {
value, err := strconv.Atoi(string(text))
if err != nil {
return err
}
*c = CustomType(value)
return nil
}
type TestOutput struct {
Body struct {
Message string `json:"message"`
}
}
func main() {
cli := humacli.New(func(hooks humacli.Hooks, options *Options) {
router := chi.NewMux()
api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))
huma.Post(api, "/test", func(ctx context.Context, input *TestInput) (*TestOutput, error) {
resp := &TestOutput{}
resp.Body.Message = fmt.Sprintf(
"Got, amount %v and custom amount %v!",
input.Body.Amount,
input.Body.CustomAmount,
)
return resp, nil
})
hooks.OnStart(func() {
http.ListenAndServe(fmt.Sprintf(":%d", options.Port), router)
})
})
cli.Run()
}
```
Send the following request:
```http
POST /test HTTP/1.1
Content-Type: application/json
{
"amount": 1,
"customAmount": "a"
}
```
### Actual response
```json
{
"$schema": "http://localhost:8888/schemas/ErrorModel.json",
"title": "Unprocessable Entity",
"status": 422,
"detail": "validation failed",
"errors": [
{
"message": "strconv.Atoi: parsing \"a\": invalid syntax",
"location": "body",
"value": "{\"amount\":1,\"customAmount\":\"a\"}"
}
]
}
```
### Expected response
```json
{
"$schema": "http://localhost:8888/schemas/ErrorModel.json",
"title": "Unprocessable Entity",
"status": 422,
"detail": "validation failed",
"errors": [
{
"message": "strconv.Atoi: parsing \"a\": invalid syntax",
"location": "body.customAmount",
"value": "a"
}
]
}
```
# Comparison with built-in type
For a built-in `int` field, the error response correctly reports the field location and value.
Request:
```json
{
"amount": "1",
"customAmount": "2"
}
```
Response:
```json
{
"$schema": "http://localhost:8888/schemas/ErrorModel.json",
"title": "Unprocessable Entity",
"status": 422,
"detail": "validation failed",
"errors": [
{
"message": "expected integer",
"location": "body.amount",
"value": "1"
}
]
}
```
The custom type should behave similarly, reporting `body.customAmount` and `"a"` instead of `body` and the entire request body.
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce the request-body decoding failure using the provided TestInput, CustomType, and UnmarshalText example, then trace the validation path that handles custom-type errors. Done means the error identifies the failing field as body.customAmount and reports only "a" as its value, while preserving the existing error message.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100