Help us shape the future of Go! Take the 2026 Go Developer Survey

problem

package
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 20 Imported by: 1

Documentation

Overview

Package problem implements RFC 9457 problem details with stable codes that clients localize on.

A problem's type is either "about:blank", meaning nothing beyond its HTTP status, or a Type defined in a Registry, served as a relative URI such as "/problems/shop/name_taken". Every defined type has a Code, a short snake_case name that clients use as a message key. For an about:blank problem, clients derive the code from the status with CodeForStatus.

Index

Constants

View Source
const Blank = "about:blank"

Blank is the problem type for a problem with no semantics beyond its status.

View Source
const MediaType = "application/problem+json"

MediaType is the RFC 9457 media type for a problem details object.

Variables

View Source
var (
	Invalid = rule("invalid", "Invalid value",
		"The value is not valid for this field.")
	Required = rule("required", "Value required",
		"The field is required and was missing or empty.")
	TooShort = rule("too_short", "Value too short",
		"The value has fewer characters than the field allows.", "min")
	TooLong = rule("too_long", "Value too long",
		"The value has more characters than the field allows.", "max")
	TooFew = rule("too_few", "Too few items",
		"The list has fewer items than the field allows.", "min")
	TooMany = rule("too_many", "Too many items",
		"The list has more items than the field allows.", "max")
	BelowMinimum = rule("below_minimum", "Number too small",
		"The number is below the field's minimum. exclusive is \"true\" when the minimum itself is not allowed.", "min", "exclusive")
	AboveMaximum = rule("above_maximum", "Number too large",
		"The number is above the field's maximum. exclusive is \"true\" when the maximum itself is not allowed.", "max", "exclusive")
	InvalidFormat = rule("invalid_format", "Invalid format",
		"The value does not have the field's format, such as an email address, a URI or a date.")
	InvalidChoice = rule("invalid_choice", "Not an allowed value",
		"The value is not one of the field's allowed values, listed in allowed.", "allowed")
	InvalidType = rule("invalid_type", "Wrong type",
		"The value has the wrong JSON type, such as a string where a number belongs.")
	UnexpectedField = rule("unexpected_field", "Unknown field",
		"The request has a field the operation does not accept.")
	DuplicateItems = rule("duplicate_items", "Duplicate items",
		"The list has the same item more than once.")
)

The rules a field can fail. They are shared by every application, so their URIs are /problems/<code>. An application whose field fails a check of its own uses one of its Types instead.

Rules lists every rule, in the order above.

View Source
var Shared = func() *Catalog {
	sub, err := fs.Sub(sharedFS, "messages")
	if err != nil {
		panic(err)
	}
	return MustCatalog(sub)
}()

Shared holds the messages for the codes every API shares: the status codes, validation and its rules, and the client-side network and unknown.

View Source
var SharedDocs = func() *Docs {
	sub, err := fs.Sub(sharedDocsFS, "docs")
	if err != nil {
		panic(err)
	}
	return MustDocs(sub)
}()

SharedDocs holds the guidance for the shared codes' pages.

StatusCodes lists the codes CodeForStatus returns.

View Source
var Validation = rule("validation", "Invalid request",
	"One or more fields in the request are invalid. Each entry in errors names the field and the rule it failed.")

Validation is the problem for a request with invalid fields. Its Errors list each one, typed with a rule.

Functions

func Denied

func Denied(err error) bool

Denied reports whether err is, or wraps, a problem marked with AsDenial.

func Handler

func Handler(registries ...*Registry) http.Handler

Handler serves the problem pages for registries in the reader's language. See Pages.

func Pages added in v0.11.0

func Pages(opts PagesOptions) http.Handler

Pages serves the documentation pages problem type URIs resolve to: /problems/ lists every type, /problems/<code> documents a shared one, and /problems/<name>/<code> a type in the registry called name. Each page is in the reader's language (?lang= overrides it, so a shared link keeps its language) and shows the message readers see, why it happens, how to fix it, and the technical details. Mount it at /problems/ on the API's host.

func Pointer

func Pointer(path ...any) string

