Documentation
¶
Overview ¶
Package problem implements the RFC 9457 problem details specification in Go.
It also provides some functionality for directly responding to HTTP requests with problems and for defining reusable problem types.
Index ¶
- Constants
- Variables
- func Handler(next http.Handler) http.Handler
- func Is(err error, t *Type) bool
- type Details
- func (d *Details) Error() string
- func (d *Details) MarshalJSON() ([]byte, error)
- func (d *Details) MarshalJSONTo(enc *jsontext.Encoder) error
- func (d *Details) ServeHTTP(w http.ResponseWriter, _ *http.Request)
- func (d *Details) UnmarshalJSON(b []byte) error
- func (d *Details) UnmarshalJSONFrom(dec *jsontext.Decoder) error
- func (d *Details) Unwrap() error
- type Option
- type Type
Constants ¶
const ( // AboutBlankTypeURI is the default problem type and is equivalent to not specifying a problem type. // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-aboutblank AboutBlankTypeURI = "about:blank" )
const ( // ContentType is the media type used for problem responses, as defined by IANA. // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-iana-considerations ContentType = "application/problem+json" )
Variables ¶
var InternalServerError = &Details{ Status: http.StatusInternalServerError, Title: "Internal Server Error", }
InternalServerError is used by Handler to serve as response if no callback is defined.
Functions ¶
func Handler ¶
Handler wraps the given http.Handler and automatically recovers panics from given handler.
When recovering from a panic, if the recovered value is an error, the handler will first try converting it into a value of type *Details using errors.As and, if successful, serve the value using Details.ServeHTTP.
Otherwise InternalServerError is served as response.
func Is ¶
Is returns true if the given error can be converted to a *Details using errors.As and the URI, Title and Status match the given type.
If any of [Type.URI], [Type.Title] or [Type.Status] is empty / zero, the field is skipped.
For example, for a type with only a URI and no title or status, only the URI will be compared.
Types ¶
type Details ¶
type Details struct { // Type contains the problem type as a URI. // // If empty, this is the same as "about:blank". See [AboutBlankTypeURI] for more information. // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-type Type string // Status is indicating the HTTP status code generated for this occurrence of the problem. // // This should be the same code as used for the HTTP response and is only advisory. // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-status Status int // Title is string containing a short, human-readable summary of the problem type // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-title Title string // Detail is string containing a human-readable explanation specific to this occurrence of the problem. // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-detail Detail string // Instance is string containing a URI reference that identifies the specific occurrence of the problem // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-instance Instance string // Extensions contains any extensions that should be added to the response. // // If the problem was parsed from a JSON response this will include all extension fields. // // See also https://datatracker.ietf.org/doc/html/rfc9457#name-extension-members Extensions map[string]any // Underlying optionally contains the underlying error that lead to / is described by this problem. // // This field is not part of RFC 9457 and is neither included in generated JSON nor populated during unmarshaling. Underlying error }
Details defines an RFC 9457 problem details object.
Details also implements the [error] interface and can optionally wrap an existing [error] value.
func From ¶
From returns the problem returned as part of the given HTTP response if any.
As a special case, if [Details.Status] would be 0, it will instead be set to the response status code.
The response body will be closed automatically.
If the response is not of type application/problem+json, the function returns nil, nil and does not close the body.
func New ¶
New returns a new Details instance using the given type, status and title.
It is also possible to set the Detail and Instance fields as well as extensions by providing one or more Option values.
Most users should prefer creating a Details instance via a struct literal or using Type.Details instead.
func (*Details) Error ¶
Error implements the error interface. The returned value is the same as d.Title.
func (*Details) MarshalJSON ¶
MarshalJSON implements the json.Marshaler interface.
See MarshalJSONTo for details.
func (*Details) MarshalJSONTo ¶
MarshalJSONTo implements the json.MarshalerTo interface.
If no Type is set, "about:blank" is used. See also AboutBlankTypeURI.
Extension fields named "type", "status", "title", "detail" or "instance" are ignored when marshaling in favor of the respective struct fields even if the field is empty.
func (*Details) ServeHTTP ¶
func (d *Details) ServeHTTP(w http.ResponseWriter, _ *http.Request)
ServeHTTP encodes the value as JSON and writes it to the given response writer.
If encoding fails, no data will be written and ServeHTTP will panic.
ServeHTTP deletes any existing Content-Length header, sets Content-Type to “application/problem+json”, and sets X-Content-Type-Options to “nosniff”.
If set the Status field is used to set the HTTP status. Otherwise http.StatusInternalServerError is used.
ServeHTTP implements the http.Handler interface.
func (*Details) UnmarshalJSON ¶
UnmarshalJSON implements the json.Unmarshaler interface.
See UnmarshalJSONV2 for details.
func (*Details) UnmarshalJSONFrom ¶
UnmarshalJSONFrom implements the json.UnmarshalerFrom interface.
As required by RFC 9457 UnmarshalJSONV2 will ignore values for known fields if those values have the wrong type.
For example if the parsed JSON contains a field "status" with the code "400" as a JSON string, the field will be ignored even if it may be possible to parse it as an integer.
type Option ¶
type Option func(*Details)
Option defines functional options that can be used to fill in optional values when creating a Details via New or via Type.Details.
func WithDetail ¶
WithDetail sets the Detail for a new Details value.
func WithExtension ¶
WithExtension adds the given key-value pair to the Extensions of a new Details value.
func WithExtensions ¶
WithExtensions adds the values to the Extensions of a new Details value.
func WithInstance ¶
WithInstance sets the Instance for a new Details value.
func WithStatus ¶
WithStatus sets the Status for a new Details value.
func WithUnderlying ¶
WithUnderlying sets the given value as the underlying error of a new Details value.
type Type ¶
type Type struct { // URI defines the type URI (typically, with the "http" or "https" scheme) URI string // Title contains a short, human-readable summary of the problem type. Title string // Status is the HTTP status code that should be used for responses. Status int // Extensions contains fixed extensions that are automatically added to Details instances // created from this type. Extensions map[string]any }
Type defines a specific problem type that can be used to create new Details instances.
The main use case is as package-level variables that can than be used across different types and functions. These types can reduce boilerplate and serve as part of the documentation.
Example:
var OutOfCreditProblemType = &problem.Type{ URI: "https://example.com/probs/out-of-credit", Title: "You do not have enough credit.", Status: http.StatusForbidden, }
Than in a handler:
type (s *MyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { // ... if outOfCredit { OutOfCreditProblemType.Details().ServeHTTP(w, r) return } // ... }
When available, extra information can be added using [Option]s:
if outOfCredit { OutOfCreditProblemType.Details( problem.WithDetail("Your current balance is 30, but that costs 50."), problem.WithInstance("/account/12345/msgs/abc"), problem.WithExtension("balance", 30), problem.WithExtension("accounts", []string{"/account/12345", "/account/67890"}), ).ServeHTTP(w, r) return }