Documentation
¶
Index ¶
- Variables
- func HandleRequest[T any](rq *http.Request, h func(ctx context.Context, c *T) any) any
- func Handler(h EndpointHandler) http.HandlerFunc
- func HandlerFunc(h EndpointFunc) http.HandlerFunc
- type EndpointFunc
- type EndpointHandler
- type Error
- func (err Error) Error() string
- 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) 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") )
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 HandleRequest ¶
HandleRequest reads the request body and unmarshals it into a value of type T which is then passed to the supplied function to handle the request. After being read, the request Body is replaced with a new ReadCloser so that it may be re-read by the handler function if required.
If the request body is empty, the handler function is called with a nil value.
If the request body is not empty but cannot be unmarshalled into a value of type T, an error is returned.
example ¶
func PostResource(rw http.ResponseWriter, rq *http.Request) any {
type resource struct {
id string `json:"id"`
name string `json:"name"`
}
return restapi.HandleRequest(rq, func(r *resource) any {
if r == nil {
return restapi.BadRequest(restapi.ErrBodyRequired)
}
r.id = uuid.New().String()
// ... create a new resource with the resuiqred name ...
return restapi.Created().WithValue(r)
})
}
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 ServeHTTP method that accepts http.ResponseWriter and *http.Request arguments and returns a value of type 'any'.
func HandlerFunc ¶
func HandlerFunc(h EndpointFunc) http.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 ¶
type EndpointFunc func(_ http.ResponseWriter, rq *http.Request) any
EndpointFunc is a function that accepts http.ResponseWriter and *http.Request arguments and returns a value of type 'any'.
It is the signature of functions that implement REST API endpoints.
type EndpointHandler ¶
type EndpointHandler interface {
ServeHTTP(http.ResponseWriter, *http.Request) any
}
EndpointHandler is an interface that defines a ServeHTTP method that accepts http.ResponseWriter and *http.Request arguments and returns a value of type 'any'.
This interface must be implemented by types that handle REST API endpoint requests.
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) 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
}
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 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 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) 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.