package rest import ( "net/http" "fmt" ) // InternalError defines an error not meant to be exposed to the public. const ( InternalErrorCode = -1 InternalErrorStatus = "internal_error" ) // Enumeration of generic errors var ( ErrNotFound = E(http.StatusNotFound, "not_found")(nil) ErrUnsupportedMediaType = E(http.StatusUnsupportedMediaType, "unsupported_media_type")(nil) ErrInternalServerError = E(http.StatusInternalServerError, "internal_server_error") ErrMalformedPayload = E(http.StatusBadRequest, "malformed_payload") ) // E is a convenience functions to construct a REST error which takes an error. func E(code int, status string) func(err error) Error { return func(err error) Error { return NewError(code, status, err) } } // NewError returns an error meant to rendered as a REST response with a given // HTTP status code and JSON body. func NewError(code int, status string, err error, opts ...Option) Error { var msg string if err != nil { msg = err.Error() } e := Error{ Code: code, Status: status, Message: msg, Errors: UnwrapError(err), } for _, o := range opts { o(&e) } // On internal server errors, we don't want to expose the internals via the // `Errors` field. if status == "" { e.Code = http.StatusInternalServerError e.Message = http.StatusText(http.StatusInternalServerError) } return e } // Error defines a failed request's response result. // // It vaguely follows: [Google Cloud APIs Errors](https://cloud.google.com/apis/design/errors) type Error struct { Code int `json:"code,omitempty"` // HTTP status code mapping, optional for nestings Status string `json:"status"` // More granular code, snake-cases, unique per service Message string `json:"message,omitempty"` // Human readable Meta interface{} `json:"meta,omitempty"` // Meta information Errors []Error `json:"errors,omitempty"` // Recursive } // Error implements the error interface. func (e Error) Error() string { return fmt.Sprintf("%d %s: %s: %v", e.Code, e.Status, e.Message, e.Errors) } // Option defines an option to a new error. type Option func(*Error) // WithMeta attaches meta information to an error. func WithMeta(meta interface{}) func(*Error) { return func(e *Error) { e.Meta = meta } } // WithErrors attaches a sequence of errors as children to a parent error. func WithErrors(errs []Error) func(*Error) { return func(e *Error) { e.Errors = append(e.Errors, errs...) } } // UnwrapError unwraps wrapped errors until either nil or an unwrappable error // is found. // // Modified version of https://github.com/pkg/errors/blob/master/errors.go#L256 func UnwrapError(err error) []Error { type causer interface { Cause() error } errs := []Error{} if cause, ok := err.(causer); ok { err = cause.Cause() } else { err = nil } for err != nil { if err, ok := err.(Error); ok { errs = append(errs, NewError(err.Code, err.Status, err)) } else { errs = append(errs, NewError(InternalErrorCode, InternalErrorStatus, err)) } cause, ok := err.(causer) if !ok { break } err = cause.Cause() } return errs }