danielgtaylor / huma

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

Panic on Validation Phase of custom `time.Time` Type #471

Closed mvrahden closed 1 month ago

mvrahden commented 4 months ago

I'm getting a panic on my Model Validation with a type-derivate Datetime of type time.Time. Funnily enough, the UUID Type works without an issue (it is a derivative of github.com/google/uuid.

I'm on the latest release v2.18.0

These are my types and interfaces:

type UUID uuid.UUID
func (u UUID) MarshalBSONValue() (bsontype.Type, []byte, error)
func (u UUID) MarshalBinary() ([]byte, error)
func (u UUID) MarshalGQL(w io.Writer)
func (u UUID) MarshalJSON() ([]byte, error)
func (u UUID) MarshalText() ([]byte, error)
func (u UUID) String() string
func (u UUID) ToUUID() uuid.UUID
func (u *UUID) UnmarshalBSONValue(dbt bsontype.Type, value []byte) error
func (u *UUID) UnmarshalBinary(data []byte) error
func (u *UUID) UnmarshalGQL(v interface{}) error
func (u *UUID) UnmarshalJSON(data []byte) error
func (u *UUID) UnmarshalText(data []byte) error

type Datetime time.Time
func (t Datetime) MarshalBSONValue() (bsontype.Type, []byte, error)
func (t Datetime) MarshalBinary() ([]byte, error)
func (t Datetime) MarshalGQL(w io.Writer)
func (t Datetime) MarshalJSON() ([]byte, error)
func (t Datetime) MarshalText() ([]byte, error)
func (t Datetime) String() string
func (t Datetime) ToTime() time.Time
func (t *Datetime) UnmarshalBSONValue(dbt bsontype.Type, value []byte) error
func (t *Datetime) UnmarshalBinary(data []byte) error
func (t *Datetime) UnmarshalGQL(v interface{}) error
func (t *Datetime) UnmarshalJSON(data []byte) error
func (t *Datetime) UnmarshalText(data []byte) error

My Model looks as follows:

type License struct {
    Body struct {
        LicenseID     UUID     `json:"license_id" example:"a3dd1aa2-f6ec-4bf1-bf22-e3461f2abaa7"`
        BuyerID       string            `json:"buyer_id" example:"a3dd1aa2-f6ec-4bf1-bf22-e3461f2abaa7"`
        OrderIDs      []string          `json:"order_id" example:"a3dd1aa2-f6ec-4bf1-bf22-e3461f2abaa7"`
        LicenseTypeID UUID     `json:"license_type_id" example:"a3dd1aa2-f6ec-4bf1-bf22-e3461f2abaa7"`
        ActivatedAt   Datetime `json:"activated_at" example:"2022-09-27T18:00:00.000+00:00"`
        ClaimedAt     Datetime `json:"claimed_at" example:"2022-09-27T18:00:00.000+00:00"`
        ValidUntil    Datetime `json:"valid_until" example:"2022-09-27T18:00:00.000+00:00"`
        LicenseKey    UUID     `json:"license_key" example:"a3dd1aa2-f6ec-4bf1-bf22-e3461f2abaa7"`
    }
}

Here's the stack trace:

--- FAIL: Test_ListLicenses (0.00s)
panic: invalid object tag value '2022-09-27T18:00:00.000+00:00' for field 'ActivatedAfter': invalid character '-' after top-level value [recovered]
    panic: invalid object tag value '2022-09-27T18:00:00.000+00:00' for field 'ActivatedAfter': invalid character '-' after top-level value

goroutine 5 [running]:
testing.tRunner.func1.2({0x103232280, 0x140006c6a40})
    /usr/local/go/src/testing/testing.go:1631 +0x1c4
testing.tRunner.func1()
    /usr/local/go/src/testing/testing.go:1634 +0x33c
panic({0x103232280?, 0x140006c6a40?})
    /usr/local/go/src/runtime/panic.go:770 +0x124