Pointer returns the JSON Pointer (RFC 6901) to a body field, in the URI fragment form RFC 9457's example uses: Pointer("items", 3, "name") is "#/items/3/name". Pointer() is the whole body, "#".

func Write

func Write(w http.ResponseWriter, p *Problem)

Write sends p as the response.

Types

type Catalog added in v0.11.0

type Catalog struct {
	// contains filtered or unexported fields
}

Catalog holds the messages for problem codes, by language: ICU MessageFormat strings keyed by code, as clients' catalogs are.

func MustCatalog added in v0.11.0

func MustCatalog(fsys fs.FS) *Catalog

MustCatalog is NewCatalog that panics, for catalogs embedded in the binary.

func NewCatalog added in v0.11.0

func NewCatalog(fsys fs.FS) (*Catalog, error)

NewCatalog reads one JSON file per language from fsys, named for the language, such as en.json and ja.json, each an object of messages keyed by code. Every message must be valid; English is required.

func (*Catalog) Has added in v0.11.0

func (c *Catalog) Has(code Code) bool

Has reports whether the catalog has an English message for code.

func (*Catalog) Languages added in v0.11.0

func (c *Catalog) Languages() []string

Languages lists the catalog's languages.

func (*Catalog) Messages added in v0.11.0

func (c *Catalog) Messages(lang string) map[Code]string

Messages returns a copy of lang's messages, keyed by code.

type Code

type Code string

Code is a problem type's stable, machine-readable name. Clients key their messages on it, so a code is part of the API contract: never rename one. The set is open: a client that meets a code it doesn't know falls back to the one for the status.

const (
	InvalidRequest  Code = "invalid_request"
	Unauthenticated Code = "unauthenticated"
	Forbidden       Code = "forbidden"
	NotFound        Code = "not_found"
	Conflict        Code = "conflict"
	RateLimited     Code = "rate_limited"
	Internal        Code = "internal"
	Unavailable     Code = "unavailable"
)

The codes clients derive from the status of an about:blank problem. They are never sent, since about:blank has no members beyond RFC 9457's own.

func CodeForStatus

func CodeForStatus(status int) Code

CodeForStatus is the code for an about:blank problem with the given status.

type Doc added in v0.11.0

type Doc struct {
	// Title replaces the type's English title on its page.
	Title string `json:"title,omitempty"`
	// Why explains what causes the problem.
	Why string `json:"why,omitempty"`
	// Fix lists the steps that resolve it, in order.
	Fix []string `json:"fix,omitempty"`
	// Links point to further reading.
	Links []Link `json:"links,omitempty"`
}

Doc is the guidance a problem type's page shows: what it means, why it happens and how to fix it.

type Docs added in v0.11.0

type Docs struct {
	// contains filtered or unexported fields
}

Docs holds Doc entries for problem codes, by language.

func MustDocs added in v0.11.0

func MustDocs(fsys fs.FS) *Docs

MustDocs is NewDocs that panics, for docs embedded in the binary.

func NewDocs added in v0.11.0

func NewDocs(fsys fs.FS) (*Docs, error)

NewDocs reads one JSON file per language from fsys, named for the language (en.json, ja.json, ...), each an object of Doc entries keyed by code.

type FieldError

type FieldError struct {
	Type      string         `json:"type" format:"uri-reference" doc:"The rule's problem type."`
	Code      Code           `` /* 160-byte string literal not displayed */
	Detail    string         `json:"detail,omitempty" doc:"Explanation, in the language the Content-Language header names; English without it."`
	Pointer   string         `json:"pointer,omitempty" doc:"JSON Pointer to the invalid body field, as a URI fragment such as #/items/0/name."`
	Parameter string         `json:"parameter,omitempty" doc:"Name of the invalid request parameter."`
	In        string         `json:"in,omitempty" enum:"query,path,header,cookie" doc:"Where parameter is."`
	Params    map[string]any `json:"params,omitempty" doc:"Values the rule's localized message shows."`
	// contains filtered or unexported fields
}

