rfc9457

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: May 8, 2026 License: MIT Imports: 4 Imported by: 0

README

rfc9457

rfc9457 provides an implementation of the RFC "Problem Details for HTTP APIs" in Go


Usage

See examples/ for extra implementation information for this library.

Suggested Implementation
package main

import (
	"net/http"

	"alpineworks.io/rfc9457"
)

func main() {
	http.Handle("/test", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		rfc9457.NewRFC9457(
			rfc9457.WithStatus(http.StatusTeapot),
			rfc9457.WithDetail("I'm a teapot"),
			rfc9457.WithTitle("Test endpoint hit - I'm a teapot"),
			rfc9457.WithInstance("/test"),
		).ServeHTTP(w, req)
	}))

	http.ListenAndServe(":8080", nil)
}

Output

curl localhost:8080/test

{
	"type": "about:blank",
	"status": 418,
	"title": "Test endpoint hit - I'm a teapot",
	"detail": "I'm a teapot",
	"instance": "/test"
}
Custom problem type

Type defaults to about:blank. Use WithType to point at a documented problem-type URI:

rfc9457.NewRFC9457(
	rfc9457.WithType("https://example.com/problems/insufficient-funds"),
	rfc9457.WithStatus(http.StatusForbidden),
	rfc9457.WithTitle("You do not have enough credit"),
	rfc9457.WithDetail("Your current balance is 30, but charge was 50"),
	rfc9457.WithInstance("/account/12345/transactions/abc"),
).ServeHTTP(w, req)

Per RFC 9457, when decoding a problem with a missing type, FromJSON populates Type with about:blank.

Status-named constructors

For common HTTP problems, the status-named helpers seed Status and a default Title from http.StatusText. Layer on detail/instance/extensions as needed:

rfc9457.NotFound(
	rfc9457.WithDetail("user 42 not found"),
	rfc9457.WithInstance("/users/42"),
	rfc9457.WithExtensions(rfc9457.NewExtension("user_id", 42)),
).ServeHTTP(w, req)

Available helpers: BadRequest, Unauthorized, Forbidden, NotFound, MethodNotAllowed, Conflict, Gone, UnprocessableEntity, TooManyRequests, InternalServerError, NotImplemented, BadGateway, ServiceUnavailable, GatewayTimeout.

Extensions

Extension members flatten into the top-level JSON object on encode and are pulled back into Extensions on decode:

rfc9457.NewRFC9457(
	rfc9457.WithStatus(http.StatusBadRequest),
	rfc9457.WithTitle("Validation failed"),
	rfc9457.WithExtensions(
		rfc9457.NewExtension("trace_id", "abc-123"),
		rfc9457.NewExtension("invalid_params", []map[string]string{
			{"name": "email", "reason": "must be a valid email"},
		}),
	),
).ServeHTTP(w, req)

WithExtensions is additive across calls — each invocation merges into the existing map (later keys overwrite earlier ones), so option helpers can compose cleanly.

Extension keys that collide with reserved members (type, status, title, detail, instance) cause ToJSON / MarshalJSON to return ErrExtensionKeyCollision.

Wrapping a Go error

FromError builds a problem with Title from http.StatusText(status) and Detail from err.Error():

if err := doWork(); err != nil {
	rfc9457.FromError(err, http.StatusInternalServerError).ServeHTTP(w, req)
	return
}

*RFC9457 also implements the error interface, so a problem can be returned and inspected as a Go error.

Encoding and decoding
// Encode
problem := rfc9457.BadRequest(rfc9457.WithDetail("missing field 'email'"))
body, err := problem.ToJSON() // []byte

// Decode
parsed, err := rfc9457.FromJSON(body)

*RFC9457 also implements json.Marshaler and json.Unmarshaler, so it works directly with encoding/json.

Sentinel errors returned by this package: ErrUnableToMarshalJSON, ErrUnableToUnmarshalJSON, ErrExtensionKeyCollision. Use errors.Is to check them.

HTTP serving behavior

*RFC9457 implements http.Handler. ServeHTTP:

  • Sets Content-Type to application/problem+json (also exposed as rfc9457.ProblemContentType).
  • Writes r.Status as the response code, defaulting to 500 Internal Server Error when Status is unset.
  • If serialization fails (for example, an extension key collides with a reserved field), writes a minimal fallback problem body with status 500 instead of leaving the response empty.