github.com/danielgtaylor/huma/v2.jsonTagValue({0x103352480, 0x140006c3580}, {0x1032c4f42, 0xe}, 0x1032c4f42?, {0x1032c4f95, 0x1d})
    /<<redacted>>/go/pkg/mod/github.com/danielgtaylor/huma/v2@v2.17.0/schema.go:462 +0x2bc
github.com/danielgtaylor/huma/v2.SchemaFromField({0x103352480, 0x140006c3580}, {{0x1032c4f42, 0xe}, {0x0, 0x0}, {0x10335ce68, 0x1032f73c0}, {0x1032c4f52, 0xb0}, ...}, ...)
    /<<redacted>>/go/pkg/mod/github.com/danielgtaylor/huma/v2@v2.17.0/schema.go:517 +0x3a8
github.com/danielgtaylor/huma/v2.findParams.func1({{0x1032c4f42, 0xe}, {0x0, 0x0}, {0x10335ce68, 0x1032f73c0}, {0x1032c4f52, 0xb0}, 0x10, {0x140001487a8, ...}, ...}, ...)
    /<<redacted>>/go/pkg/mod/github.com/danielgtaylor/huma/v2@v2.17.0/huma.go:160 +0x368
github.com/danielgtaylor/huma/v2._findInType[...]({0x10335ce68, 0x1032ef140}, {0x103a845c0, 0x0, 0x0}, 0x140001248d0, 0x0, 0x1400004d3b8, 0x0, 0x1400004d318, ...)
    /<<redacted>>/go/pkg/mod/github.com/danielgtaylor/huma/v2@v2.17.0/huma.go:422 +0x5c4
github.com/danielgtaylor/huma/v2.findInType[...]({0x10335ce68, 0x1032ef140}, 0x0, 0x1400004d3b8, 0x0, {0x1400004d3a8, 0x1, 0x1})
    /<<redacted>>/go/pkg/mod/github.com/danielgtaylor/huma/v2@v2.17.0/huma.go:382 +0xd0
github.com/danielgtaylor/huma/v2.findParams({0x103352480?, 0x140006c3580?}, 0x102c095fb?, {0x10335ce68?, 0x1032ef140?})
    /<<redacted>>/go/pkg/mod/github.com/danielgtaylor/huma/v2@v2.17.0/huma.go:109 +0x90
github.com/danielgtaylor/huma/v2.Register[...]({_, _}, {{0x102bfa792, 0x3}, {0x102c095fb, 0xb}, 0x0, 0x0, 0x0, {0x0, ...}, ...}, ...)
    /<<redacted>>/go/pkg/mod/github.com/danielgtaylor/huma/v2@v2.17.0/huma.go:571 +0x130
<<redacted>>/partnerapi/router.listLicenses({0x14aae44a8, 0x140006c67e0}, 0x140006c3600)
    <<redacted>>/partnerapi/router/route_definitions.go:43 +0xd14
<<redacted>>/partnerapi/router.RegisterRoutes({0x14aae44a8, 0x140006c67e0}, {0x1033486e0, 0x140001130b0}, {0x103348700, 0x140001130c0}, {0x103348720, 0x140001130d0}, {0x103348428, 0x103a845c0})
    <<redacted>>/partnerapi/router/router.go:39 +0x110
<<redacted>>/partnerapi/router_test.getSUT(0x140006f6b60)
    <<redacted>>/partnerapi/router/router_test.go:33 +0x230
<<redacted>>/partnerapi/router_test.Test_ListLicenses(0x140006f6b60)
    <<redacted>>/partnerapi/router/router_test.go:220 +0x28
testing.tRunner(0x140006f6b60, 0x103342270)
    /usr/local/go/src/testing/testing.go:1689 +0xec
created by testing.(*T).Run in goroutine 1
    /usr/local/go/src/testing/testing.go:1742 +0x318
FAIL    <<redacted>>/partnerapi/router  0.244s
FAIL
danielgtaylor commented 3 months ago

@mvrahden nice find! This looks like a bug where it isn't treating it as a string value. Thanks for reporting :smile:

mvrahden commented 3 months ago

@danielgtaylor thanks for checking and confirming this. Is there anything I can do in order to fix this? I wouldn't know where to check, so I'd need some pointers to start with.