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
}