Overview
Support More Multipart Form Values
This enabled the use of fields with arbitrary types in the form which will get parsed & validated for you:
huma.Register(api, huma.Operation{
OperationID: "upload-and-decode-files"
Method: http.MethodPost,
Path: "/upload",
}, func(ctx context.Context, input *struct {
RawBody huma.MultipartFormFiles[struct {
MyFile huma.FormFile `form:"file" contentType:"text/plain" required:"true"`
SomeOtherFiles []huma.FormFile `form:"other-files" contentType:"text/plain" required:"true"`
NoTagBindingFile huma.FormFile `contentType:"text/plain"`
MyGreeting string `form:"greeting", minLength:"6"`
SomeNumbers []int `form:"numbers"`
NonTaggedValuesAreIgnored string // ignored
}]
}) (*struct{}, error) {
// ...
})
Better Auto-patch Support with Sub-routers
The auto-patch functionality now tries to find a common path prefix so it's possible to do stuff like this and have the generated PATCH
operation function correctly:
func main() {
router := chi.NewRouter()
router.Route("/api", apiMux())
err = http.ListenAndServe(fmt.Sprintf(":%d", 8080), router)
}
// apiMux returns a function that initializes the API routes
func apiMux() func(chi.Router) {
return func(router chi.Router) {
humaConfig := huma.DefaultConfig("API", "dev")
humaConfig = openapi.WithAuthSchemes(humaConfig)
humaConfig = openapi.WithOverviewDoc(humaConfig)
humaConfig = openapi.WithServers(humaConfig, config)
api := humachi.New(router, humaConfig)
huma.Register(api, huma.Operation{
Method: "GET",
Path: "/ressources/{id}",
}, getRessourceByID)
huma.Register(api, huma.Operation{
Method: "PUT",
Path: "/ressources/{id}",
}, updateRessourceByID)
autopatch.AutoPatch(api)
}
}
Custom Param Type Enhancements
Two new interfaces enable some additional advanced customization enhancements when creating operation input parameters:
type ParamWrapper interface {
Receiver() reflect.Value
}
type ParamReactor interface {
OnParamSet(isSet bool, parsed any)
}
These can be used like so:
type OptionalParam[T any] struct {
Value T
IsSet bool
}
// Define schema to use wrapped type
func (o OptionalParam[T]) Schema(r huma.Registry) *huma.Schema {
return huma.SchemaFromType(r, reflect.TypeOf(o.Value))
}
// Expose wrapped value to receive parsed value from Huma
// MUST have pointer receiver
func (o *OptionalParam[T]) Receiver() reflect.Value {
return reflect.ValueOf(o).Elem().Field(0)
}
// React to request param being parsed to update internal state
// MUST have pointer receiver
func (o *OptionalParam[T]) OnParamSet(isSet bool, parsed any) {
o.IsSet = isSet
}
Fix Panic from External Schema
It's possible to use the default schema transformers now with custom external schemas without causing a panic. For example:
Responses: map[string]*huma.Response{
"200": {
Content: map[string]*huma.MediaType{
"application/json": {
Schema: &huma.Schema{
Ref: "https://json-schema.org/draft/2020-12/schema",
},
},
},
},
},
Note: external schemas are not validated and are just there for informational purposes and to help with client generation.
Fiber Fixes
A major rework of the humafiber
adapter was done in #725. This ensures tests are run with the -race
detector and fixes a race that was present in the Fiber adapter. It should be much more stable now.
Deep Object Support for Params
Params now support the OpenAPI deepObject
style, enabling e.g. query params to send structured input in that style.
// GreetingOutput represents the greeting operation response.
type GreetingOutput struct {
Body struct {
Person Person `json:"person"`
Map map[string]string `json:"map"`
}
}
type Person struct {
Name string `json:"name"`
Age int `json:"age,omitempty" default:"20"`
Birthday string `json:"birthday,omitempty"`
}
func main() {
// Create a new router & API
router := chi.NewMux()
api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))
// Register GET /greeting
huma.Get(api, "/greeting", func(ctx context.Context, input *struct {
Person Person `query:"person,deepObject"`
Map map[string]string `query:"map,deepObject"`
}) (*GreetingOutput, error) {
out := &GreetingOutput{}
out.Body.Person = input.Person
out.Body.Map = input.Map
return out, nil
})
// Start the server!
log.Println("http://127.0.0.1:8888/docs")
http.ListenAndServe("127.0.0.1:8888", router)
}
Example request:
curl --request GET \
--url 'http://127.0.0.1:8888/greeting?person%5Bname%5D=foo&map%5Ba%5D=foo&map%5Bb%5D=foo2' \
--header 'Accept: application/json, application/problem+json'
Other fixes
- Prevent double validation errors
- Stop Huma from overwriting a custom request body schema when a
Body
field is present.
What's Changed
- Update middleware.md by @NeroBlackstone in #710
- Update middleware.md by @NeroBlackstone in #712
- feat: allow arbitrary fields in MultipartFormFiles by @b-kamphorst in #706
- fix: double validation errors by @skwair in #718
- fix: implement findRelativeRessourcePath function to fix PatchResourc… by @froz42 in #714
- feat: custom query parameter types by @lsdch in #722
- fix: allow providing a custom request schema by @danielgtaylor in #727
- fix: do not panic when encountering external schemas by @danielgtaylor in #729
- fixed work with fiber.Ctx, fiber.UserContext - fixed graceful shutdown and race condition on access to huma.Context outside handler by @excavador in #725
- feat: add example for type renaming by @shakhzodkudratov in #731
- chore(deps): bump golang.org/x/net from 0.30.0 to 0.33.0 by @dependabot in #732
- feat: param query support deepObject style by @fourcels in #711
New Contributors
- @NeroBlackstone made their first contribution in #710
- @skwair made their first contribution in #718
- @froz42 made their first contribution in #714
- @shakhzodkudratov made their first contribution in #731
Full Changelog: v2.28.0...v2.29.0