danielgtaylor / danielgtaylor/huma

[BUG] Wrong location and value in error response for invalid custom type in request body

Open
#1,109 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.