hproblem

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2023 License: ISC Imports: 9 Imported by: 0

README

hproblem - Error responses for HTTP APIs in Go

GoDoc Go Report Card Coverage Status

Overview

Package hproblem provides a standard interface for handling API error responses in web applications. It implements RFC 7807 (Problem Details for HTTP APIs) which specifies a way to carry machine-readable details of errors in a HTTP response to avoid the need to define new error response formats for HTTP APIs.

Install

go get -u github.com/askeladdk/hproblem

Quickstart

The two basic functions are Wrap and ServeError. Wrap associates an error with a status code. ServeError replies to a request by marshaling the error to JSON, XML or plain text depending on the request's Accept header. Use it instead of http.Error. ServeError also accepts errors that implement the http.Handler interface, in which case the error is in charge of marshaling itself.

func endpoint(w http.ResponseWriter, r *http.Request) {
    hproblem.ServeError(w, r, hproblem.Wrap(http.StatusBadRequest, io.EOF))
}

Use Errorf as a shorthand for Wrap(statusCode, fmt.Errorf(...)).

err = hproblem.Errorf(http.StatusBadRequest, "package: error: %w", err)

Use the DetailsError type directly if you need more control.

var err error = &hproblem.DetailsError{
    Detail: "This is not the Jedi that you are looking for",
    Instance: "/jedi/obi-wan",
    Status: http.StatusNotFound,
    Title: "Jedi Mind Trick",
}

Embed DetailsError inside another type to add custom fields and use NewDetailsError to initialize it.

type TraceError struct {
    *hproblem.DetailsError
    TraceID string `json:"trace_id" xml:"trace_id"`
}

var err error = &TraceError{
    DetailsError: hproblem.NewDetailsError(hproblem.Wrap(http.StatusBadRequest, io.EOF)),
    TraceID: "42",
}

Use the predefined Status* errors to serve HTTP status codes without needing to wrap. This is convenient in cases where it is not needed to attach extra information to an error. Every status code present in the http package has an equivalent error in hproblem. Handlers MethodNotFound and NotFound are also provided.

hproblem.ServeError(w, r, hproblem.StatusForbidden)

Read the rest of the documentation on pkg.go.dev. It's easy-peasy!

License

Package hproblem is released under the terms of the ISC license.

Documentation

Overview

Package hproblem provides a standard interface for handling API error responses in web applications. It implements RFC 7807 (Problem Details for HTTP APIs) which specifies a way to carry machine-readable details of errors in a HTTP response to avoid the need to define new error response formats for HTTP APIs.

Index

Examples

Constants

View Source
const (
	StatusContinue                      statusError = 100 // RFC 9110, 15.2.1
	StatusSwitchingProtocols            statusError = 101 // RFC 9110, 15.2.2
	StatusProcessing                    statusError = 102 // RFC 2518, 10.1
	StatusEarlyHints                    statusError = 103 // RFC 8297
	StatusOK                            statusError = 200 // RFC 9110, 15.3.1
	StatusCreated                       statusError = 201 // RFC 9110, 15.3.2
	StatusAccepted                      statusError = 202 // RFC 9110, 15.3.3
	StatusNonAuthoritativeInfo          statusError = 203 // RFC 9110, 15.3.4
	StatusNoContent                     statusError = 204 // RFC 9110, 15.3.5
	StatusResetContent                  statusError = 205 // RFC 9110, 15.3.6
	StatusPartialContent                statusError = 206 // RFC 9110, 15.3.7
	StatusMultiStatus                   statusError = 207 // RFC 4918, 11.1
	StatusAlreadyReported               statusError = 208 // RFC 5842, 7.1
	StatusIMUsed                        statusError = 226 // RFC 3229, 10.4.1
	StatusMultipleChoices               statusError = 300 // RFC 9110, 15.4.1
	StatusMovedPermanently              statusError = 301 // RFC 9110, 15.4.2
	StatusFound                         statusError = 302 // RFC 9110, 15.4.3
	StatusSeeOther                      statusError = 303 // RFC 9110, 15.4.4
	StatusNotModified                   statusError = 304 // RFC 9110, 15.4.5
	StatusUseProxy                      statusError = 305 // RFC 9110, 15.4.6
	StatusTemporaryRedirect             statusError = 307 // RFC 9110, 15.4.8
	StatusPermanentRedirect             statusError = 308 // RFC 9110, 15.4.9
	StatusBadRequest                    statusError = 400 // RFC 9110, 15.5.1
	StatusUnauthorized                  statusError = 401 // RFC 9110, 15.5.2
	StatusPaymentRequired               statusError = 402 // RFC 9110, 15.5.3
	StatusForbidden                     statusError = 403 // RFC 9110, 15.5.4
	StatusNotFound                      statusError = 404 // RFC 9110, 15.5.5
	StatusMethodNotAllowed              statusError = 405 // RFC 9110, 15.5.6
	StatusNotAcceptable                 statusError = 406 // RFC 9110, 15.5.7
	StatusProxyAuthRequired             statusError = 407 // RFC 9110, 15.5.8
	StatusRequestTimeout                statusError = 408 // RFC 9110, 15.5.9
	StatusConflict                      statusError = 409 // RFC 9110, 15.5.10
	StatusGone                          statusError = 410 // RFC 9110, 15.5.11
	StatusLengthRequired                statusError = 411 // RFC 9110, 15.5.12
	StatusPreconditionFailed            statusError = 412 // RFC 9110, 15.5.13
	StatusRequestEntityTooLarge         statusError = 413 // RFC 9110, 15.5.14
	StatusRequestURITooLong             statusError = 414 // RFC 9110, 15.5.15
	StatusUnsupportedMediaType          statusError = 415 // RFC 9110, 15.5.16
	StatusRequestedRangeNotSatisfiable  statusError = 416 // RFC 9110, 15.5.17
	StatusExpectationFailed             statusError = 417 // RFC 9110, 15.5.18
	StatusTeapot                        statusError = 418 // RFC 9110, 15.5.19 (Unused)
	StatusMisdirectedRequest            statusError = 421 // RFC 9110, 15.5.20
	StatusUnprocessableEntity           statusError = 422 // RFC 9110, 15.5.21
	StatusLocked                        statusError = 423 // RFC 4918, 11.3
	StatusFailedDependency              statusError = 424 // RFC 4918, 11.4
	StatusTooEarly                      statusError = 425 // RFC 8470, 5.2.
	StatusUpgradeRequired               statusError = 426 // RFC 9110, 15.5.22
	StatusPreconditionRequired          statusError = 428 // RFC 6585, 3
	StatusTooManyRequests               statusError = 429 // RFC 6585, 4
	StatusRequestHeaderFieldsTooLarge   statusError = 431 // RFC 6585, 5
	StatusUnavailableForLegalReasons    statusError = 451 // RFC 7725, 3
	StatusInternalServerError           statusError = 500 // RFC 9110, 15.6.1
	StatusNotImplemented                statusError = 501 // RFC 9110, 15.6.2
	StatusBadGateway                    statusError = 502 // RFC 9110, 15.6.3
	StatusServiceUnavailable            statusError = 503 // RFC 9110, 15.6.4
	StatusGatewayTimeout                statusError = 504 // RFC 9110, 15.6.5
	StatusHTTPVersionNotSupported       statusError = 505 // RFC 9110, 15.6.6
	StatusVariantAlsoNegotiates         statusError = 506 // RFC 2295, 8.1
	StatusInsufficientStorage           statusError = 507 // RFC 4918, 11.5
	StatusLoopDetected                  statusError = 508 // RFC 5842, 7.2
	StatusNotExtended                   statusError = 510 // RFC 2774, 7
	StatusNetworkAuthenticationRequired statusError = 511 // RFC 6585, 6
)

HTTP status codes as registered with IANA. See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml

Variables

View Source
var ErrInvalidEncoding = errors.New("hproblem: invalid details error encoding")

Functions

func Errorf

func Errorf(statusCode int, format string, a ...interface{}) error

Errorf is a shorthand for Wrap(fmt.Errorf(...), statusCode).

func MethodNotAllowed

func MethodNotAllowed(w http.ResponseWriter, r *http.Request)

MethodNotAllowed replies to the request with StatusMethodNotAllowed.

func NotFound

func NotFound(w http.ResponseWriter, r *http.Request)

NotFound replies to the request with StatusNotFound.

func ServeError

func ServeError(w http.ResponseWriter, r *http.Request, err error)

ServeError replies to the request by rendering err. If err implements http.Handler, its ServeHTTP method is called. Otherwise, err is rendered as JSON, XML or plain text depending on the request's Accept header. If err is nil, it will be rendered as StatusOK.

Example (Json)
package main

import (
	"fmt"
	"io"
	"net/http/httptest"
	"os"

	"github.com/askeladdk/hproblem"
)

func main() {
	w := httptest.NewRecorder()
	r := httptest.NewRequest("GET", "/", nil)
	r.Header.Set("Accept", "application/json")

	hproblem.ServeError(w, r, hproblem.StatusBadRequest)

	fmt.Println(w.Result().Status)
	_, _ = io.Copy(os.Stdout, w.Body)

}
Output:

400 Bad Request
{"detail":"Bad Request","status":400,"title":"Bad Request"}
Example (Text)
package main

import (
	"fmt"
	"io"
	"net/http/httptest"
	"os"

	"github.com/askeladdk/hproblem"
)

func main() {
	w := httptest.NewRecorder()
	r := httptest.NewRequest("GET", "/", nil)

	hproblem.ServeError(w, r, hproblem.StatusBadRequest)

	fmt.Println(w.Result().Status)
	_, _ = io.Copy(os.Stdout, w.Body)

}
Output:

400 Bad Request
Bad Request
Example (Xml)
package main

import (
	"fmt"
	"io"
	"net/http/httptest"
	"os"

	"github.com/askeladdk/hproblem"
)

func main() {
	w := httptest.NewRecorder()
	r := httptest.NewRequest("GET", "/", nil)
	r.Header.Set("Accept", "text/xml")

	hproblem.ServeError(w, r, hproblem.StatusBadRequest)

	fmt.Println(w.Result().Status)
	_, _ = io.Copy(os.Stdout, w.Body)

}
Output:

400 Bad Request
<?xml version="1.0" encoding="UTF-8"?>
<problem xmlns="urn:ietf:rfc:7807"><detail>Bad Request</detail><status>400</status><title>Bad Request</title></problem>

func StatusCode

func StatusCode(err error) int

StatusCode reports the HTTP status code associated with err if it implements the StatusCode() int method, 504 Gateway Timeout if it implements Timeout() bool, 503 Service Unavailable if it implements Temporary() bool, 500 Internal Server Error otherwise, or 200 OK if err is nil. StatusCode will unwrap err to find the most precise status code.

Example
package main

import (
	"fmt"
	"io"
	"net/http"

	"github.com/askeladdk/hproblem"
)

func main() {
	fmt.Println(hproblem.StatusCode(nil))
	fmt.Println(hproblem.StatusCode(io.EOF))
	fmt.Println(hproblem.StatusCode(hproblem.Wrap(http.StatusBadRequest, io.EOF)))
}
Output:

200
500
400

func Wrap

func Wrap(statusCode int, err error) error

Wrap associates an error with a status code.

Types

type DetailsError

type DetailsError struct {
	// A human-readable explanation specific to this occurrence of the problem.
	Detail string `json:"detail,omitempty" xml:"detail,omitempty"`

	// A URI reference that identifies the specific occurrence of the problem.
	// It may or may not yield further information if dereferenced.
	Instance string `json:"instance,omitempty" xml:"instance,omitempty"`

	// The HTTP status code ([RFC7231], Section 6)
	// generated by the origin server for this occurrence of the problem.
	Status int `json:"status,omitempty" xml:"status,omitempty"`

	// A short, human-readable summary of the problem
	// type. It SHOULD NOT change from occurrence to occurrence of the
	// problem, except for purposes of localization (e.g., using
	// proactive content negotiation; see [RFC7231], Section 3.4).
	Title string `json:"title,omitempty" xml:"title,omitempty"`

	// A URI reference [RFC3986] that identifies the
	// problem type. This specification encourages that, when
	// dereferenced, it provide human-readable documentation for the
	// problem type (e.g., using HTML [W3C.REC-html5-20141028]). When
	// this member is not present, its value is assumed to be
	// "about:blank".
	Type string `json:"type,omitempty" xml:"type,omitempty"`

	// XMLName is needed to marshal to XML.
	XMLName xml.Name `json:"-" xml:"urn:ietf:rfc:7807 problem"`
	// contains filtered or unexported fields
}

DetailsError implements the RFC 7807 model. See: https://datatracker.ietf.org/doc/html/rfc7807

Additional fields can be added by embedding it inside another struct.

type TraceDetailsError struct {
    *hproblem.DetailsError
    TraceID string `json:"trace_id" xml:"trace_id"`
}

hproblem.ServeError(w, r, TraceDetailsError{})

func NewDetailsError

func NewDetailsError(err error) *DetailsError

NewDetailsError returns a new DetailsError with the Detail, Status and Title fields set according to err.

func (*DetailsError) Error

func (details *DetailsError) Error() string

Error implements the error interface and returns the Detail field.

func (*DetailsError) StatusCode

func (details *DetailsError) StatusCode() int

StatusCode implements the interface used by StatusCode and returns the Status field.

func (*DetailsError) Unmarshal added in v0.0.2

func (details *DetailsError) Unmarshal(data []byte) error

Unmarshal parses a JSON or XML encoded details error. Returns ErrInvalidEncoding if the encoding is invalid.

func (*DetailsError) Unwrap

func (details *DetailsError) Unwrap() error

Unwrap implements the interface used by errors.Unwrap() and returns the wrapped error.

Jump to

Keyboard shortcuts

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