Huma Versions Save

Huma REST/HTTP API Framework for Golang with OpenAPI 3.1

v2.15.0

2 weeks ago

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: https://github.com/danielgtaylor/huma/compare/v2.14.0...v2.15.0

v2.14.0

3 weeks ago

Overview

Support for netip.Addr Type

In addition to a net.IP, request inputs can now use a netip.Addr type, passing in a value like 127.0.0.1 from the client in e.g. query params and it will parse into the correct type with validation.

Error Wrapping Improvements

Arbitrary headers can now be added if needed by wrapping errors. For example:

return nil, huma.ErrorWithHeaders(
	huma.Error404NotFound("thing not found"),
	http.Header{
		"Cache-Control": {"no-store"},
	},
)

This uses Go's built-in error wrapping functionality and works via errors.Is/errors.As for any error which satisfies the huma.HeadersError interface.

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.13.1...v2.14.0

v2.13.1

4 weeks ago

Overview

This patch release fixes a small bug where a 204 No Content or 304 Not Modified which included a body would result in a panic. After this fix the body is ignored, making it easier to use the huma.Status304NotModified() utility.

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.13.0...v2.13.1

v2.13.0

1 month ago

Overview

This release includes some minor but important changes that may break some existing users by removing deprecated functionality. I'm really sorry for the potential breaks and will do my best to adhere more strictly to semantic versioning in the future. Be aware that these changes are not taken lightly and possible alternatives were considered. I have also tried to group all the small breaks/changes into a single release to mitigate repeated painful upgrades. Building this type of library is hard and I feel for your frustrations and appreciate your understanding!

As of this release, you can now build a full-fledged Huma service with zero additional hard dependencies (if using the Go 1.22+ built-in huma.ServeMux router and only JSON).

Removal of deprecated huma.NewCLI

Back in version 2.8.0 the huma.NewCLI functionality was deprecated in favor of humacli.New. The deprecated call is now removed.

[!CAUTION] This has the potential to break existing users, but is being treated as a bug fix to remove a hard dependency on spf13/cobra. If you are already using humacli.New there is no need to change anything.

Make CBOR support optional

CBOR is now made optional in the default config. If you wish to continue using it, you must opt-in to a new import, which automatically registers the format with the default configuration:

import (
  "github.com/danielgtaylor/huma/v2"

  _ "github.com/danielgtaylor/huma/v2/formats/cbor"
)

This new behavior is documented at https://huma.rocks/features/response-serialization/#default-formats. In the future this also makes it easier to support other additional formats without adding hard dependencies.

[!WARNING] While not a breaking change per se, without adding the new import your clients will no longer be able to request CBOR and will get JSON responses instead.

humatest no longer requires Chi

The humatest package now uses a tiny internal router rather than relying on the Chi router, which introduced extra dependencies especially for people not using Chi as their main router package. You can continue to use humatest.Wrap to wrap any router you like.

Also new are humatest.DumpRequest/humatest.DumpResponse and humatest.PrintRequest/humatest.PrintResponse utility functions which function similar to httptest.DumpRequest/httptest.DumpResponse but pretty print the body JSON to make debugging and showing examples easier. Usage example: https://go.dev/play/p/QQ9Bi7iws12.

[!CAUTION] This is technically a small breaking change, but is being treated as a bug fix to remove the dependency and make the package more future-proof by not relying on any single external router package and returning http.Handler for the router instead.

Per-operation Middleware

You can now attach middleware to a huma.Operation during registration. These middlewares will only run for that specific operation rather than for all operations:

huma.Register(api, huma.Operation{
	Method: http.MethodGet,
	Path:   "/demo",
	Middlewares: huma.Middlewares{
		func(ctx huma.Context, next func(huma.Context)) {
			// TODO: implement middleware here...
			next(ctx)
		},
	},
}, func(ctx context.Context, input *struct{}) (*struct{}, error) {
	// TODO: implement handler here...
	return nil, nil
})

See also https://huma.rocks/features/middleware/#operations.

Add ctx.Status()

You can now get the response status from a huma.Context in router-agnostic middleware, enabling easier logging/metrics/traces of the response status code.

func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	next(ctx)
	fmt.Println("Response status is", ctx.Status())
}

Better parameter descriptions

Some tools, notably SwaggerUI, require parameters to have descriptions to display properly (descriptions in the schema are not sufficient). These are now generated by Huma, so users opting to render docs with SwaggerUI should now get nicer output.

Bug Fixes

  • The humafiber adapter accepts the correct group interface now.
  • Schema name text transformations for generics are now applied to hints as well as generated names
  • Do not crash when reflect.StructOf fails in schema link transformer
  • Use errors.As when looking for huma.StatusError to enable error wrapping
  • Better error messages when missing OpenAPI config

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.12.0...v2.13.0