FieldError is one entry in a validation problem's errors, the extension RFC 9457 §3 uses in its validation example. Pointer locates a field in the request body; Parameter and In locate a query, path, header or cookie parameter instead.

func (*FieldError) Error

func (e *FieldError) Error() string

Error makes a FieldError an error, so a huma Resolver can return one.

func (FieldError) MarshalJSON added in v0.7.0

func (e FieldError) MarshalJSON() ([]byte, error)

MarshalJSON encodes a field error missing a param its type's message needs as Invalid, so a client never shows a message with a placeholder it cannot fill.

func (*FieldError) With

func (e *FieldError) With(key string, value any) *FieldError

With adds a value the field error's localized message shows.

type Link struct {
	Title string `json:"title"`
	Href  string `json:"href"`
}

Link is a further-reading link on a problem type's page.

type Localizer added in v0.11.0

type Localizer struct {
	// Locale picks the reader's locale, such as "ja" or "es-MX", from the
	// request's headers. Nil means Accept-Language's first language.
	Locale func(http.Header) string
	// Catalogs hold the app's own codes; the first with a code wins. Shared
	// is always consulted after them.
	Catalogs []*Catalog
}

Localizer writes problems in the reader's language: each one's detail, and each field error's, becomes the catalog's message for its code.

func (*Localizer) Language added in v0.11.0

func (l *Localizer) Language(h http.Header) string

Language is the language l writes for a request: the base of its locale when a catalog has it, else English.

func (*Localizer) Localize added in v0.11.0

func (l *Localizer) Localize(lang string, p *Problem) *Problem

Localize returns p as clients receive it (see Resolve), in lang. A problem of a defined type, and each field error, gets its code's message as its detail. An about:blank problem keeps a detail the server wrote when lang is English, since that is the language servers write in, and otherwise gets its status's message, so the reader never sees two languages. p is not modified.

func (*Localizer) Message added in v0.11.0

func (l *Localizer) Message(lang string, code Code, params map[string]any) (msg string, ok bool)

Message is code's message in lang with params, falling back to English when lang lacks it. ok is false when no catalog has code, or the message can't be formatted with params.

func (*Localizer) Write added in v0.11.0

func (l *Localizer) Write(w http.ResponseWriter, r *http.Request, p *Problem)

Write sends p in the language r asks for, with Content-Language set.

type PagesOptions added in v0.11.0

type PagesOptions struct {
	// Registries are the application's problem types, documented beside the
	// shared ones.
	Registries []*Registry
	// Localizer picks the reader's language and renders each type's message.
	// Nil uses Accept-Language and the shared messages only.
	Localizer *Localizer
	// Docs hold the application's guidance; SharedDocs is consulted after them.
	Docs []*Docs
}

PagesOptions configure Pages.

type Problem

type Problem struct {
	Type     string         `` /* 148-byte string literal not displayed */
	Title    string         `json:"title,omitempty" doc:"Short English summary of the problem type, for developers."`
	Status   int            `json:"status,omitempty" doc:"The HTTP status code."`
	Detail   string         `` /* 212-byte string literal not displayed */
	Instance string         `json:"instance,omitempty" format:"uri-reference" doc:"Identifies this occurrence of the problem."`
	Code     Code           `` /* 222-byte string literal not displayed */
	Params   map[string]any `json:"params,omitempty" doc:"Values the code's localized message shows."`
	Errors   []*FieldError  `json:"errors,omitempty" doc:"For a validation problem, each invalid field and the rule it failed."`
	// contains filtered or unexported fields
}

Problem is an RFC 9457 problem details object. Title is English. Detail and each FieldError's Detail are English as written; a Localizer rewrites them in the reader's language and says which with Content-Language. Clients that get no Content-Language show their own message for Code instead.

func Status

func Status(status int, detail string) *Problem

Status returns an about:blank problem: one the status fully describes.

func ValidationFailed

func ValidationFailed(errs ...*FieldError) *Problem

ValidationFailed returns a Validation problem listing errs.

func (*Problem) AsDenial

func (p *Problem) AsDenial() *Problem

