Skip to content

Releases: danielgtaylor/huma

v1.14.3

11 Sep 22:42
a0d7a96
Compare
Choose a tag to compare

What's Changed

Full Changelog: v1.14.2...v1.14.3

v2.22.1

20 Aug 16:28
77c7a16
Compare
Choose a tag to compare

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

Full Changelog: v2.22.0...v2.22.1

v2.22.0

19 Aug 16:19
e1ffc73
Compare
Choose a tag to compare

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

12 Aug 16:50
5c12ecb
Compare
Choose a tag to compare

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

Full Changelog: v2.20.0...v2.21.0

v2.20.0

30 Jul 02:13
9eae273
Compare
Choose a tag to compare

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

New Contributors

Full Changelog: v2.19.0...v2.20.0

v2.19.0

15 Jul 16:46
9bf5661
Compare
Choose a tag to compare

Overview

Sponsors

A big thank you to our new sponsor:

Multipart Form File Metadata

It's now possible to get filename & size metadata information from multipart form files.

huma.Post(api, "/form-example", func(ctx context.Context, input *struct{
	RawBody huma.MultipartFormFiles[struct {
		HelloWorld   huma.FormFile   `form:"file" contentType:"text/plain" required:"true"`
	}]
}) (*struct{}, error) {
 	fileData := input.RawBody.Data()
	fmt.Println( fileData.HelloWorld.Filename)
	fmt.Println( fileData.HelloWorld.Size)
}

Easier Custom Context When Testing

It's now easier to pass a custom context to operations in the test API. Instead of having to create a custom request with its own context and manually call the adapter you can now use the methods like GetCtx instead of Get.

_, api := humatest.New(t)
ctx := context.Background() // define your necessary context
resp := api.GetCtx(ctx, "/greeting/world") // provide it using the 'Ctx' suffixed methods

Exploded Query Params

It's now possible to use the OpenAPI explode feature where query params are passed multiple times rather than using comma separated values.

huma.Get(api, "/example", func(ctx context.Context, input *struct{
	Value []string `query:"value,explode"`
}) (*struct{}, error) {
	fmt.Println(input.Value)
	return nil, nil
})

You can then make requests like GET /example?value=foo&value=bar.

Autopatch Schema Improvements

Autopatch now uses the PUT schema (modified to all be optional) rather than just relying on an object with any allowed properties, which improves documentation for users. This is automatic so there is no need to configure anything new.

Other Improvements

  • Performance improvement by removing the response body from panics which could be very large.
  • Fixes to min/max items schema generation when using arrays.
  • Remove slices dependency to continue to support Go 1.20 until 1.23 is released (we will support the latest two major versions just like the Go project itself)

What's Changed

New Contributors

Full Changelog: v2.18.0...v2.19.0

v2.18.0

03 Jun 16:35
158b076
Compare
Choose a tag to compare

Overview

Better Multipart Files Uploads

You can now use huma.MultipartFormFiles[YourType] as the request's RawBody in order to handle multipart file uploads. Files can be limited to specific content types and required or optional. Example:

type FileData struct {
        // This is an example, any number of `multipart.File` fields can be defined.
        // Nested structs are not supported.
	SomeFile multipart.File   `form-data:"some_file" content-type:"image/png" required:"true"`
	SeveralFiles []multipart.File `form-data:"several_files" content-type:"image/png,image/jpeg" required:"true"`
}

type FileHandlerInput struct {
	RawBody huma.MultipartFormFiles[FileData]
}

func FileHandler(ctx context.Context, input *FileHandlerInput) (*struct{}, error) {
  fileData := input.RawBody.Data()
  DoSomeThingWith(fileData.SomeFile)
  OrSomethingElseWith(fileData.SeveralFiles)
}

huma.Register(api,
  huma.Operation{
	  Path:             "/handle-files",
	  Method:        http.MethodPost,
	  OperationID:     "Handle files",
  }, FileHandler)

Schema Transformers

It's now possible to have types implement a schema transformer, which lets them modify the generated schema for the type. This option lives in between using the generated types and providing your own schema, and makes it a bit easier to modify the generated schema by not needing you to call into the registry manually. This is the interface:

type SchemaTransformer interface {
	TransformSchema(r Registry, s *Schema) *Schema
}

Simple example:

type MyInput struct {
	Field string `json:"field"`
}

func (i *MyInput) TransformSchema(r huma.Registry, s *huma.Schema) *huma.Schema {
	s.Description = "I am an override"
	return s
}

What's Changed

  • Fix typo in SSE doc by @op-tmplt in #448
  • Feature: handling files from multipart/form-data request by @lsdch in #415
  • feat: allow modifying generated schema by implementing SchemaTransformer interface by @lsdch in #456
  • fix: type schema description not work by @fourcels in #462

New Contributors

Full Changelog: v2.17.0...v2.18.0

v2.17.0

20 May 17:10
b318810
Compare
Choose a tag to compare

Overview

Convenience Method Operation Modifiers

You can now add modifier functions when registering operations using convenience methods like huma.Get:

func OperationTags(tags ...string) func(o *Operation) {
	return func(o *Operation) {
		o.Tags = tags
	}
}

huma.Get(api, "/demo", myHandler, OperationTags("one", "two"))

Use this to build out whatever functionality you need to simplify registration. These can also be composed & joined easily to give a concise way to register operations without falling back to huma.Register directly.

Request Remote Address Access

It's now possible to access the requests's RemoteAddr field through huma.Context for use in middleware and/or resolvers, which works with all routers:

func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	fmt.Println("Request address", ctx.RemoteAddr())
	next(ctx)
}