Documentation

Overview

Package rfc9457 is a Go implementation of RFC 9457 "Problem Details for HTTP APIs".

A problem detail describes a specific occurrence of an HTTP error in a machine-readable, structured format. Build one with NewRFC9457 and the With* options, or with one of the status-named constructors such as NotFound or BadRequest.

*RFC9457 implements http.Handler, [error], encoding/json.Marshaler, and encoding/json.Unmarshaler, so a problem can be served directly, returned from functions as a Go error, and round-tripped through encoding/json.

https://datatracker.ietf.org/doc/html/rfc9457

Index

Constants

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

Variables

View Source
var (
	ErrUnableToMarshalJSON   = errors.New("rfc9457: unable to marshal json")
	ErrUnableToUnmarshalJSON = errors.New("rfc9457: unable to unmarshal json")
	ErrExtensionKeyCollision = errors.New("rfc9457: extension key collides with reserved field")
)

Functions

This section is empty.

Types

type Extension

type Extension struct {
	Key   string
	Value any
}

Extension is a single extension member: a JSON key and its value.

func NewExtension

func NewExtension(key string, value any) Extension

NewExtension constructs an Extension.

type RFC9457

type RFC9457 struct {
	// Type is a JSON string containing a URI reference that identifies the
	// problem type. Consumers MUST use the "type" URI (after resolution, if
	// necessary) as the problem type's primary identifier. When this member
	// is not present, its value is assumed to be "about:blank".
	//
	// https://datatracker.ietf.org/doc/html/rfc9457#name-type
	Type string `json:"type"`

	// Status is a JSON number indicating the HTTP status code generated by
	// the origin server for this occurrence of the problem. The "status"
	// member, if present, is only advisory; it conveys the HTTP status code
	// used for the convenience of the consumer.
	//
	// https://datatracker.ietf.org/doc/html/rfc9457#name-status
	Status int `json:"status,omitempty"`

	// Title is a JSON string containing a short, human-readable summary of
	// the problem type. It SHOULD NOT change from occurrence to occurrence
	// of the problem, except for localization. The "title" string is
	// advisory and is included only for users who are unaware of and cannot
	// discover the semantics of the type URI.
	//
	// https://datatracker.ietf.org/doc/html/rfc9457#name-title
	Title string `json:"title,omitempty"`

	// Detail is a JSON string containing a human-readable explanation
	// specific to this occurrence of the problem. The "detail" string, if
	// present, ought to focus on helping the client correct the problem,
	// rather than giving debugging information.
	//
	// https://datatracker.ietf.org/doc/html/rfc9457#name-detail
	Detail string `json:"detail,omitempty"`

	// Instance is a JSON string containing a URI reference that identifies
	// the specific occurrence of the problem.
	//
	// https://datatracker.ietf.org/doc/html/rfc9457#name-instance
	Instance string `json:"instance,omitempty"`

	// Extensions are additional members defined by the problem type. They
	// are flattened into the top-level JSON object on encode and pulled
	// back here on decode. Extension keys must not collide with any
	// reserved member name.
	//
	// https://datatracker.ietf.org/doc/html/rfc9457#name-extension-members
	Extensions map[string]any `json:"-"`
}

RFC9457 represents a problem details object as defined by RFC 9457 "Problem Details for HTTP APIs".

https://datatracker.ietf.org/doc/html/rfc9457

func BadGateway added in v1.1.0

func BadGateway(options ...RFC9457Option) *RFC9457

BadGateway builds a problem with status 502 and the matching standard title.

func BadRequest added in v1.1.0

func BadRequest(options ...RFC9457Option) *RFC9457

BadRequest builds a problem with status 400 and the matching standard title.

func Conflict added in v1.1.0

func Conflict(options ...RFC9457Option) *RFC9457

Conflict builds a problem with status 409 and the matching standard title.

func Forbidden added in v1.1.0

func Forbidden(options ...RFC9457Option) *RFC9457

Forbidden builds a problem with status 403 and the matching standard title.

func FromError added in v1.1.0

func FromError(err error, status int) *RFC9457

FromError builds a problem from a Go error and HTTP status, using http.StatusText(status) as the title and err.Error() as the detail.

func FromJSON added in v1.0.1

