package rest import ( "net/http" ) // REST API error. var ( Err = Error{} ErrMovedPermanently = NewError(http.StatusMovedPermanently, "") // 301 ErrFound = NewError(http.StatusFound, "") // 302 ErrTemporaryRedirect = NewError(http.StatusTemporaryRedirect, "") // 307 ErrPermanentRedirect = NewError(http.StatusPermanentRedirect, "") // 308 ErrBadRequest = NewError(http.StatusBadRequest, "") // 400 ErrUnauthorized = NewError(http.StatusUnauthorized, "") // 401 ErrPaymentRequired = NewError(http.StatusPaymentRequired, "") // 402 ErrForbidden = NewError(http.StatusForbidden, "") // 403 ErrNotFound = NewError(http.StatusNotFound, "") // 404 ErrMethodNotAllowed = NewError(http.StatusMethodNotAllowed, "") // 405 ErrNotAcceptable = NewError(http.StatusNotAcceptable, "") // 406 ErrInternalServerError = NewError(http.StatusInternalServerError, "") // 500 ErrNotImplemented = NewError(http.StatusNotImplemented, "") // 501 ErrBadGateway = NewError(http.StatusBadGateway, "") // 502 ErrServiceUnavailable = NewError(http.StatusServiceUnavailable, "") // 503 ErrGatewayTimeout = NewError(http.StatusGatewayTimeout, "") // 504 ) // Error represents a REST API error. // It can be marshaled to JSON with ease and provides a standard format for printing errors and additional data. type Error struct { StatusCode int `json:"-"` // HTTP status code (200, 404, 500 etc.) Message string `json:"message"` // Status message ("OK", "Not found", "Internal server error" etc.) Data map[string]interface{} `json:"data,omitempty"` // Optional additional data. } // Error retrieves the message of a REST API error. func (e Error) Error() string { return e.Message } // Is determines whether the Error is an instance of the target. // https://pkg.go.dev/errors#Is // // If the target is a REST API error and specifies a status code, this function returns true if the status codes match. // If the target is an empty REST API error, this function always returns true. func (e Error) Is(target error) bool { if t, ok := target.(Error); ok { return t.StatusCode == e.StatusCode || t.StatusCode == 0 } return false } // WithData returns a copy of the HTTP error with the given data merged in. func (e Error) WithData(data map[string]interface{}) Error { if e.Data == nil { e.Data = map[string]any{} } for key, value := range data { e.Data[key] = value } return e } // WithError returns a copy of the HTTP error with the given error added either as the Message, if it empty, or as additional data. func (e Error) WithError(err error) Error { if e.Message == "" { return e.WithMessage(err.Error()) } return e.WithData(map[string]interface{}{ "error": err.Error(), }) } // WithMessage returns a copy of the HTTP error with the given message. func (e Error) WithMessage(message string) Error { e.Message = message return e } // WithValue returns a copy of the HTTP error with a single data value added. func (e Error) WithValue(name string, value any) Error { return e.WithData(map[string]any{ name: value, }) } // Write writes the HTTP error to an HTTP response as plain text. // Additional data is omitted. func (e Error) Write(w http.ResponseWriter) (int, error) { if e.StatusCode == 0 { e.StatusCode = 200 } w.WriteHeader(e.StatusCode) return w.Write([]byte(e.Message)) } // WriteJSON writes the HTTP error to an HTTP response as JSON. func (e Error) WriteJSON(w http.ResponseWriter) error { if e.StatusCode == 0 { e.StatusCode = 200 } return WriteResponseJSON(w, e.StatusCode, e) } // NewError creates a new REST API error. // If the message is empty, the standard text provided by http.StatusText is substituted. func NewError(statusCode int, message string) Error { if len(message) == 0 { message = http.StatusText(statusCode) } return Error{ StatusCode: statusCode, Message: message, } }