Documentation
¶
Overview ¶
Package problems provides an RFC-9457 (https://tools.ietf.org/html/rfc9457) and RFC 7807 (https://tools.ietf.org/html/rfc7807) compliant implementation of HTTP problem details. Which are defined as a means to carry machine-readable details of errors in an HTTP response to avoid the need to define new error response formats for HTTP APIs.
The problem details specification was designed to allow for schema extensions. There are two possible ways to create problem extensions:
1. You can embed a problem in your extension problem type. 2. You can use the ExtendedProblem to leverage the existing types in this library.
See the examples for references on how to use either of these extension mechanisms.
Additionally, this library also ships with default http.HandlerFunc implementations which are capable of writing problems to a http.ResponseWriter in either of the two standard media formats, JSON and XML.
Index ¶
- Constants
- Variables
- func NewErrInvalidProblemType(value string, e error) error
- func ProblemHandler(p *Problem) http.HandlerFunc
- func XMLProblemHandler(p *Problem) http.HandlerFunc
- type ErrInvalidProblemType
- type ExtendedProblem
- func (p *ExtendedProblem[T]) Error() string
- func (p *ExtendedProblem[T]) Validate() (*ValidExtendedProblem[T], error)
- func (p *ExtendedProblem[T]) WithDetail(detail string) *ExtendedProblem[T]
- func (p *ExtendedProblem[T]) WithDetailf(format string, args ...interface{}) *ExtendedProblem[T]
- func (p *ExtendedProblem[T]) WithError(err error) *ExtendedProblem[T]
- func (p *ExtendedProblem[T]) WithExtension(ext T) *ExtendedProblem[T]
- func (p *ExtendedProblem[T]) WithInstance(instance string) *ExtendedProblem[T]
- func (p *ExtendedProblem[T]) WithStatus(status int) *ExtendedProblem[T]
- func (p *ExtendedProblem[T]) WithTitle(title string) *ExtendedProblem[T]
- func (p *ExtendedProblem[T]) WithType(typ string) *ExtendedProblem[T]
- type Problem
- func (p *Problem) Error() string
- func (p *Problem) Validate() (*ValidProblem, error)
- func (p *Problem) WithDetail(detail string) *Problem
- func (p *Problem) WithDetailf(format string, args ...interface{}) *Problem
- func (p *Problem) WithError(err error) *Problem
- func (p *Problem) WithInstance(instance string) *Problem
- func (p *Problem) WithStatus(status int) *Problem
- func (p *Problem) WithTitle(title string) *Problem
- func (p *Problem) WithType(typ string) *Problem
- type ValidExtendedProblem
- type ValidProblem
Examples ¶
Constants ¶
const ( // ProblemMediaType is the default media type for a Problem response ProblemMediaType = "application/problem+json" // ProblemMediaTypeXML is the XML variant on the Problem Media type ProblemMediaTypeXML = "application/problem+xml" // DefaultURL is the default url to use for problem types DefaultURL = "about:blank" )
Variables ¶
var ErrTitleMustBeSet = fmt.Errorf("%s: problem title must be set", errPrefix)
ErrTitleMustBeSet is the error returned from a call to ValidateProblem if the problem is validated without a title.
Functions ¶
func NewErrInvalidProblemType ¶
NewErrInvalidProblemType returns a new ErrInvalidProblemType instance which wraps the provided error.
func ProblemHandler ¶
func ProblemHandler(p *Problem) http.HandlerFunc
ProblemHandler returns a http.HandlerFunc which writes a provided problem to a http.ResponseWriter as JSON with the status code.
func XMLProblemHandler ¶
func XMLProblemHandler(p *Problem) http.HandlerFunc
XMLProblemHandler returns a http.HandlerFunc which writes a provided problem to a http.ResponseWriter as XML with the status code.
Types ¶
type ErrInvalidProblemType ¶
ErrInvalidProblemType is the error type returned if a problems type is not a valid URI when it is validated. The inner Err will contain the error returned from attempting to parse the invalid URI.
func (*ErrInvalidProblemType) Error ¶
func (e *ErrInvalidProblemType) Error() string
type ExtendedProblem ¶ added in v1.0.0
type ExtendedProblem[T any] struct { Problem // Extensions allows for Problem type definitions to extend the standard // problem details object with additional members that are specific to that // problem type. Extensions T `json:"extensions,omitempty" xml:"extensions,omitempty"` }
An ExtendedProblem extends the Problem type with a new field, Extensions, of type T.
Example ¶
package main import ( "encoding/json" "fmt" "net/http" "github.com/moogar0880/problems" ) func main() { type CreditProblemExt struct { Balance float64 `json:"balance"` Accounts []string `json:"accounts"` } problem := problems.NewExt[CreditProblemExt](). WithStatus(http.StatusForbidden). WithDetail("You do not have sufficient funds to complete this transaction."). WithExtension(CreditProblemExt{ Balance: 30, Accounts: []string{"/account/12345", "/account/67890"}, }) b, _ := json.MarshalIndent(problem, "", " ") fmt.Println(string(b)) }
Output: { "type": "about:blank", "title": "Forbidden", "status": 403, "detail": "You do not have sufficient funds to complete this transaction.", "extensions": { "balance": 30, "accounts": [ "/account/12345", "/account/67890" ] } }
Example (Embedding) ¶
package main import ( "encoding/json" "fmt" "net/http" "github.com/moogar0880/problems" ) func main() { type CreditProblem struct { problems.Problem Balance float64 `json:"balance"` Accounts []string `json:"accounts"` } problem := &CreditProblem{ Problem: *problems.New(). WithStatus(http.StatusForbidden). WithDetail("You do not have sufficient funds to complete this transaction."), Balance: 30, Accounts: []string{"/account/12345", "/account/67890"}, } b, _ := json.MarshalIndent(problem, "", " ") fmt.Println(string(b)) }
Output: { "type": "about:blank", "title": "Forbidden", "status": 403, "detail": "You do not have sufficient funds to complete this transaction.", "balance": 30, "accounts": [ "/account/12345", "/account/67890" ] }
func ExtFromError ¶ added in v1.0.0
func ExtFromError[T any](err error) *ExtendedProblem[T]
ExtFromError returns a new ExtendedProblem instance which contains the string version of the provided error as the details of the problem.
func Extend ¶ added in v1.0.0
func Extend[T any](p *Problem, ext T) *ExtendedProblem[T]
Extend allows you to convert a standard Problem instance to an ExtendedProblem with the provided extension data.
func NewExt ¶ added in v1.0.0
func NewExt[T any]() *ExtendedProblem[T]
NewExt returns a new ExtendedProblem with all the same default values as applied by a call to New.
func (*ExtendedProblem[T]) Error ¶ added in v1.0.0
func (p *ExtendedProblem[T]) Error() string
Error implements the error interface and allows a Problem to be used as a native error.
func (*ExtendedProblem[T]) Validate ¶ added in v1.0.0
func (p *ExtendedProblem[T]) Validate() (*ValidExtendedProblem[T], error)
Validate validates the content of the ExtendedProblem instance. If the ExtendedProblem is invalid, as defined by RFC-9457, then an error explaining the validation error is returned. Otherwise, a sealed ValidProblem instance is returned.
See the documentation for ErrTitleMustBeSet and ErrInvalidProblemType for more information on the validation errors returned by this method.
func (*ExtendedProblem[T]) WithDetail ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithDetail(detail string) *ExtendedProblem[T]
WithDetail sets the detail message to the provided string.
func (*ExtendedProblem[T]) WithDetailf ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithDetailf(format string, args ...interface{}) *ExtendedProblem[T]
WithDetailf behaves identically to WithDetail, but allows consumers to provide a format string and arguments which will be formatted internally.
func (*ExtendedProblem[T]) WithError ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithError(err error) *ExtendedProblem[T]
WithError sets the detail message to the provided error.
func (*ExtendedProblem[T]) WithExtension ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithExtension(ext T) *ExtendedProblem[T]
WithExtension sets the extensions value to the provided extension of type T.
func (*ExtendedProblem[T]) WithInstance ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithInstance(instance string) *ExtendedProblem[T]
WithInstance sets the instance uri to the provided string.
func (*ExtendedProblem[T]) WithStatus ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithStatus(status int) *ExtendedProblem[T]
WithStatus sets the status field to the provided int.
If no title is set then this call will also set the title to the return value of http.StatusText for the provided status code.
func (*ExtendedProblem[T]) WithTitle ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithTitle(title string) *ExtendedProblem[T]
WithTitle sets the title field to the provided string.
func (*ExtendedProblem[T]) WithType ¶ added in v1.0.0
func (p *ExtendedProblem[T]) WithType(typ string) *ExtendedProblem[T]
WithType sets the type field to the provided string.
type Problem ¶
type Problem struct { // Type contains a URI that identifies the problem type. This URI will, // ideally, contain human-readable documentation for the problem when // de-referenced. Type string `json:"type" xml:"type"` // Title is a short, human-readable summary of the problem type. This title // SHOULD NOT change from occurrence to occurrence of the problem, except // for purposes of localization. Title string `json:"title" xml:"title"` // The HTTP status code for this occurrence of the problem. Status int `json:"status,omitempty" xml:"status,omitempty"` // A human-readable explanation specific to this occurrence of the problem. Detail string `json:"detail,omitempty" xml:"detail,omitempty"` // A URI that identifies the specific occurrence of the problem. This URI // may or may not yield further information if de-referenced. Instance string `json:"instance,omitempty" xml:"instance,omitempty"` }
A Problem defines all the standard problem detail fields as defined by RFC-9457 and can easily be serialized to either JSON or XML.
To add extensions to a Problem definition, see ExtendedProblem or consider embedding a Problem in your extension struct.
func FromError ¶ added in v1.0.0
FromError returns a new Problem instance which contains the string version of the provided error as the details of the problem.
Example ¶
package main import ( "encoding/json" "errors" "fmt" "net/http" "github.com/moogar0880/problems" ) func main() { err := func() error { // Some fallible function. return errors.New("something bad happened") }() internalServerError := problems.FromError(err).WithStatus(http.StatusInternalServerError) b, _ := json.MarshalIndent(internalServerError, "", " ") fmt.Println(string(b)) }
Output: { "type": "about:blank", "title": "Internal Server Error", "status": 500, "detail": "something bad happened" }
func New ¶ added in v1.0.0
func New() *Problem
New returns a new Problem instance with the type field set to DefaultURL.
func NewDetailedProblem ¶
NewDetailedProblem returns a new Problem with a Detail string set for a more detailed explanation of the problem being returned.
func NewStatusProblem ¶
NewStatusProblem will generate a default problem for the provided HTTP status code. The Problem's Status field will be set to match the status argument, and the Title will be set to the default Go status text for that code.
Example ¶
package main import ( "encoding/json" "fmt" "github.com/moogar0880/problems" ) func main() { notFound := problems.NewStatusProblem(404) b, _ := json.MarshalIndent(notFound, "", " ") fmt.Println(string(b)) }
Output: { "type": "about:blank", "title": "Not Found", "status": 404 }
Example (Detailed) ¶
package main import ( "encoding/json" "fmt" "github.com/moogar0880/problems" ) func main() { notFound := problems.NewStatusProblem(404) notFound.Detail = "The item you've requested either does not exist or has been deleted." b, _ := json.MarshalIndent(notFound, "", " ") fmt.Println(string(b)) }
Output: { "type": "about:blank", "title": "Not Found", "status": 404, "detail": "The item you've requested either does not exist or has been deleted." }
func (*Problem) Error ¶ added in v1.0.0
Error implements the error interface and allows a Problem to be used as a native error.
func (*Problem) Validate ¶ added in v1.0.0
func (p *Problem) Validate() (*ValidProblem, error)
Validate validates the content of the Problem instance. If the Problem is invalid, as defined by RFC-9457, then an error explaining the validation error is returned. Otherwise, a sealed ValidProblem instance is returned.
See the documentation for ErrTitleMustBeSet and ErrInvalidProblemType for more information on the validation errors returned by this method.
func (*Problem) WithDetail ¶ added in v1.0.0
WithDetail sets the detail message to the provided string.
func (*Problem) WithDetailf ¶ added in v1.0.0
WithDetailf behaves identically to WithDetail, but allows consumers to provide a format string and arguments which will be formatted internally.
func (*Problem) WithError ¶ added in v1.0.0
WithError sets the detail message to the provided error.
func (*Problem) WithInstance ¶ added in v1.0.0
WithInstance sets the instance uri to the provided string.
func (*Problem) WithStatus ¶ added in v1.0.0
WithStatus sets the status field to the provided int.
If no title is set then this call will also set the title to the return value of http.StatusText for the provided status code.
type ValidExtendedProblem ¶ added in v1.0.0
type ValidExtendedProblem[T any] struct { ValidProblem // contains filtered or unexported fields }
A ValidExtendedProblem is a sealed variant of ExtendedProblem which is guaranteed to contain valid fields.
Instances of ValidExtendedProblem can be created by using the Validate method on the ExtendedProblem type.
func (*ValidExtendedProblem[T]) IntoExtendedProblem ¶ added in v1.0.0
func (p *ValidExtendedProblem[T]) IntoExtendedProblem() *ExtendedProblem[T]
IntoExtendedProblem allows you to convert from a ValidExtendedProblem back into an ExtendedProblem.
func (*ValidExtendedProblem[T]) IntoProblem ¶ added in v1.0.0
func (p *ValidExtendedProblem[T]) IntoProblem() *Problem
IntoProblem allows you to convert from a ValidExtendedProblem back into a Problem.
func (*ValidExtendedProblem[T]) MarshalJSON ¶ added in v1.0.0
func (p *ValidExtendedProblem[T]) MarshalJSON() ([]byte, error)
MarshalJSON implements the json.Marshaler interface and ensures that a ValidExtendedProblem is properly serialized into JSON.
type ValidProblem ¶ added in v1.0.0
type ValidProblem struct {
// contains filtered or unexported fields
}
A ValidProblem is a sealed variant of Problem which is guaranteed to have valid fields.
Instances of ValidProblem can be created by using the Validate method from the Problem type.
func (*ValidProblem) IntoProblem ¶ added in v1.0.0
func (p *ValidProblem) IntoProblem() *Problem
IntoProblem allows you to convert from a ValidProblem back into a Problem.
func (*ValidProblem) MarshalJSON ¶ added in v1.0.0
func (p *ValidProblem) MarshalJSON() ([]byte, error)
MarshalJSON implements the json.Marshaler interface and ensures that a ValidProblem is properly serialized into JSON.