func FromJSON(data []byte) (*RFC9457, error)

FromJSON decodes a problem from JSON. Errors are wrapped with ErrUnableToUnmarshalJSON.

func GatewayTimeout added in v1.1.0

func GatewayTimeout(options ...RFC9457Option) *RFC9457

GatewayTimeout builds a problem with status 504 and the matching standard title.

func Gone added in v1.1.0

func Gone(options ...RFC9457Option) *RFC9457

Gone builds a problem with status 410 and the matching standard title.

func InternalServerError added in v1.1.0

func InternalServerError(options ...RFC9457Option) *RFC9457

InternalServerError builds a problem with status 500 and the matching standard title.

func MethodNotAllowed added in v1.1.0

func MethodNotAllowed(options ...RFC9457Option) *RFC9457

MethodNotAllowed builds a problem with status 405 and the matching standard title.

func NewRFC9457

func NewRFC9457(options ...RFC9457Option) *RFC9457

NewRFC9457 builds a problem details object, defaulting Type to "about:blank" before applying the supplied options.

func NotFound added in v1.1.0

func NotFound(options ...RFC9457Option) *RFC9457

NotFound builds a problem with status 404 and the matching standard title.

func NotImplemented added in v1.1.0

func NotImplemented(options ...RFC9457Option) *RFC9457

NotImplemented builds a problem with status 501 and the matching standard title.

func ServiceUnavailable added in v1.1.0

func ServiceUnavailable(options ...RFC9457Option) *RFC9457

ServiceUnavailable builds a problem with status 503 and the matching standard title.

func TooManyRequests added in v1.1.0

func TooManyRequests(options ...RFC9457Option) *RFC9457

TooManyRequests builds a problem with status 429 and the matching standard title.

func Unauthorized added in v1.1.0

func Unauthorized(options ...RFC9457Option) *RFC9457

Unauthorized builds a problem with status 401 and the matching standard title.

func UnprocessableEntity added in v1.1.0

func UnprocessableEntity(options ...RFC9457Option) *RFC9457

UnprocessableEntity builds a problem with status 422 and the matching standard title.

func (*RFC9457) Error added in v1.1.0

func (r *RFC9457) Error() string

Error implements the error interface so a problem can be returned as a Go error. It is safe to call on a nil receiver.

func (*RFC9457) MarshalJSON added in v1.1.0

func (r *RFC9457) MarshalJSON() ([]byte, error)

MarshalJSON serializes the problem, flattening Extensions into the top-level object. Returns ErrExtensionKeyCollision if an extension key collides with a reserved member name.

func (*RFC9457) ServeHTTP added in v1.0.2

func (r *RFC9457) ServeHTTP(w http.ResponseWriter, _ *http.Request)

ServeHTTP writes the problem as a application/problem+json response. If Status is unset, it defaults to 500 Internal Server Error. If serialization fails, a minimal problem body describing the failure is written instead.

func (*RFC9457) ToJSON

func (r *RFC9457) ToJSON() ([]byte, error)

ToJSON returns the problem encoded as JSON.

func (*RFC9457) UnmarshalJSON added in v1.1.0

func (r *RFC9457) UnmarshalJSON(data []byte) error

UnmarshalJSON populates the problem and pulls any non-reserved members into Extensions. A missing "type" defaults to "about:blank" per RFC 9457.

type RFC9457Option

type RFC9457Option func(*RFC9457)

RFC9457Option configures an RFC9457 in NewRFC9457.

func WithDetail

func WithDetail(d string) RFC9457Option

WithDetail sets a human-readable explanation specific to this occurrence of the problem.

func WithExtensions

func WithExtensions(e ...Extension) RFC9457Option

WithExtensions adds extension members to the problem. Calling it multiple times merges entries; later keys overwrite earlier ones.

func WithInstance

func WithInstance(i string) RFC9457Option

WithInstance sets a URI reference identifying the specific occurrence of the problem (typically the request path).

func WithStatus

func WithStatus(i int) RFC9457Option

WithStatus sets the HTTP status code on the problem.

func WithTitle

func WithTitle(t string) RFC9457Option

WithTitle sets a short, human-readable summary of the problem type.

func WithType

func WithType(t string) RFC9457Option

WithType sets the problem type URI. Use this to point at a documented problem-type definition; otherwise leave the default of "about:blank".

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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