Fix Unmarshaling of Embedded Body

Unmarshaling of embedded structs with a body field now works as expected:

type BodyContainer struct {
	Body struct {
		Name string `json:"name"`
	}
}

huma.Register(api, huma.Operation{
	Method: http.MethodPost,
	Path:   "/body",
}, func(ctx context.Context, input *struct {
	BodyContainer
}) (*struct{}, error) {
	// Do something with `input.Body.Name`...
	return nil, nil
})

What's Changed

New Contributors

Full Changelog: v2.16.0...v2.17.0

v2.16.0

06 May 15:56
1796483
Compare
Choose a tag to compare

Overview

Drop Deprecated Chi v4 Adapter

This release drops Chi v4 support. Chi v4 has been deprecated & replaced with Chi v5 since 2021, and was included to help migrate from Huma v1 to v2. Huma v2 has now been out for about six months. Keeping Chi v4 in the main package results in extra dependencies and deprecation warnings from automated tools like dependabot. It is highly recommended to discontinue using Chi v4.

If you wish to continue using Chi v4, please copy the v4 adapter from https://github.com/danielgtaylor/huma/blob/v2.15.0/adapters/humachi/humachi.go into your own project. With that simple change you can migrate off at your own pace while continuing to get new Huma releases.

Better Panics

Some panic messages have been improved to be more helpful for users, for example letting them know that the response type must be a struct rather than some confusing reflection panic.

What's Changed

Full Changelog: v2.15.0...v2.16.0

v2.15.0

29 Apr 15:48
3abb70d
Compare
Choose a tag to compare

Sponsors

A big thank you to our new sponsor:

Overview

Schema Discriminator Support

Basic support for the OpenAPI 3.1 discriminator which gives hints to clients that the value of a field defines the type of the response. This is not currently exposed via tags but can be used when manually creating schemas:

s := &huma.Schema{
	OneOf: []*huma.Schema{
		{Type: "object", Properties: map[string]*huma.Schema{
			"type": {Type: "string", Enum: []any{"foo"}},
			"foo":  {Type: "string"},
		}},
		{Type: "object", Properties: map[string]*huma.Schema{
			"type": {Type: "string", Enum: []any{"bar"}},
			"bar":  {Type: "string"},
		}},
	},
	Discriminator: &huma.Discriminator{
		PropertyName: "type",
		Mapping: map[string]string{
			"foo": "#/components/schemas/Foo",
			"bar": "#/components/schemas/Bar",
		},
	},
}

Anonymous Struct Name Hints

Allow providing name hints via field tags for anonymous structs defined inline. This gives a bit more control over the JSON Schema type names:

type EndpointInput struct {
  Body struct {
    SomeData string `json:"some_data"`
  } `name-hint:"SomeName"`
}

Better File Upload UI Support

A contentMediaType field is generated for fields which are format: "binary" which enables a better UI for uploading files in the generated docs.

file upload in docs

Bug Fixes

The generated $schema field now uses http instead of https when the host is 127.0.0.1. Previously this was only the case for localhost.

Pointer types with custom schemas are now better supported by dereferencing the pointer to the underlying type before checking for the custom schema interface implementation.

The built-in Flow router has a fix applied to handle path params that are percent encoded with slashes. Fix has also been submitted upstream.

Fixed a possible panic in the schema link transformer when passed nil body types.

Updated the precondition error locations to match the rest of the project. request.headers.If-Matchheaders.If-Match as we no longer explicitly state it's in the request. It's always in the request.

Fixed an example in the docs that was resulting in an error due to a typo.

What's Changed

New Contributors

Full Changelog: v2.14.0...v2.15.0