Skip to content

v2.29.0

Latest
Compare
Choose a tag to compare
@danielgtaylor danielgtaylor released this 18 Feb 21:11
· 2 commits to main since this release
68c578f

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

New Contributors

Full Changelog: v2.28.0...v2.29.0