v2.12.0

1 month ago

Overview

Support json.RawMessage

There is now better support in the validator for json.RawMessage, which turns into an empty schema (allow anything).

type Demo struct {
  Field1 string          `json:"field1"`
  Field2 json.RawMessage `json:"field2"`
}

OpenAPI 3.0.3

It's now easy to get OpenAPI 3.0.3 from a Huma service for tools that aren't completely compatible with OpenAPI 3.1 just yet. See https://huma.rocks/features/openapi-generation/. The new specs are available at /openapi-3.0.json and /openapi-3.0.yaml by default. Programmatic access is available via api.OpenAPI().Downgrade().

Better Support for Optional / Nullable Types

Huma now has better support for optional/nullable schemas. Broadly speaking, omitempty controls whether a field is optional/required and the use of a pointer controls nullability. As described in:

Huma generates type arrays from pointers to simple scalar types (boolean, integer, number, string) like "type": ["string", "null"]. This is especially useful for frontend languages like Javascript/Typescript where there is a distinction between explicit null vs. undefined and an SDK generator might produce a type union like:

// Generated Typescript example
type GreetingOutputBody = {
  message: string | null;
};

In addition to the default automatic behavior for determining optional/nullable, these can be manually overridden by the user via the required:"true|false" and nullable:"true|false" field tags, ensuring you have full control over how the schema is generated.

[!IMPORTANT]
Types which result in a JSON array or object are not marked as nullable by default, due to the complexity of modeling this in JSON Schema and poor support from SDK generators for Go and some other languages. This may change in a future release. You can mark a struct as nullable manually using a _ field with a nullable:"true" tag as described in the docs linked above.

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.11.0...v2.12.0

v2.11.0

1 month ago

Overview

Sponsors

Big shout out to our new sponsors. Thank you so much! ❤️

  • @victoraugustolls

Stoplight Elements 8.1.0

Stoplight Elements is upgraded to the latest release, version 8.1.0.

Fix omitempty Without Name

Providing a JSON tag without a name but with ,omitempty would result in fields without a name. This is now fixed and uses the same behavior as encoding/json.

type Demo struct {
  Field string `json:",omitempty"`
}

The field name would then be Field in the JSON.

Route Groups & Servers Base Path

There is now better support for route groups in various routers, as well as smart routing for fetching the OpenAPI / JSON schemas when the OpenAPI servers field is set with a base path.

mux := chi.NewMux()
mux.Route("/api", func(r chi.Router) {
	config := huma.DefaultConfig("My API", "1.0.0")
	config.Servers = []*huma.Server{
		{URL: "https://example.com/api"},
	}
	api = humachi.New(r, config)

	// Register operations...
	huma.Get(api, "/demo", func(ctx context.Context, input *struct{}) (*struct{}, error) {
		// TODO: Implement me!
		return nil, nil
	})
})
http.ListenAndServe("localhost:8888", mux)

More docs at https://huma.rocks/features/bring-your-own-router/#route-groups-base-urls.

Schema Field Example

The generated $schema field now shows an example in the docs to help make it easy to see the JSON schema path on the server.

Docs Page Title

If an OpenAPI title is provided, then the docs page title will use it now.

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.10.0...v2.11.0

v2.10.0

1 month ago

Overview

Sponsors

Big shout out to our new sponsors. Thank you so much! ❤️

  • @bclements
  • @bekabaz

Better Support For Recursive Structs

Request/response bodies now have better support for recursive data structures, for example:

type Node struct {
	Value string `json:"value"`
	Left  *Node  `json:"left,omitempty"`
	Right *Node  `json:"right,omitempty"`
}

Multipart Form Support

You can now more easily access the incoming request's multipart form via the RawBody field:

huma.Register(api, huma.Operation{
	OperationID: "upload-files",
	Method:      http.MethodPost,
	Path:        "/upload",
	Summary:     "Example to upload a file",
}, func(ctx context.Context, input struct {
	RawBody multipart.Form
}) (*struct{}, error) {
	// Process multipart form here.
	return nil, nil
})

https://huma.rocks/features/request-inputs/#multipart-form-data

Body Fields Can Be Hidden

You can now hide body fields just like you could e.g. query/header params.

type MyObject struct {
	Public  string `json:"public"`
	Private string `json:"private" hidden:"true"`
}

This is useful if you want the field hidden from the docs but still serialized on the wire, so json:"-" would be inappropriate.

Type Aliases

Besides huma.SchemaProvider, you can now override schema generation behavior by registering known types with aliases, making it possible to override types used in structs from other packages.

registry := huma.NewMapRegistry("#/components/schemas", huma.DefaultSchemaNamer)