AsDenial marks p as a refusal to authorize, so callers can tell it from a failure to work out the answer, such as an unreachable database, which must never be reported as a permission problem. It changes nothing sent.

func (*Problem) ContentType

func (p *Problem) ContentType(string) string

ContentType makes huma serve a Problem as application/problem+json.

func (*Problem) Error

func (p *Problem) Error() string

func (*Problem) GetStatus

func (p *Problem) GetStatus() int

GetStatus returns the HTTP status, so a Problem is a huma.StatusError.

func (*Problem) Key

func (p *Problem) Key() Code

Key is the code clients localize on: Code, or for an about:blank problem, the code its status implies.

func (Problem) MarshalJSON added in v0.7.0

func (p Problem) MarshalJSON() ([]byte, error)

MarshalJSON encodes the problem as Resolve returns it.

func (*Problem) Resolve added in v0.7.0

func (p *Problem) Resolve() *Problem

Resolve returns p as clients receive it. A problem missing a param its type's message needs is sent as the about:blank problem for its status, so a client never shows a message with a placeholder it cannot fill. MarshalJSON sends the resolved problem.

func (*Problem) Unwrap

func (p *Problem) Unwrap() error

Unwrap returns the cause attached with WithCause.

func (*Problem) With

func (p *Problem) With(key string, value any) *Problem

With adds a value the problem's localized message shows.

func (*Problem) WithCause

func (p *Problem) WithCause(err error) *Problem

WithCause attaches the underlying error, for logs. It is never sent.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry holds the problem types of one application. Their URIs are /problems/<name>/<code>, so a host that mounts several applications serves all their pages without collisions.

func NewRegistry

func NewRegistry(name string) *Registry

NewRegistry returns an empty registry for the application name, such as "shop". It panics if name is not a lowercase URI segment.

func (*Registry) Define

func (r *Registry) Define(t Type) *Type

Define adds a type to the registry and returns it. Call it from package variable declarations: it panics on an invalid or duplicate code, or on one that collides with the codes this package defines.

func (*Registry) Lookup

func (r *Registry) Lookup(code Code) (*Type, bool)

Lookup returns the type with the given code.

func (*Registry) Name

func (r *Registry) Name() string

Name is the application name in the registry's URIs.

func (*Registry) Types

func (r *Registry) Types() []*Type

Types returns the registry's types in the order they were defined.

type Type

type Type struct {
	Code   Code
	Status int
	// Title is a short English summary that never varies between occurrences.
	Title string
	// Doc explains, in English, what causes the problem and how to resolve it.
	Doc string
	// Params names the values the type's localized message shows.
	Params []string
	// contains filtered or unexported fields
}

Type is a problem type (RFC 9457 §4): a URI, a short title and the status it is used with. Define one with Registry.Define.

func (*Type) At

func (t *Type) At(pointer, detail string) *FieldError

At returns a field error of this type for the body field at pointer, which Pointer builds.

func (*Type) AtParameter

func (t *Type) AtParameter(in, name, detail string) *FieldError

AtParameter returns a field error of this type for a request parameter. in is "query", "path", "header" or "cookie", as in OpenAPI.

func (*Type) New

func (t *Type) New(detail string) *Problem

New returns a problem of this type.

func (*Type) Newf

func (t *Type) Newf(format string, args ...any) *Problem

Newf returns a problem of this type with a formatted detail.

func (*Type) URI

func (t *Type) URI() string

URI is the type's relative URI, which resolves to its documentation page.

Directories

Path Synopsis
Package humaproblem makes huma produce problem details: every error huma builds, including request validation, becomes a *problem.Problem, and the OpenAPI document lists the codes clients localize on.
Package humaproblem makes huma produce problem details: every error huma builds, including request validation, becomes a *problem.Problem, and the OpenAPI document lists the codes clients localize on.
Package problemrules holds go-ruleguard rules that keep internal errors out of problem responses.
Package problemrules holds go-ruleguard rules that keep internal errors out of problem responses.
Package problemtest checks that a client's message catalog covers an application's problem types.
Package problemtest checks that a client's message catalog covers an application's problem types.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL