Documentation ¶
Index ¶
- Variables
- func Handler(h EndpointHandler) http.HandlerFunc
- func HandlerFunc(h func(context.Context, *http.Request) any) http.HandlerFunc
- type EndpointFunc
- type EndpointHandler
- type Error
- func (err Error) Error() string
- func (err Error) Is(target error) bool
- func (h Error) String() string
- func (apierr Error) Unwrap() error
- func (err *Error) WithHeader(k string, v any) *Error
- func (err *Error) WithHeaders(headers map[string]any) *Error
- func (err *Error) WithHelp(s string) *Error
- func (err *Error) WithMessage(s string) *Error
- func (err *Error) WithNonCanonicalHeader(k string, v any) *Error
- func (err *Error) WithProperty(key string, value any) *Error
- type ErrorInfo
- type InternalError
- type Problem
- type Request
- type Response
- type Result
- func (r *Result) Content() any
- func (r *Result) ContentType() string
- func (r *Result) Headers() headers
- func (r *Result) StatusCode() int
- func (r Result) String() string
- func (r *Result) WithContent(contentType string, content []byte) *Result
- func (r *Result) WithHeader(k string, v any) *Result
- func (r *Result) WithHeaders(headers map[string]any) *Result
- func (r *Result) WithNonCanonicalHeader(k string, v any) *Result
- func (r *Result) WithValue(value any) *Result
Constants ¶
This section is empty.
Variables ¶
var ( ErrBodyRequired = errors.New("a body is required") ErrErrorReadingRequestBody = errors.New("error reading request body") ErrInvalidAcceptHeader = errors.New("no formatter for content type") ErrInvalidArgument = errors.New("invalid argument") ErrInvalidOperation = errors.New("invalid operation") ErrInvalidStatusCode = errors.New("invalid statuscode") ErrMarshalErrorFailed = errors.New("error marshalling an Error response") ErrMarshalResultFailed = errors.New("error marshalling response") ErrNoAcceptHeader = errors.New("no Accept header") ErrUnexpectedField = errors.New("unexpected field") )
var LogError = func(InternalError) {}
LogError is called when an error is returned from a restapi.Handler or if an error occurs in an aspect of the restapi implementation itself.
LogError is a function variable with an initial NO-OP implementation, i.e. no log is emitted. Applications should replace the implementation with one that produces an appropriate log using the logger configured in their application.
var ProjectError = func(err ErrorInfo) any { pe := errorResponse{ XMLName: xml.Name{Local: "error"}, Status: err.StatusCode, Error: http.StatusText(err.StatusCode), Message: err.Message, Path: err.Request.URL.Path, Query: err.Request.URL.RawQuery, Timestamp: err.TimeStamp, Help: err.Help, } switch { case pe.Message == "" && err.Err != nil: pe.Message = err.Err.Error() case pe.Message != "" && err.Err != nil: pe.Message = err.Err.Error() + ": " + pe.Message } if len(err.Properties) > 0 { pe.Additional = maps.Clone(err.Properties) } return pe }
ProjectError is called when writing an error response to obtain a representation of a REST API Error (the 'projection') to be used as the response body. The function is a variable with a default implementation returning a struct with tags supporting both JSON and XML marshalling:
type struct { XMLName xml.Name `json:"-"` // omit from JSON; set to "error" in XML Status int `json:"status" xml:"status"` Error string `json:"error" xml:"error"` Message string `json:"message,omitempty" xml:"message,omitempty"` Path string `json:"path" xml:"path"` Query string `json:"query" xml:"query"` Timestamp time.Time `json:"timestamp" xml:"timestamp"` Help string `json:"help,omitempty" xml:"help,omitempty"` Additional map[string]any `json:"additional,omitempty" xml:"additional,omitempty"` }
Applications may customise the body of error responses by replacing the implementation of this function and returning a custom struct or other type with marshalling support appropriate to the needs of the application.
Functions ¶
func Handler ¶
func Handler(h EndpointHandler) http.HandlerFunc
Handler returns a http.HandlerFunc that calls a restapi.EndpointHandler.
A restapi.EndpointHandler is an interface that defines a ServeAPI method that accepts a context.Context and a *http.Request argument, returning a value of type 'any'.
func HandlerFunc ¶
HandlerFunc returns a http.HandlerFunc that calls a REST API endpoint function.
The endpoint function differs from a http handler function in that in addition to accepting http.ResponseWriter and *http.Request arguments, it also returns a value of type 'any'.
The returned value is processed by the Handler function to generate an appropriate response.
Types ¶
type EndpointFunc ¶
EndpointFunc is a type for a function that conforms to the EndpointHandler ServeAPI() method. A function with the appropriate signature may be converted to an EndpointHandler by casting to this type.
example ¶
func MyEndpoint(ctx context.Context, rq *http.Request) any { // do something return result } var MyHandler = EndpointFunc(MyEndpoint) func main() { http.HandleFunc("/my-endpoint", restapi.Handler(MyHandler)) http.ListenAndServe(":8080", nil) }
type EndpointHandler ¶
EndpointHandler is an interface that defines a ServeAPI method that conforms to the EndpointFunc signature, accepting a context.Context and *http.Request arguments, returning a value of type 'any'.
type Error ¶
type Error struct {
// contains filtered or unexported fields
}
Error holds details of a REST API error.
The Error type is exported but has no exported members; an endpoint function will usually obtain an Error value using an appropriate factory function, using the exported methods to provide information about the error.
examples ¶
// an unexpected error occurred if err := SomeOperation(); err != nil { return restapi.InternalServerError(fmt.Errorf("SomeOperation: %w", err)) } // an error occurred due to invalid input; provide guidance to the user if err := GetIDFromRequest(rq, &d); err != nil { return restapi.BadRequest(). WithMessage("ID is missing or invalid"). WithHelp("The ID must be a valid UUID provided in the request path: /v1/resource/<ID>") URL // the URL of the request that resulted in the error TimeStamp // the (UTC) time that the error occurred
The following additional information may also be provided by a Handler when returning an Error:
Message // a message to be displayed with the error. If not provided, // the message will be the string representation of the error (Err). // // NOTE: if Message is set, the Err string will NOT be used Help // a help message to be displayed with the error. If not provided, // the help message will be omitted from the response.
func BadRequest ¶
BadRequest returns an ApiError with a status code of 400 and the specified error.
func InternalServerError ¶
InternalServerError returns an ApiError with a status code of 500 and the specified error.
func NewError ¶
NewError returns an Error with the specified status code. One or more additional arguments may be provided to be used as follows://
int // the status code for the error error // an error to be wrapped by the Error string // a message to be displayed with (or instead of) an error
If no status code is provided http.StatusInternalServerError will be used. If multiple int arguments are provided only the first will be used; any subsequent int arguments will be ignored.
If multiple error arguments are provided they will be wrapped as a single error using errors.Join.
If multiple string arguments are provided, the first non-empty string will be used as the message; any remaining strings will be ignored.
The returned Error will have a timestamp set to the current time in UTC.
panics ¶
NewError will panic with the following errors:
- ErrInvalidArgument if arguments of an unsupported type are provided.
- ErrInvalidStatusCode if a status code is provided that is not in the range 4xx-5xx.
examples ¶
// no error occurred, but the operation was not successful return NewError(http.StatusNotFound, "no document exists with that ID") // an error occurred, but the error is not relevant to the user id, err := GetRequestID(rq) if err != nil { return NewError(http.BadRequest, "required document ID is missing or invalid", err) }
func Unauthorized ¶
Unauthorized returns an ApiError with a status code of 401 and the specified error.
func (Error) Error ¶
Error implements the error interface for an Error, returning a simplified string representation of the error in the form:
<status code> <status>[: error][: message]
where <status> is the http status text associated with <status code>; <error> and <message> are only included if they are set on the Error.
func (Error) Is ¶ added in v0.2.0
Is returns true if the target error is an Error and the Error matches the target error. An Error matches the target error if:
- the status codes match or the target has no status code (matches any);
- the messages match or the target has no message (matches any);
- the help messages match or the target has no help message (matches any);
- the properties match or the target has no properties (matches any);
- the wrapped target error satisfies errors.Is() or the target has no wrapped error (matches any).
func (*Error) WithHeader ¶
WithHeader sets a header to be included in the response for the error.
The specified header will be added to any headers already set on the Error. If the specified header is already set on the Error the existing header will be replaced with the new value.
The header key is canonicalised using http.CanonicalHeaderKey. To set a header with a non-canonical key use WithNonCanonicalHeader.
func (*Error) WithHeaders ¶
WithHeaders sets the headers to be included in the response for the error.
The specified headers will be added to any headers already set on the Error. If the new headers contain values already set on the Error the existing headers will be replaced with the new values.
The header keys are canonicalised using http.CanonicalHeaderKey. To set a header with a non-canonical key use WithNonCanonicalHeader.
func (*Error) WithMessage ¶
WithMessage sets the message for the error.
func (*Error) WithNonCanonicalHeader ¶
WithNonCanonicalHeader sets a non-canonical header to be included in the response for the error.
The specified header will be added to any headers already set on the Error. If the specified header is already set on the Error the existing header will be replaced with the new value.
The header key is not canonicalised; if the specified key is canonical then the canonical header will be set.
WithNonCanonicalHeader should only be used when a non-canonical header key is specifically required (which is rare). Ordinarily WithHeader should be used.
type ErrorInfo ¶
type ErrorInfo struct { StatusCode int Err error Help string Message string Request *http.Request Properties map[string]any TimeStamp time.Time }
ErrorInfo represents an error that occurred during the processing of a request.
Although it is exported this type should not be used directly by REST API implementations, except when providing an implementation for the restapi.LogError or restapi.ProjectError functions. These functions receive a copy of the Error to be logged or projected in the form of an ErrorInfo.
type InternalError ¶
type InternalError struct { Err error Help string Message string Request *http.Request ContentType string }
InternalError represents an error that occurred during the processing of a request.
Although it is exported this type should not be used directly by REST API implementations, except when providing an implementation for the restapi.LogError or restapi.ProjectError functions. These functions receive a copy of the Error to be logged or projected in the form of an ErrorInfo.
type Problem ¶
type Problem struct { Type *url.URL Status int Instance *url.URL Detail string Title string // contains filtered or unexported fields }
Implements an RFC7807 Problem Details response https://www.rfc-editor.org/rfc/rfc7807
func NewProblem ¶
NewProblem returns a Problem with the specified arguments. Arguments are processed in order and can be of the following types:
int // the HTTP status code; will replace any existing Status; // if not specified, defaults to http.StatusInternalServerError url.URL // the problem type *url.URL string // the problem detail; will replace any existing detail error // will apply a status code of http.StatusInternalServerError and set the // detail to the error message; if the StatusCode or Detail are already // set, they will NOT be overwritten map[string]any // additional properties to be included in the response. If multiple // property maps are specified they will be merged; keys from earlier // arguments will be overwritten by any values for the same key in later // ones
An argument of any other type will cause a panic with ErrInvalidArgument.
If multiple arguments of any of the supported types are specified earlier values in the argument list will be applied and over-written by later values (except as noted above).
examples ¶
// multiple status codes specified: only the last one is applied NewProblem(http.StatusNotFound, "resource not found", http.BadRequest)
results in a Problem with a StatusCode of 400 (Bad Request) and a Detail of "resource not found"
// status code with multiple errors specified NewProblem(http.StatusBadRequest, errors.New("some error"), errors.New("another error"))
results in a Problem with a StatusCode of 400 (BadRequest) and a Detail of "some error" (the second error is ignored)
note ¶
Some combinations of arguments may result one or more arguments being ignored. For example, specifying a StatusCode, Detail (string) and an error will result in the error being ignored.
func (*Problem) WithDetail ¶
WithDetail sets the Detail property of the Problem instance.
The Detail property must provide a human-readable explanation specific to this occurrence of the problem.
func (*Problem) WithInstance ¶
WithInstance sets the instance property of the Problem instance.
The instance property is a URI that identifies the specific occurrence of the problem.
type Response ¶
type Result ¶
type Result struct {
// contains filtered or unexported fields
}
Result holds details of a valid REST API result.
The Result struct is exported but does not export any members; exported methods are provided for endpoint functions to work with a Result when required.
An endpoint function will initialise a Result using one of the provided functions (e.g. OK, Created, etc.) and then set the content and content type and any additional headers if/as required:
examples ¶
// return a 200 OK response with a marshalled struct body s := resource{ID: "123", Name: "example"} r := restapi.OK(). WithValue(s) // return a 200 OK response with a plain text body // (ignores/overrides any request Accept header) r := restapi.OK(). WithContent("plain/text", []byte("example"))
methods ¶
func NotImplemented ¶ added in v0.2.0
func NotImplemented() *Result
NotImplemented returns a Result with http.StatusNotImplemented
Strictly speaking this is an Error response (in the 5xx range) but is provided as a Result as it is a common placeholder response for yet-to-be implemented endpoints. Responses of this nature do not require the capabilities of an Error, such as wrapping some runtime error or providing additional Help etc.
func Status ¶
Status returns a Result with the specified status code. The status code must be in the range 1xx-5xx; any other status code will cause a panic.
NOTE: ¶
this is a more strict enforcement of standard HTTP response codes than is applied by WriteHeader itself which, as of May 2024, accepts codes 1xx-9xx.
func (*Result) ContentType ¶ added in v0.3.0
ContentType returns the content type set on the Result.
func (*Result) Headers ¶ added in v0.3.0
func (r *Result) Headers() headers
Headers returns a copy of the headers set on the Result.
func (*Result) StatusCode ¶ added in v0.3.0
StatusCode returns the status code set on the Result.
func (*Result) WithContent ¶
WithContent sets the content and content type of the Result. The specified content and content type will replace any content or content type that may have been set on the Result previously.
func (*Result) WithHeader ¶
WithHeader sets a canonical header on the Result.
The specified header will be added to any headers already set on the Result. If the specified header is already set on the Result the existing header will be replaced with the new value.
The header key is canonicalised using http.CanonicalHeaderKey. To set a header with a non-canonical key use WithNonCanonicalHeader.
func (*Result) WithHeaders ¶
WithHeaders sets the headers of the Result.
The specified headers will be added to any headers already set on the Result. If the new headers contain values already set on the Result the existing headers will be replaced with the new values.
The header keys are canonicalised using http.CanonicalHeaderKey. To set a header with a non-canonical key use WithNonCanonicalHeader.
func (*Result) WithNonCanonicalHeader ¶
WithNonCanonicalHeader sets a non-canonical header on the Result.
The specified header will be added to any headers already set on the Result. If the specified header is already set on the Result the existing header will be replaced with the new value.
The header key is not canonicalised; if the specified key is canonical then the canonical header will be set.
WithNonCanonicalHeader should only be used when a non-canonical header key is specifically required (which is rare). Ordinarily WithHeader should be used.
func (*Result) WithValue ¶
WithValue sets the content of the Result to a value that will be marshalled in the response to the content type indicated in the request Accept header (or restapi.Default.ResponseContentType).
The specified value will replace any content and content type that may have been set on the Result previously.