Releases: danielgtaylor/huma
v2.27.0
Sponsors
A big thank you to our new sponsors:
Consider sponsoring the project!
Overview
Write Errors & Warnings to Stderr
When writing custom commands that output to stdout, sometimes an error or warning can be generated by Huma, which could add unwanted output to stdout. This has been updated to use stderr so it is easier to differentiate. This is useful for an openapi
command that dumps the OpenAPI document to stdout as it is now safe to redirect it to a file even if warnings are generated.
Better Handling of Embedded Header Fields
When headers are embedded in the output struct they are now properly referenced in the documentation and the parent embedded struct itself is ignored (previously it would include the headers twice). For example, this now works as expected:
// PaginationOutput contains reusable response headers
// for implementing pagination
type PaginationOutput struct {
Link string `header:"Link" doc:"HTTP links for e.g. pagination"`
Cursor string `header:"Cursor" doc:"Identifier that can be used to paginate. If the value is empty then there are no more pages to iterate over."`
}
// list_slates.go
type listSlateResponse struct {
pagination.PaginationOutput
Body []listSlateBody
}
Fiber UserContext
Support
When using the Fiber adapter and getting items from the context it now first checks Fiber's UserContext
before checking the underlying context for the value. This makes Huma easier to use with Fiber and Fiber-specific middleware. No change in behavior is needed, things should Just Work™️.
Remove Chi From Tests
The code has been refactored to remove reliance on Chi for the tests, simplifying the project overall and relying more on the standard library.
Fix Operation Callbacks
Operation callbacks mistakenly used the wrong type of map[string]*PathItem
when it should really have been map[string]map[string]*PathItem
instead. The structure should look something like this, which is now supported properly to document asynchronous callbacks that your operation supports:
paths:
/create:
post:
callbacks:
myEvent:
"{$request.body#/callbackUrl}":
post:
requestBody: # Contents of the callback message
…
Better Support for Embedded RawBody Field
It's now possible to embed the RawBody
field and have things work. For example:
type RequestHeader struct {
Test string `header:"Test"`
}
type EmbedGreeting struct {
RawBody multipart.Form
}
type AnotherGreetingInput struct {
RequestHeader
EmbedGreeting
}
ContentTypeFilter
Now Updates OpenAPI
If an operation output implements ContentTypeFilter
, then this will be called with the default value application/json
and the result used to build the OpenAPI document. For example this will result in application/ld+json
in the OpenAPI rather than application/json
:
type Response struct {
Message string `json:"message" doc:"The message."`
}
func (r *Response) ContentType(t string) string {
return "application/ld+json"
}
type Output struct {
Body Response
}
Other Fixes
- Various doc fixes / improvements
- New linters enabled for better code quality
What's Changed
- fix: write errors/warnings to stderr rather than stdout by @sm3142 in #651
- Exclude embedded fields in response header docs by @lucaspopp-wbd in #656
- Propagate fiberCtx.UserContext() to huma.Context - Context() by @excavador in #659
- Chi is a dep even for pure go implementations, refactor its usage in the main package to pure go. by @fwilkerson in #661
- fix: operation callbacks shape by @danielgtaylor in #665
- feat: support embedded raw body fields by @danielgtaylor in #666
- fix: body with ContentTypeFilter should update OpenAPI by @danielgtaylor in #667
- fix oauth2-jwt.md mermaid rendering issue by @ssoroka-tc in #668
- docs: fix typos in request-validation.md by @alexandear in #671
- chore: enable unconvert and tenv linters by @alexandear in #672
- refactor: simplify error handling for multipart form reading by @alexandear in #673
New Contributors
- @sm3142 made their first contribution in #651
- @excavador made their first contribution in #659
- @fwilkerson made their first contribution in #661
Full Changelog: v2.26.0...v2.27.0
v2.26.0
Overview
Better Marking of Visited Types
When looking for params, headers, defaults, and resolvers the Huma type traversal code now tracks previously visited types more narrowly, continuing to detect recursive loops while allowing multiple adjacent fields to use the same type. Before this fix it would ignore some fields. For example, this now works propertly to run the resolver on both HomeAddress
and AwayAddress
:
type Address struct {
Line1 string `json:"line1" required:"true" minLength:"1" maxLength:"50"`
Line2 string `json:"line2,omitempty" required:"false" minLength:"0" maxLength:"50" default:""`
City string `json:"city" required:"true" minLength:"1" maxLength:"64"`
State string `json:"state" required:"true" minLength:"1" maxLength:"32"`
Zip string `json:"zip" required:"true" minLength:"1" maxLength:"16"`
CountryCode string `json:"countryCode" required:"false" minLength:"1" maxLength:"2" default:"US"`
}
func (a Address) Resolve(_ huma.Context, prefix *huma.PathBuffer) []error {
/* ... do stuff ... */
}
type TestRequestBody struct {
Name string `json:"name"`
Age int `json:"age"`
HomeAddress Address `json:"home" required:"true"`
AwayAddress Address `json:"away" required:"true"`
}
More Resilient Fast Q Value Selection
Several minor bugs have been fixed in the fast zero-allocation q
value parsing for client-based content negotiation via the Accept
header. Values like a;,
no longer cause a panic. Several new tests were added to ensure robustness.
No Longer Panic From Client Disconnect
When a client disconnects and a write to the socket results in an error, we now check if the context is context.Canceled
and ignore it. This should not result in a panic as that has a negative impact on metrics and perceived service health. An attempt is made to tell the huma.Context
that the response status code should be 499 Client Disconnected to help with metrics/logging middleware.
Others
- Fixed doc bug
- Minor fix when printing response bodies in tests if JSON indenting fails
- Refactored code for sending responses to be more consistent & re-used between the handlers and
WriteErr
and ensureNewErrorWithContext
is used everywhere. This should be a no-op for library users.
What's Changed
- fix: doc comments for TestAPI functions by @alexandear in #644
- fix: marking visited types in findInType by @danielgtaylor in #641
- fix: make content negotiation more resilient to bad input by @danielgtaylor in #645
- fix: dump body directly if json indent fails by @danielgtaylor in #643
- refactor: use WriteErr function for handling error returned by handler by @notjustmoney in #640
- fix: do not panic on client disconnect by @danielgtaylor in #650
New Contributors
- @notjustmoney made their first contribution in #640
Full Changelog: v2.25.0...v2.26.0
v2.25.0
Overview
Case-insensitive JSON
Since the standard library Go unmarshaler supports case-insensitive field matches, Huma has been updated to support this during validation as well to better support integration with legacy systems & clients. This behavior can be disabled by explicitly setting huma.ValidateStrictCasing = true
(the default matches the standard library behavior). For example, given:
huma.Put(api, "/demo", func(ctx context.Context, input *struct{
Body struct {
Value string `json:"value"`
}
}) (*struct{}, error) {
fmt.Println("Value is", input.Body.Value)
return nil, nil
})
If a client were to send {"Value": "test"}
instead of {"value": "test"}
it will now pass validation and work. This also works for the built-in CBOR format as well.
Support Scalar Pointers with Defaults
Defaults have become more useful by enabling the use of pointers for basic types to have default values attached. For example, given this input to an operation:
type MyInput struct {
Body struct {
Enabled *bool `json:"enabled,omitempty" default:"true"`
}
}
It's now possible to explicitly send false
without the value being overridden by the default. The behavior seen by the handler code is this:
Client sends | Handler sees |
---|---|
true |
true |
false |
false |
null / undefined |
true |
Since this is using the built-in mechanism to determine if a value was sent, there is no additional performance penalty for setting the default values in Huma.
What's Changed
- Update request-resolvers.md by @alexanderilyin in #619
- docs: fix YAML command example by @danielgtaylor in #624
- feat: case-insensitive field matching for JSON/CBOR by @danielgtaylor in #629
- feat: allow scalar pointers with defaults by @danielgtaylor in #633
- Update build-sdk.sh by @MaximeWeyl in #637
New Contributors
- @MaximeWeyl made their first contribution in #637
Full Changelog: v2.24.0...v2.25.0
v2.24.0
Overview
Better Support of String Subtype Slice Params
It's now possible to use types based on string
like you commonly see for enumerations as slice inputs to Huma operations.
type MyEnum string
const (
Value1 MyEnum = "value1"
Value2 MyEnum = "value2"
// ...
)
huma.Get(api, "/example", func(ctx context.Context, input *struct{
Example []MyEnum `query:"example" enum:"value1,value2"`
}) (*struct{}, error) {
// ...
}
Better Support of non-Object Refs
Fixes a bug that prevented deliberate refs of nullable non-objects from being used due to a panic. It's now possible to do something like this to automatically add enum values from a type when generating the schema and use a $ref
in the JSON Schema:
type InstitutionKind string
const (
Lab InstitutionKind = "Lab"
FoundingAgency InstitutionKind = "FundingAgency"
SequencingPlatform InstitutionKind = "SequencingPlatform"
Other InstitutionKind = "Other"
)
var InstitutionKindValues = []InstitutionKind{
Lab,
FoundingAgency,
SequencingPlatform,
Other,
}
// Register enum in OpenAPI specification
func (u InstitutionKind) Schema(r huma.Registry) *huma.Schema {
if r.Map()["InstitutionKind"] == nil {
schemaRef := r.Schema(reflect.TypeOf(""), false, "InstitutionKind")
schemaRef.Title = "InstitutionKind"
for _, v := range InstitutionKindValues {
schemaRef.Enum = append(schemaRef.Enum, string(v))
}
r.Map()["InstitutionKind"] = schemaRef
}
return &huma.Schema{Ref: "#/components/schemas/InstitutionKind"}
}
Fix Empty Security Marshaling
The empty security object has semantic meaning in OpenAPI 3.x which enables you to override a global security setting to make one or more operations public. It's now possible to do so:
huma.Register(api, huma.Operation{
OperationID: "GetUser",
Method: http.MethodGet,
Path: "/user/{id}",
Security: []map[string][]string{}, // This will not require security!
}, func(ctx context.Context, input *GetUserInput) (*GetUserOutput, error) {
resp := &GetUserOutput{}
resp.Body.Message = "GetUser with ID: " + input.ID + " works!"
return resp, nil
})
Expanded Adapter Interface
The huma.Adapter
interface now has methods for getting the HTTP version and TLS info of the incoming request, which enables better tracing middleware using e.g. OpenTelemetry.
Schema Transformers Automatically Call schema.PrecomputeMessages()
Schema transformers may modify the schema in ways that precomputed messages & validation cache data are no longer valid. This change makes sure to recompute them if the schema has been modified, preventing potential panics.
Configurable Array Nullability
Huma v2.20.0 introduced nullable JSON Schema arrays for Go slices due to the default behavior of Go's JSON marshaler. This change resulted in some clients (e.g. Typescript) now needing to do extra checks even when the service is sure it will never return a nil
slice. This release includes a way to change the global default Huma behavior by setting huma.DefaultArrayNullable = false
. It is still possible to set nullable
on each field to override this behavior, but now it is easier to do so globally for those who wish to use the old (arguably less correct) behavior.
Better SSE Support
This release includes some http.ResponseController
behavior to unwrap response writers to try and get access to SetWriteDeadline
and Flush
methods on response writers. This prevents error messages from being dumped into the console and enables Gin SSE support for the first time with the Huma sse
package. Most routers should Just Work ™️ with SSE now.
Read-Only & Write-Only Behavior Clarification
The read and write-only behavior of Huma validation has been clarified in the docs. See https://huma.rocks/features/request-validation/#read-and-write-only to ensure it works as you expect.
What's Changed
- Fix typo in request-inputs.md by @j0urneyK in #591
- refactor: use type switch instead of if-else by @alexandear in #595
- chore: enable dupword; fix appeared lint issues by @alexandear in #598
- docs: fix typos in docs, comments, and tests by @alexandear in #596
- fix: panic - allow for parameters to be subtype of string by @hlavavit in #592
- fix: check actual type in schema ref when enforcing non-nullable object schemas by @lsdch in #599
- fix: dependency upgrades, fixes #569 by @danielgtaylor in #601
- docs: add new testimonial to README by @danielgtaylor in #602
- fix: marshal empty security object by @danielgtaylor in #603
- docs: add info about read/write only behavior by @danielgtaylor in #608
- docs: add note about required write-only field by @danielgtaylor in #609
- Add api version and tls to adapter by @fntz in #610
- fix: recompute property names after schema transform by @danielgtaylor in #611
- fix: make nullable arrays configurable by @danielgtaylor in #612
- Update your-first-api.md by @alexanderilyin in #615
- feat: unwrap resp for better deadline/flush SSE support by @danielgtaylor in #613
New Contributors
- @j0urneyK made their first contribution in #591
- @alexandear made their first contribution in #595
- @hlavavit made their first contribution in #592
- @fntz made their first contribution in #610
- @alexanderilyin made their first contribution in #615
Full Changelog: v2.23.0...v2.24.0
v2.23.0
Overview
Pointers for Non-Param Fields
It's now possible to use pointers for non-param fields in input structs without Huma complaining. For example, here the User
is not a path/query/header param and is populated from the Authorization
header value for use later:
type EndpointInput struct {
Token string `header:"Authorization"`
User *User
}
func (i *EndpointInput) Resolve(ctx huma.Context) []error {
user, token_valid := ValidateToken(i.Token) // user is nil if token is missing or invalid
i.User = user
return nil
}
Hidden Field Validation
Hidden fields are now validated properly if they are present in the input. For example:
huma.Put(api, "/demo", func(ctx context.Context, input *struct{
Body struct {
Field1 string `json:"field1"
Field2 int `json:"field2" hidden:"true" minimum:"10"`
}
}) (*MyResponse, error) {
// If `input.Field2` is sent by the client, the request will fail
// if its value is below 10 due to the validation schema.
return &MyResponse{...}, nil
})
Prevent Overwriting Schema Validations
All validations now take the existing value of the validator as input when generating the schema, which means a SchemaProvider
or SchemaTransformer
output won't get overwritten when generating schemas. This fixes a bug that was partially fixed but missed several important fields like pattern
.
Non-Addressable Resolver
It's now possible to use non-addressable types which implement Resolver
, such as custom primitive types as map keys. This is currently a little less efficient as a pointer to the type needs to be generated, but at least it is now possible and performance can be improved in the future.
Use the Status Code from NewError
When providing your own custom huma.NewError
function, the resulting error's status code was ignored. This has been fixed to be used as the output status code, enabling the function to modify the status code before going out on the wire.
NewError with a Context
It's now possible to replace huma.NewErrorWithContext
so your error generation function has access to the underlying request context.
NewWithPrefix & Servers
When using humago.NewWithPrefix
and not providing any servers, a single server entry is now generated for you with the given prefix.
Support url.URL
Parameters
You can now use a URL as an input path/query/header parameter and it will be parsed/validated for you.
Request Body Generation Improvements
Like response body generation, the request body generation has been improved to generate missing pieces of the body OpenAPI structure. This enables you to easily e.g. add a description but have Huma still generate the JSON Schema for you. Example:
func (tr TEERouter) RegisterRoutes(api huma.API) {
operation := huma.Operation{
Method: http.MethodPost,
Path: "/tee",
Summary: "TEE",
Description: "TEE description",
RequestBody: &huma.RequestBody{
Description: "My custom request schema",
},
}
huma.Register(api, operation, tr.CalculateTEE)
}
What's Changed
- Allow using pointers for non param fields in input structs by @lsdch in #565
- fix: validate hidden fields by @danielgtaylor in #573
- fix: prevent overwriting schema validations by @danielgtaylor in #575
- fix: support non-addressable resolver values by @danielgtaylor in #580
- fix: use status code returned from NewError when writing errors by @danielgtaylor in #581
- add NewErrorWithContext by @nunoo in #582
- Add default Server object when calling humago.NewWithPrefix by @yursan9 in #579
- fix: unsupported param type url.URL by @ddl-ebrown in #584
- fix: allow setting request description and still gen schema by @danielgtaylor in #587
New Contributors
- @yursan9 made their first contribution in #579
- @ddl-ebrown made their first contribution in #584
Full Changelog: v2.22.1...v2.23.0
v1.14.3
What's Changed
- fix: reset recovery middleware buffer by @danielgtaylor in #572
Full Changelog: v1.14.2...v1.14.3
v2.22.1
Overview
This patch release fixes a bug where the order of operations when resetting a buffer could cause a race condition when putting that buffer back into the shared sync.Pool
for re-use when reading in request bodies.
What's Changed
- fix: order of operations when resetting buffer by @danielgtaylor in #553
Full Changelog: v2.22.0...v2.22.1
v2.22.0
Sponsors
A big thank you to our new sponsor:
Overview
Minimum Go Version: 1.21
The minimum Go version has been upgraded to 1.21, in alignment with the official Go policy. This enables us to fix some critical vulnerabilities with optional dependencies via dependabot and allows the code to be updated to use newer packages like slices
, modernizing the codebase.
Each major Go release is supported until there are two newer major releases. For example, Go 1.5 was supported until the Go 1.7 release, and Go 1.6 was supported until the Go 1.8 release.
https://go.dev/doc/devel/release
Fixes Raw Body Race Condition
This release fixes a critical bug where you could run into a race condition using a shared buffer when accessing a request's RawBody []byte
field. The buffer was getting returned to the sync.Pool
too early, resulting in multiple requests having concurrent access. For handlers which register needing access to the RawBody
field, returning the buffer to the pool is now deferred until after then entire handler has run, fixing the issue.
Warning
If you use the RawBody
feature, you should upgrade immediately. This bug results in incorrect/corrupted data.
Better encoding.TextUnmarshaler
Support
Support for types which implement encoding.TextUnmarshaler
has been improved. The types are now treated as a JSON Schema string
by default, making it easier to set up validation and defaults without needing to provide a custom schema via huma.SchemaProvider
. Among other things this can be used for custom date/time types:
type MyDate time.Time
func (d *MyDate) UnmarshalText(data []byte) error {
t, err := time.Parse(time.RFC3339, string(data))
if err != nil {
return err
}
*d = MyDate(t)
return nil
}
// Later use it in a request
type Request struct {
Date MyDate `json:"date" format:"date-time" example:"2024-01-01T12:00:00Z"`
}
Precompute Schema Validation
Schema validation messages are no longer required to be precomputed manually with a call to schema.PrecomputeMessages()
as this now happens at operation registration time. This simplifies using custom schemas and makes it possible to define them inline with the operation.
If you modify a schema after registration, you must still call PrecomputeMessages()
manually to update the messages.
Fix Nil Response Panic
If an operation is registered as returning a body and a handler mistakenly invokes return nil, nil
(meaning no response, no error) this caused a panic as the body is required. This release changes that behavior to no longer panic, but instead return the operation's default status code instead.
What's Changed
- fix: race by deferring the return of buf to sync.Pool when using RawBody by @nunoo in #542
- fix: automatically precompute schema validation messages by @danielgtaylor in #545
- fix: if err & response are nil, return default status by @danielgtaylor in #546
- feat: Update minimum Go version to 1.21 by @danielgtaylor in #547
- chore(deps): bump github.com/gofiber/fiber/v2 from 2.52.1 to 2.52.5 by @dependabot in #549
- feat: treat encoding.TextUnmarshaler as string in schema by @danielgtaylor in #550
New Contributors
Full Changelog: v2.21.0...v2.22.0
v2.21.0
Overview
Better Support for Default/Example in Custom Schemas
Fixes an issue where custom schemas could have values overwritten by the default instead of using the given value. For example:
type GreetingType int
func (*GreetingType) Schema(r huma.Registry) *huma.Schema {
schema := &huma.Schema{
Type: huma.TypeInteger,
Default: 10,
Examples: []any{1},
}
return schema
}
Better Errors When Using Discriminators
OpenAPI supports using a discriminator field in schemas that use oneOf
to determine which of the included schemas to validate against. Huma now uses this information to generate better error messages like expected required property color to be present
instead of just saying it expected one of the schemas to match. Also handles problems with the discriminator type and value mapping. Example https://go.dev/play/p/5gkNczNJ_jK:
type Cat struct {
Name string `json:"name" minLength:"2" maxLength:"10"`
Kind string `json:"kind" enum:"cat"`
}
type Dog struct {
Color string `json:"color" enum:"black,white,brown"`
Kind string `json:"kind" enum:"dog"`
}
type DogOrCat struct {
Kind string `json:"kind" enum:"cat,dog"`
}
func (v DogOrCat) Schema(r huma.Registry) *huma.Schema {
catSchema := r.Schema(reflect.TypeOf(Cat{}), true, "Cat")
dogSchema := r.Schema(reflect.TypeOf(Dog{}), true, "Dog")
return &huma.Schema{
Type: huma.TypeObject,
Description: "Animal",
OneOf: []*huma.Schema{
{Ref: catSchema.Ref},
{Ref: dogSchema.Ref},
},
Discriminator: &huma.Discriminator{
PropertyName: "kind",
Mapping: map[string]string{
"cat": catSchema.Ref,
"dog": dogSchema.Ref,
},
},
}
}
// ...
huma.Put(api, "/demo", func(ctx context.Context, input *struct {
Body DogOrCat
}) (*DemoResponse, error) {
resp := &DemoResponse{}
resp.Body.Message = "You sent a " + input.Body.Kind
return resp, nil
})
What's Changed
- fix(schema): default value not work by @fourcels in #531
- fix(typo): accmplished -> accomplished by @superstas in #534
- fix(schema): schema example not work in Parameter by @fourcels in #532
- #533: Add Validation for Discriminator with OneOf and Mapping. by @superstas in #536
Full Changelog: v2.20.0...v2.21.0
v2.20.0
Overview
Sponsors
Thank you to Zuplo for sponsoring the project.
Fixed Validation Tags & Referenced Schemas
Sometimes schemas and schemas in references wound up getting overwritten with default values for validation like minimum
, maximum
, minLength
, maxLength
, as well as skipping calling TransformSchema
. This is now fixed to behave as expected by derefencing structs and only setting validation values when the tag is actually present.
Customizable Validation Errors
Validation errors are now completely customizable as globals in the library.
// Set a custom message for minimum validation failure.
huma.MsgExpectedMinimumNumber = "expected number bigger than or equal to %v"
You can also change the function used to format error messages via huma.ErrorFormatter
. This must be overwritten before generating any schemas, as the generated messages are cached at schema creation time.
var ErrorFormatter func(format string, a ...any) string = fmt.Sprintf
Nullable Slices
Due to the way nil
slices get encoded in Go as null
in JSON and other formats, we now generate array schemas as nullable by default. See also the discussion and links to JSON v2 at golang/go#37711.
What's Changed
- fix(schema): proper minLength, maxLength in generated schemas. by @superstas in #513
- fix(schema): proper minimum, maximum in generated schemas. by @superstas in #515
- fix(schema): add dereferencing for SchemaFromType by @superstas in #518
- feat: add zuplo sponsorship by @danielgtaylor in #501
- docs: fix formatting in sponsors by @danielgtaylor in #523
- feat: make validation error messages customizable by @smacker in #520
- fix: nullable schemas for arrays/slices by @lsdch in #527
New Contributors
- @superstas made their first contribution in #513
- @smacker made their first contribution in #520
Full Changelog: v2.19.0...v2.20.0