registry.RegisterTypeAlias(reflect.TypeFor[some_external_pkg.OptionalStr](), reflect.TypeFor[string]())
registry.RegisterTypeAlias(reflect.TypeFor[some_external_pkg.OptionalDateTime](), reflect.TypeFor[time.Time]())

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.9.0...v2.10.0

v2.9.0

2 months ago

Overview

String Length Validation

String length now counts the UTF8 runes in a string rather than using len(value), making for a more accurate count of the visible characters and being compatible with systems supporting unicode.

Add dependentRequired Validation

You can now utilize JSON Schema's dependentRequired validation which marks a field as required conditional on the presence of another field, for example:

type PaymentInfo struct {
  Name    string `json:"name" doc:"Billing name"`
  Card    int64  `json:"card,omitempty" doc:"Credit card number" dependentRequired:"address"`
  Address string `json:"address,omitempty" doc:"Billing address"`
  IBAN    string `json:"iban,omitempty" doc:"Bank account ID for transfer"`
}

This requires a name and then you can pass an iban for a bank transfer or use a credit card. If the credit card is passed, then validation will fail unless an address is also passed.

Readonly Nested Structs

Nested structs can now utilize the readOnly / writeOnly / etc validation:

type Example struct {
  Field struct {
    Value string `json:"value"`
  } `readOnly:"true"`
}

Better Pattern Errors

String pattern validation using regular expressions can now provide a user-friendly name so that the error says something like expected string to be alphabetical instead of expected string to match pattern ^[a-zA-Z]+$.

type Example struct {
  Field string `json:"field" pattern:"^[a-zA-Z]+$" patternDescription:"alphabetical"`
}

Overriding Fields

A bug was fixed in the generated JSON Schema when overriding fields:

type One struct {
  A string `json:"a"`
  B string `json:"b"`
}

type Two struct {
  One
  B string `json:"-"`
}

This will result in a JSON Schema for Two with only one field: a.

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.8.0...v2.9.0

v2.8.0

2 months ago

Overview

CLI Package

The CLI functionality has been moved into its own package humacli. The existing huma.NewCLI call continues to work but is marked as deprecated and will be removed in a future release. This will be a small breaking change in the future but is necessary to fix a design mistake that impacts dependencies that cannot otherwise be resolved. Migrating is an easy find/replace:

  • huma.NewCLIhumacli.New
  • huma.CLIhumacli.CLI
  • huma.Hookshumacli.Hooks
  • huma.WithOptionshumacli.WithOptions

If you want to ensure your code doesn't include anything from the existing CLI and Cobra functionality, use the humanewclipackage build tag, e.g. go build -tags humanewclipackage. You won't save much space but this can help for package auditing.

Convenience Summary

Convenience functions like huma.Get, huma.Put, etc now generate a human-readable summary of the operation using the path. For example, huma.Get(api, "/things/{id}", ...) would generate a summary of Get things by id.

Easier Context Values

Router-agnostic middleware has gotten a bit easier to write with the new huma.WithValue and huma.WithContext functions to return a wrapped Huma context with its underlying request context.Context replaced. Use it like:

func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	// Wrap the context to add a value.
	ctx = huma.WithValue(ctx, "some-key", "some-value")

	// Call the next middleware in the chain. This eventually calls the
	// operation handler as well.
	next(ctx)
}

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.7.0...v2.8.0

v2.7.0

2 months ago

New Features

Convenience Functions

Convenience functions are available for common HTTP verbs, e.g. huma.Get, huma.Post, huma.Put, etc. These provide less control over the OpenAPI generation, but are significantly less verbose than huma.Register and make it easier to get started, provide quick examples/demos, and more.

huma.Get(api, "/demo", func(ctx context.Context, input *Input) (*Output, error) {
	// ...
})

The OpenAPI operationId field is generated from the path. The behavior can be modified by overriding huma.GenerateOperationID if desired. It's easy to switch to huma.Register at any time if/when you want to provide more information for the OpenAPI generation.

Custom Input Params

You can now use any type that supports encoding.TextUnmarshaler as an input param (path/query/header/cookie). Combined with custom field schemas this is very powerful, and it can use custom request resolvers as well enabling better support for exhaustive error responses to clients. For example, the Google UUID library supports TextUnmarshaler:

import "github.com/google/uuid"

type UUID struct {
	uuid.UUID
}

func (UUID) Schema(r huma.Registry) *huma.Schema {
	return &huma.Schema{Type: huma.TypeString, Format: "uuid"}
}

huma.Get(api, "/demo", func(ctx context.Context, input *struct{
	Example UUID `query:"example"`
}) (*Output, error) {
	// Print out the UUID time portion
	fmt.Println(input.Example.Time())
})

What's Changed

New Contributors

Full Changelog: https://github.com/danielgtaylor/huma/compare/v2.6.0...v2.7.0