Move api errors to their own package and modify the typedef
parent
f033422ffa
commit
b9f6aacb0f
@ -0,0 +1,250 @@
|
||||
package errs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// StatusCoder interface is used by errors that returns the HTTP response code.
|
||||
type StatusCoder interface {
|
||||
StatusCode() int
|
||||
}
|
||||
|
||||
// StackTracer must be by those errors that return an stack trace.
|
||||
type StackTracer interface {
|
||||
StackTrace() errors.StackTrace
|
||||
}
|
||||
|
||||
// Option modifies the Error type.
|
||||
type Option func(e *Error) error
|
||||
|
||||
// WithMessage returns an Option that modifies the error by overwriting the
|
||||
// message only if it is empty.
|
||||
func WithMessage(format string, args ...interface{}) Option {
|
||||
return func(e *Error) error {
|
||||
if len(e.Msg) > 0 {
|
||||
return e
|
||||
}
|
||||
e.Msg = fmt.Sprintf(format, args...)
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
// Error represents the CA API errors.
|
||||
type Error struct {
|
||||
Status int
|
||||
Err error
|
||||
Msg string
|
||||
}
|
||||
|
||||
// New returns a new Error. If the given error implements the StatusCoder
|
||||
// interface we will ignore the given status.
|
||||
func New(status int, err error, opts ...Option) error {
|
||||
var e *Error
|
||||
if sc, ok := err.(StatusCoder); ok {
|
||||
e = &Error{Status: sc.StatusCode(), Err: err}
|
||||
} else {
|
||||
cause := errors.Cause(err)
|
||||
if sc, ok := cause.(StatusCoder); ok {
|
||||
e = &Error{Status: sc.StatusCode(), Err: err}
|
||||
} else {
|
||||
e = &Error{Status: status, Err: err}
|
||||
}
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// ErrorResponse represents an error in JSON format.
|
||||
type ErrorResponse struct {
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Cause implements the errors.Causer interface and returns the original error.
|
||||
func (e *Error) Cause() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// Error implements the error interface and returns the error string.
|
||||
func (e *Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// StatusCode implements the StatusCoder interface and returns the HTTP response
|
||||
// code.
|
||||
func (e *Error) StatusCode() int {
|
||||
return e.Status
|
||||
}
|
||||
|
||||
// Message returns a user friendly error, if one is set.
|
||||
func (e *Error) Message() string {
|
||||
if len(e.Msg) > 0 {
|
||||
return e.Msg
|
||||
}
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace at the point Wrap is
|
||||
// called, and the supplied message. If err is nil, Wrap returns nil.
|
||||
func Wrap(status int, e error, m string, opts ...Option) error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
if err, ok := e.(*Error); ok {
|
||||
err.Err = errors.Wrap(err.Err, m)
|
||||
e = err
|
||||
} else {
|
||||
e = errors.Wrap(e, m)
|
||||
}
|
||||
return StatusCodeError(status, e, opts...)
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace at the point Wrap is
|
||||
// called, and the supplied message. If err is nil, Wrap returns nil.
|
||||
func Wrapf(status int, e error, format string, args ...interface{}) error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
var opts []Option
|
||||
for i, arg := range args {
|
||||
// Once we find the first Option, assume that all further arguments are Options.
|
||||
if _, ok := arg.(Option); ok {
|
||||
for _, a := range args[i:] {
|
||||
// Ignore any arguments after the first Option that are not Options.
|
||||
if opt, ok := a.(Option); ok {
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
}
|
||||
args = args[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if err, ok := e.(*Error); ok {
|
||||
err.Err = errors.Wrapf(err.Err, format, args...)
|
||||
e = err
|
||||
} else {
|
||||
e = errors.Wrapf(e, format, args...)
|
||||
}
|
||||
return StatusCodeError(status, e, opts...)
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaller interface for the Error struct.
|
||||
func (e *Error) MarshalJSON() ([]byte, error) {
|
||||
var msg string
|
||||
if len(e.Msg) > 0 {
|
||||
msg = e.Msg
|
||||
} else {
|
||||
msg = http.StatusText(e.Status)
|
||||
}
|
||||
return json.Marshal(&ErrorResponse{Status: e.Status, Message: msg})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface for the Error struct.
|
||||
func (e *Error) UnmarshalJSON(data []byte) error {
|
||||
var er ErrorResponse
|
||||
if err := json.Unmarshal(data, &er); err != nil {
|
||||
return err
|
||||
}
|
||||
e.Status = er.Status
|
||||
e.Err = fmt.Errorf(er.Message)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format implements the fmt.Formatter interface.
|
||||
func (e *Error) Format(f fmt.State, c rune) {
|
||||
if err, ok := e.Err.(fmt.Formatter); ok {
|
||||
err.Format(f, c)
|
||||
return
|
||||
}
|
||||
fmt.Fprint(f, e.Err.Error())
|
||||
}
|
||||
|
||||
// Messenger is a friendly message interface that errors can implement.
|
||||
type Messenger interface {
|
||||
Message() string
|
||||
}
|
||||
|
||||
// StatusCodeError selects the proper error based on the status code.
|
||||
func StatusCodeError(code int, e error, opts ...Option) error {
|
||||
switch code {
|
||||
case http.StatusBadRequest:
|
||||
return BadRequest(e, opts...)
|
||||
case http.StatusUnauthorized:
|
||||
return Unauthorized(e, opts...)
|
||||
case http.StatusForbidden:
|
||||
return Forbidden(e, opts...)
|
||||
case http.StatusInternalServerError:
|
||||
return InternalServerError(e, opts...)
|
||||
case http.StatusNotImplemented:
|
||||
return NotImplemented(e, opts...)
|
||||
default:
|
||||
return UnexpectedError(code, e, opts...)
|
||||
}
|
||||
}
|
||||
|
||||
var seeLogs = "Please see the certificate authority logs for more info."
|
||||
|
||||
// InternalServerError returns a 500 error with the given error.
|
||||
func InternalServerError(err error, opts ...Option) error {
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithMessage("The certificate authority encountered an Internal Server Error. "+seeLogs))
|
||||
}
|
||||
return New(http.StatusInternalServerError, err, opts...)
|
||||
}
|
||||
|
||||
// NotImplemented returns a 501 error with the given error.
|
||||
func NotImplemented(err error, opts ...Option) error {
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithMessage("The requested method is not implemented by the certificate authority. "+seeLogs))
|
||||
}
|
||||
return New(http.StatusNotImplemented, err, opts...)
|
||||
}
|
||||
|
||||
// BadRequest returns an 400 error with the given error.
|
||||
func BadRequest(err error, opts ...Option) error {
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithMessage("The request could not be completed due to being poorly formatted or "+
|
||||
"missing critical data. "+seeLogs))
|
||||
}
|
||||
return New(http.StatusBadRequest, err, opts...)
|
||||
}
|
||||
|
||||
// Unauthorized returns an 401 error with the given error.
|
||||
func Unauthorized(err error, opts ...Option) error {
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithMessage("The request lacked necessary authorization to be completed. "+seeLogs))
|
||||
}
|
||||
return New(http.StatusUnauthorized, err, opts...)
|
||||
}
|
||||
|
||||
// Forbidden returns an 403 error with the given error.
|
||||
func Forbidden(err error, opts ...Option) error {
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithMessage("The request was Forbidden by the certificate authority. "+seeLogs))
|
||||
}
|
||||
return New(http.StatusForbidden, err, opts...)
|
||||
}
|
||||
|
||||
// NotFound returns an 404 error with the given error.
|
||||
func NotFound(err error, opts ...Option) error {
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithMessage("The requested resource could not be found. "+seeLogs))
|
||||
}
|
||||
return New(http.StatusNotFound, err, opts...)
|
||||
}
|
||||
|
||||
// UnexpectedError will be used when the certificate authority makes an outgoing
|
||||
// request and receives an unhandled status code.
|
||||
func UnexpectedError(code int, err error, opts ...Option) error {
|
||||
if len(opts) == 0 {
|
||||
opts = append(opts, WithMessage("The certificate authority received an "+
|
||||
"unexpected HTTP status code - '%d'. "+seeLogs, code))
|
||||
}
|
||||
return New(code, err, opts...)
|
||||
}
|
Loading…
Reference in New Issue