1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
|
package admin
import (
"encoding/json"
"fmt"
"net/http"
"github.com/pkg/errors"
"github.com/smallstep/certificates/api/render"
)
// ProblemType is the type of the Admin problem.
type ProblemType int
const (
// ErrorNotFoundType resource not found.
ErrorNotFoundType ProblemType = iota
// ErrorAuthorityMismatchType resource Authority ID does not match the
// context Authority ID.
ErrorAuthorityMismatchType
// ErrorDeletedType resource has been deleted.
ErrorDeletedType
// ErrorBadRequestType bad request.
ErrorBadRequestType
// ErrorNotImplementedType not implemented.
ErrorNotImplementedType
// ErrorUnauthorizedType unauthorized.
ErrorUnauthorizedType
// ErrorServerInternalType internal server error.
ErrorServerInternalType
// ErrorConflictType conflict.
ErrorConflictType
)
// String returns the string representation of the admin problem type,
// fulfilling the Stringer interface.
func (ap ProblemType) String() string {
switch ap {
case ErrorNotFoundType:
return "notFound"
case ErrorAuthorityMismatchType:
return "authorityMismatch"
case ErrorDeletedType:
return "deleted"
case ErrorBadRequestType:
return "badRequest"
case ErrorNotImplementedType:
return "notImplemented"
case ErrorUnauthorizedType:
return "unauthorized"
case ErrorServerInternalType:
return "internalServerError"
case ErrorConflictType:
return "conflict"
default:
return fmt.Sprintf("unsupported error type '%d'", int(ap))
}
}
type errorMetadata struct {
details string
status int
typ string
String string
}
var (
errorServerInternalMetadata = errorMetadata{
typ: ErrorServerInternalType.String(),
details: "the server experienced an internal error",
status: http.StatusInternalServerError,
}
errorMap = map[ProblemType]errorMetadata{
ErrorNotFoundType: {
typ: ErrorNotFoundType.String(),
details: "resource not found",
status: http.StatusNotFound,
},
ErrorAuthorityMismatchType: {
typ: ErrorAuthorityMismatchType.String(),
details: "resource not owned by authority",
status: http.StatusUnauthorized,
},
ErrorDeletedType: {
typ: ErrorDeletedType.String(),
details: "resource is deleted",
status: http.StatusNotFound,
},
ErrorNotImplementedType: {
typ: ErrorNotImplementedType.String(),
details: "not implemented",
status: http.StatusNotImplemented,
},
ErrorBadRequestType: {
typ: ErrorBadRequestType.String(),
details: "bad request",
status: http.StatusBadRequest,
},
ErrorUnauthorizedType: {
typ: ErrorUnauthorizedType.String(),
details: "unauthorized",
status: http.StatusUnauthorized,
},
ErrorServerInternalType: errorServerInternalMetadata,
ErrorConflictType: {
typ: ErrorConflictType.String(),
details: "conflict",
status: http.StatusConflict,
},
}
)
// Error represents an Admin error
type Error struct {
Type string `json:"type"`
Detail string `json:"detail"`
Message string `json:"message"`
Err error `json:"-"`
Status int `json:"-"`
}
// IsType returns true if the error type matches the input type.
func (e *Error) IsType(pt ProblemType) bool {
return pt.String() == e.Type
}
// NewError creates a new Error type.
func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
return newError(pt, errors.Errorf(msg, args...))
}
func newError(pt ProblemType, err error) *Error {
meta, ok := errorMap[pt]
if !ok {
meta = errorServerInternalMetadata
return &Error{
Type: meta.typ,
Detail: meta.details,
Status: meta.status,
Err: err,
}
}
return &Error{
Type: meta.typ,
Detail: meta.details,
Status: meta.status,
Err: err,
}
}
// NewErrorISE creates a new ErrorServerInternalType Error.
func NewErrorISE(msg string, args ...interface{}) *Error {
return NewError(ErrorServerInternalType, msg, args...)
}
// WrapError attempts to wrap the internal error.
func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error {
var ee *Error
switch {
case err == nil:
return nil
case errors.As(err, &ee):
if ee.Err == nil {
ee.Err = errors.Errorf(msg+"; "+ee.Detail, args...)
} else {
ee.Err = errors.Wrapf(ee.Err, msg, args...)
}
return ee
default:
return newError(typ, errors.Wrapf(err, msg, args...))
}
}
// WrapErrorISE shortcut to wrap an internal server error type.
func WrapErrorISE(err error, msg string, args ...interface{}) *Error {
return WrapError(ErrorServerInternalType, err, msg, args...)
}
// StatusCode returns the status code and implements the StatusCoder interface.
func (e *Error) StatusCode() int {
return e.Status
}
// Error allows AError to implement the error interface.
func (e *Error) Error() string {
return e.Err.Error()
}
// Cause returns the internal error and implements the Causer interface.
func (e *Error) Cause() error {
if e.Err == nil {
return errors.New(e.Detail)
}
return e.Err
}
// ToLog implements the EnableLogger interface.
func (e *Error) ToLog() (interface{}, error) {
b, err := json.Marshal(e)
if err != nil {
return nil, WrapErrorISE(err, "error marshaling authority.Error for logging")
}
return string(b), nil
}
// Render implements render.RenderableError for Error.
func (e *Error) Render(w http.ResponseWriter, r *http.Request) {
e.Message = e.Err.Error()
render.JSONStatus(w, r, e, e.StatusCode())
}
|