danielgtaylor / danielgtaylor/huma
Validation for string-based types in Enum has unexpected results
- Dominant language
- Go
- Stars
- 4.4k
- Forks
- 285
- Avg merge
- 40m
- Merged PRs (30d)
- 1
Description
Hi there,
I have the following types
**main.go**
```go
const (
NameTypeFoo NameType = "FOO"
NameTypeBar NameType = "BAR"
)
type (
NameType string
UserCreateRequest struct {
Name NameType `json:"name" minLength:"1" maxLength:"32"`
}
CreateUserInput struct {
Body UserCreateRequest
}
CreateUserOutput struct {
Body struct {
Message string `json:"message"`
}
}
)
var _ huma.SchemaTransformer = NameType("")
func (nt NameType) TransformSchema(r huma.Registry, s *huma.Schema) *huma.Schema {
s.Enum = []interface{}{NameTypeFoo, NameTypeBar}
s.Default = NameTypeFoo
s.PrecomputeMessages()
return s
}
func addRoutes(api huma.API) {
huma.Register(api, huma.Operation{
OperationID: "CreateUser",
Method: http.MethodPost,
Path: "/user",
}, func(ctx context.Context, input *CreateUserInput) (*CreateUserOutput, error) {
resp := &CreateUserOutput{}
resp.Body.Message = "CreateUser works!"
return resp, nil
})
}
```
I have two string-type-based constants: `NameTypeFoo` and `NameTypeBar` ( `FOO` and `BAR` respectively ).
I set `Enum` in `TransformSchema` by `s.Enum = []interface{}{NameTypeFoo, NameTypeBar}`.
After that, I get the correct OAS
spec
```yaml
components:
schemas:
CreateUserOutputBody:
additionalProperties: false
properties:
$schema:
description: A URL to the JSON Schema for this object.
examples:
- https://example.com/schemas/CreateUserOutputBody.json
format: uri
readOnly: true
type: string
message:
type: string
required:
- message
type: object
ErrorDetail:
additionalProperties: false
properties:
location:
description: Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
type: string
message:
description: Error message text
type: string
value:
description: The value at the given location
type: object
ErrorModel:
additionalProperties: false
properties:
$schema:
description: A URL to the JSON Schema for this object.
examples:
- https://example.com/schemas/ErrorModel.json
format: uri
readOnly: true
type: string
detail:
description: A human-readable explanation specific to this occurrence of the problem.
examples:
- Property foo is required but is missing.
type: string
errors:
description: Optional list of individual error details
items:
$ref: "#/components/schemas/ErrorDetail"
type:
- array
- "null"
instance:
description: A URI reference that identifies the specific occurrence of the problem.
examples:
- https://example.com/error-log/abc123
format: uri
type: string
status:
description: HTTP status code
examples:
- 400
format: int64
type: integer
title:
description: A short, human-readable summary of the problem type. This value should not change between occurrences of the error.
examples:
- Bad Request
type: string
type:
default: about:blank
description: A URI reference to human-readable documentation for the error.
examples:
- https://example.com/errors/example
format: uri
type: string
type: object
UserCreateRequest:
additionalProperties: false
properties:
$schema:
description: A URL to the JSON Schema for this object.
examples:
- https://example.com/schemas/UserCreateRequest.json
format: uri
readOnly: true
type: string
name:
default: FOO
enum:
- FOO
- BAR
maxLength: 32
minLength: 1
type: string
required:
- name
type: object
info:
title: My API
version: 1.0.0
openapi: 3.1.0
paths:
/user:
post:
operationId: CreateUser
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/UserCreateRequest"
required: true
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserOutputBody"
description: OK
default:
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorModel"
description: Error
```
where `enum` is correct
```yaml
name:
default: FOO
enum:
- FOO
- BAR
maxLength: 32
minLength: 1
type: string
```
I expect the request with `FOO` or `BAR` to pass the validation.
**The issue**
main_test.go
```go
func TestUserCreate(t *testing.T) {
_, api := humatest.New(t, huma.DefaultConfig("Test API", "1.0.0"))
addRoutes(api)
_ = api.Post("/user", map[string]any{"name": "FOO"})
}
```
I get a `422` error with the correct `FOO` ( or `BAR` ) value on the request.
```
main_test.go:14: Making request:
POST /user HTTP/1.1
Content-Type: application/json
{
"name": "FOO"
}
main_test.go:14: Got response:
HTTP/1.1 422 Unprocessable Entity
Connection: close
Content-Type: application/problem+json
{
"$schema": "https:///schemas/ErrorModel.json",
"title": "Unprocessable Entity",
"status": 422,
"detail": "validation failed",
"errors": [
{
"message": "expected value to be one of \"FOO, BAR\"",
"location": "body.name",
"value": "FOO"
}
]
}
--- PASS: TestUserCreate (0.00s)
```
My question is it a bug or a wrong way to set `Enum` in `TransformSchema`?
If I set it as strings `s.Enum = []interface{}{"FOO", "BAR"}` everything works as expected.
Thank you.
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce the failure from main_test.go using humatest.New and api.Post, then trace how TransformSchema and s.Enum reach request validation. Compare named string values with plain strings; done means valid FOO and BAR requests pass while the generated OpenAPI enum remains correct.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, openapi
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100