danielgtaylor / huma

Huma REST/HTTP API Framework for Golang with OpenAPI 3.1
https://huma.rocks/
MIT License
1.88k stars 138 forks source link

The 'pattern' struct tag does not work in some cases. #555

Open ayoo opened 3 weeks ago

ayoo commented 3 weeks ago

The pattern attribute used to validate input values with regex sometimes doesn't work, allowing invalid requests to get through.

Here is my test script.

    import (
        "context"
        "github.com/danielgtaylor/huma/v2"
        "github.com/danielgtaylor/huma/v2/humatest"
        "net/http"
        "testing"
    )

    type TestPatternInput struct {
        Body struct {
            FirstName string `json:"firstName" required:"true" pattern:"^[\p{L}]+([\p{L} '-][\p{L}]+)*$" patternDescription:"matches any kind of letters in any language including hyphen, apostrophe and space"`
        }
    }

    func Test_StringPattern(t *testing.T) {
        _, humaapi := humatest.New(t)
        huma.Register(humaapi, huma.Operation{
            Method:        http.MethodPost,
            Path:          "/test",
            DefaultStatus: http.StatusCreated,
        }, func(ctx context.Context, i *TestPatternInput) (*struct{}, error) {
            return &struct{}{}, nil
        })

        t.Run("with valid name", func(t *testing.T) {
            resp := humaapi.Post("/test",
                "Content-Type: application/json",
                map[string]string{
                    "firstName": "John",
                })
            if resp.Code != http.StatusCreated {
                t.Fatal()
            }
        })

        t.Run("with invalid name", func(t *testing.T) {
            resp := humaapi.Post("/test",
                "Content-Type: application/json",
                map[string]string{
                    "firstName": "11111",
                })
            if resp.Code == http.StatusCreated {
                t.Fatal()
            }
        })
    }

When '111111' was provided as a firstName and it still passes the validation.

The pattern matching works in Go and tested as shown here.

Example

Is this a bug or am I missing something here?

Thanks in advance.

ayoo commented 2 weeks ago

This turned out that the backslash in the pattern needed to be escaped like this pattern:

^[\\p{L}]+([\\p{L} '-][\\p{L}]+)*$

then it works all good.

The only issue with this is the default error message without 'patternDescription' containing the extra backslash as well.

  "$schema": "http://localhost:8080/schemas/ErrorModel.json",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "validation failed",
  "errors": [
    {
      "message": "expected string to match pattern ^[\\p{L}]+([\\p{L} '-][\\p{L}]+)*$",
      "location": "body.firstName",
      "value": "11111"
    }
  ]
}
danielgtaylor commented 16 hours ago

@ayoo glad you figured it out! The extra backslash in the returned JSON is because of the JSON encoder I believe, for example take a look at:

https://go.dev/play/p/-Qz6io2G89d

If this gets parsed and displayed somewhere it should just have the one backslash.