github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/error.go (about) 1 /* 2 Package goa standardizes on structured error responses: a request that fails because of an 3 invalid input or an unexpected condition produces a response that contains a structured error. 4 5 The error data structures returned to clients contains five fields: an ID, a code, a status, a 6 detail and metadata. The ID is unique for the occurrence of the error, it helps correlate the 7 content of the response with the content of the service logs. The code defines the class of error 8 (e.g. "invalid_parameter_type") and the status the corresponding HTTP status (e.g. 400). The detail 9 contains a message specific to the error occurrence. The metadata contains key/value pairs that 10 provide contextual information (name of parameters, value of invalid parameter etc.). 11 12 Instances of Error can be created via Error Class functions. 13 See http://goa.design/implement/error_handling.html 14 All instance of errors created via a error class implement the ServiceError interface. This 15 interface is leveraged by the error handler middleware to produce the error responses. 16 17 The code generated by goagen calls the helper functions exposed in this file when it encounters 18 invalid data (wrong type, validation errors etc.) such as InvalidParamTypeError, 19 InvalidAttributeTypeError etc. These methods return errors that get merged with any previously 20 encountered error via the Error Merge method. The helper functions are error classes stored in 21 global variable. This means your code can override their values to produce arbitrary error 22 responses. 23 24 goa includes an error handler middleware that takes care of mapping back any error returned by 25 previously called middleware or action handler into HTTP responses. If the error was created via an 26 error class then the corresponding content including the HTTP status is used otherwise an internal 27 error is returned. Errors that bubble up all the way to the top (i.e. not handled by the error 28 middleware) also generate an internal error response. 29 */ 30 package goa 31 32 import ( 33 "crypto/rand" 34 "encoding/base64" 35 "fmt" 36 "io" 37 "strings" 38 ) 39 40 var ( 41 // ErrorMediaIdentifier is the media type identifier used for error responses. 42 ErrorMediaIdentifier = "application/vnd.goa.error" 43 44 // ErrBadRequest is a generic bad request error. 45 ErrBadRequest = NewErrorClass("bad_request", 400) 46 47 // ErrUnauthorized is a generic unauthorized error. 48 ErrUnauthorized = NewErrorClass("unauthorized", 401) 49 50 // ErrInvalidRequest is the class of errors produced by the generated code when a request 51 // parameter or payload fails to validate. 52 ErrInvalidRequest = NewErrorClass("invalid_request", 400) 53 54 // ErrInvalidEncoding is the error produced when a request body fails to be decoded. 55 ErrInvalidEncoding = NewErrorClass("invalid_encoding", 400) 56 57 // ErrRequestBodyTooLarge is the error produced when the size of a request body exceeds 58 // MaxRequestBodyLength bytes. 59 ErrRequestBodyTooLarge = NewErrorClass("request_too_large", 413) 60 61 // ErrNoAuthMiddleware is the error produced when no auth middleware is mounted for a 62 // security scheme defined in the design. 63 ErrNoAuthMiddleware = NewErrorClass("no_auth_middleware", 500) 64 65 // ErrInvalidFile is the error produced by ServeFiles when requested to serve non-existant 66 // or non-readable files. 67 ErrInvalidFile = NewErrorClass("invalid_file", 404) 68 69 // ErrNotFound is the error returned to requests that don't match a registered handler. 70 ErrNotFound = NewErrorClass("not_found", 404) 71 72 // ErrInternal is the class of error used for uncaught errors. 73 ErrInternal = NewErrorClass("internal", 500) 74 ) 75 76 type ( 77 // ErrorClass is an error generating function. 78 // It accepts a message and optional key value pairs and produces errors that implement 79 // ServiceError. 80 // If the message is a string or a fmt.Stringer then the string value is used. 81 // If the message is an error then the string returned by Error() is used. 82 // Otherwise the string produced using fmt.Sprintf("%v") is used. 83 // The optional key value pairs are intended to provide additional contextual information 84 // and are returned to the client. 85 ErrorClass func(message interface{}, keyvals ...interface{}) error 86 87 // ServiceError is the interface implemented by all errors created using a ErrorClass 88 // function. 89 ServiceError interface { 90 // ServiceError extends the error interface 91 error 92 // ResponseStatus dictates the status used to build the response sent to the client. 93 ResponseStatus() int 94 // Token is a unique value associated with the occurrence of the error. 95 Token() string 96 } 97 98 // ErrorResponse contains the details of a error response. It implements ServiceError. 99 // This struct is mainly intended for clients to decode error responses. 100 ErrorResponse struct { 101 // ID is the unique error instance identifier. 102 ID string `json:"id" xml:"id" form:"id"` 103 // Code identifies the class of errors. 104 Code string `json:"code" xml:"code" form:"code"` 105 // Status is the HTTP status code used by responses that cary the error. 106 Status int `json:"status" xml:"status" form:"status"` 107 // Detail describes the specific error occurrence. 108 Detail string `json:"detail" xml:"detail" form:"detail"` 109 // Meta contains additional key/value pairs useful to clients. 110 Meta map[string]interface{} `json:"meta,omitempty" xml:"meta,omitempty" form:"meta,omitempty"` 111 } 112 ) 113 114 // NewErrorClass creates a new error class. 115 // It is the responsibility of the client to guarantee uniqueness of code. 116 func NewErrorClass(code string, status int) ErrorClass { 117 return func(message interface{}, keyvals ...interface{}) error { 118 var msg string 119 switch actual := message.(type) { 120 case string: 121 msg = actual 122 case error: 123 msg = actual.Error() 124 case fmt.Stringer: 125 msg = actual.String() 126 default: 127 msg = fmt.Sprintf("%v", actual) 128 } 129 var meta map[string]interface{} 130 l := len(keyvals) 131 if l > 0 { 132 meta = make(map[string]interface{}) 133 } 134 for i := 0; i < l; i += 2 { 135 k := keyvals[i] 136 var v interface{} = "MISSING" 137 if i+1 < l { 138 v = keyvals[i+1] 139 } 140 meta[fmt.Sprintf("%v", k)] = v 141 } 142 return &ErrorResponse{ID: newErrorID(), Code: code, Status: status, Detail: msg, Meta: meta} 143 } 144 } 145 146 // MissingPayloadError is the error produced when a request is missing a required payload. 147 func MissingPayloadError() error { 148 return ErrInvalidRequest("missing required payload") 149 } 150 151 // InvalidParamTypeError is the error produced when the type of a parameter does not match the type 152 // defined in the design. 153 func InvalidParamTypeError(name string, val interface{}, expected string) error { 154 msg := fmt.Sprintf("invalid value %#v for parameter %#v, must be a %s", val, name, expected) 155 return ErrInvalidRequest(msg, "param", name, "value", val, "expected", expected) 156 } 157 158 // MissingParamError is the error produced for requests that are missing path or querystring 159 // parameters. 160 func MissingParamError(name string) error { 161 msg := fmt.Sprintf("missing required parameter %#v", name) 162 return ErrInvalidRequest(msg, "name", name) 163 } 164 165 // InvalidAttributeTypeError is the error produced when the type of payload field does not match 166 // the type defined in the design. 167 func InvalidAttributeTypeError(ctx string, val interface{}, expected string) error { 168 msg := fmt.Sprintf("type of %s must be %s but got value %#v", ctx, expected, val) 169 return ErrInvalidRequest(msg, "attribute", ctx, "value", val, "expected", expected) 170 } 171 172 // MissingAttributeError is the error produced when a request payload is missing a required field. 173 func MissingAttributeError(ctx, name string) error { 174 msg := fmt.Sprintf("attribute %#v of %s is missing and required", name, ctx) 175 return ErrInvalidRequest(msg, "attribute", name, "parent", ctx) 176 } 177 178 // MissingHeaderError is the error produced when a request is missing a required header. 179 func MissingHeaderError(name string) error { 180 msg := fmt.Sprintf("missing required HTTP header %#v", name) 181 return ErrInvalidRequest(msg, "name", name) 182 } 183 184 // InvalidEnumValueError is the error produced when the value of a parameter or payload field does 185 // not match one the values defined in the design Enum validation. 186 func InvalidEnumValueError(ctx string, val interface{}, allowed []interface{}) error { 187 elems := make([]string, len(allowed)) 188 for i, a := range allowed { 189 elems[i] = fmt.Sprintf("%#v", a) 190 } 191 msg := fmt.Sprintf("value of %s must be one of %s but got value %#v", ctx, strings.Join(elems, ", "), val) 192 return ErrInvalidRequest(msg, "attribute", ctx, "value", val, "expected", strings.Join(elems, ", ")) 193 } 194 195 // InvalidFormatError is the error produced when the value of a parameter or payload field does not 196 // match the format validation defined in the design. 197 func InvalidFormatError(ctx, target string, format Format, formatError error) error { 198 msg := fmt.Sprintf("%s must be formatted as a %s but got value %#v, %s", ctx, format, target, formatError.Error()) 199 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "expected", format, "error", formatError.Error()) 200 } 201 202 // InvalidPatternError is the error produced when the value of a parameter or payload field does 203 // not match the pattern validation defined in the design. 204 func InvalidPatternError(ctx, target string, pattern string) error { 205 msg := fmt.Sprintf("%s must match the regexp %#v but got value %#v", ctx, pattern, target) 206 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "regexp", pattern) 207 } 208 209 // InvalidRangeError is the error produced when the value of a parameter or payload field does 210 // not match the range validation defined in the design. value may be a int or a float64. 211 func InvalidRangeError(ctx string, target interface{}, value interface{}, min bool) error { 212 comp := "greater than or equal to" 213 if !min { 214 comp = "less than or equal to" 215 } 216 msg := fmt.Sprintf("%s must be %s %d but got value %#v", ctx, comp, value, target) 217 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "comp", comp, "expected", value) 218 } 219 220 // InvalidLengthError is the error produced when the value of a parameter or payload field does 221 // not match the length validation defined in the design. 222 func InvalidLengthError(ctx string, target interface{}, ln, value int, min bool) error { 223 comp := "greater than or equal to" 224 if !min { 225 comp = "less than or equal to" 226 } 227 msg := fmt.Sprintf("length of %s must be %s %d but got value %#v (len=%d)", ctx, comp, value, target, ln) 228 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "len", ln, "comp", comp, "expected", value) 229 } 230 231 // NoAuthMiddleware is the error produced when goa is unable to lookup a auth middleware for a 232 // security scheme defined in the design. 233 func NoAuthMiddleware(schemeName string) error { 234 msg := fmt.Sprintf("Auth middleware for security scheme %s is not mounted", schemeName) 235 return ErrNoAuthMiddleware(msg, "scheme", schemeName) 236 } 237 238 // Error returns the error occurrence details. 239 func (e *ErrorResponse) Error() string { 240 msg := fmt.Sprintf("[%s] %d %s: %s", e.ID, e.Status, e.Code, e.Detail) 241 for k, v := range e.Meta { 242 msg += ", " + fmt.Sprintf("%s: %v", k, v) 243 } 244 return msg 245 } 246 247 // ResponseStatus is the status used to build responses. 248 func (e *ErrorResponse) ResponseStatus() int { return e.Status } 249 250 // Token is the unique error occurrence identifier. 251 func (e *ErrorResponse) Token() string { return e.ID } 252 253 // MergeErrors updates an error by merging another into it. It first converts other into a 254 // ServiceError if not already one - producing an internal error in that case. The merge algorithm 255 // is: 256 // 257 // * If any of e or other is an internal error then the result is an internal error 258 // 259 // * If the status or code of e and other don't match then the result is a 400 "bad_request" 260 // 261 // The Detail field is updated by concatenating the Detail fields of e and other separated 262 // by a semi-colon. The MetaValues field of is updated by merging the map of other MetaValues 263 // into e's where values in e with identical keys to values in other get overwritten. 264 // 265 // Merge returns the updated error. This is useful in case the error was initially nil in 266 // which case other is returned. 267 func MergeErrors(err, other error) error { 268 if err == nil { 269 if other == nil { 270 return nil 271 } 272 return asErrorResponse(other) 273 } 274 if other == nil { 275 return asErrorResponse(err) 276 } 277 e := asErrorResponse(err) 278 o := asErrorResponse(other) 279 switch { 280 case e.Status == 500 || o.Status == 500: 281 if e.Status != 500 { 282 e.Status = 500 283 e.Code = "internal_error" 284 } 285 case e.Status != o.Status || e.Code != o.Code: 286 e.Status = 400 287 e.Code = "bad_request" 288 } 289 e.Detail = e.Detail + "; " + o.Detail 290 291 if e.Meta == nil && len(o.Meta) > 0 { 292 e.Meta = make(map[string]interface{}) 293 } 294 for k, v := range o.Meta { 295 e.Meta[k] = v 296 } 297 return e 298 } 299 300 func asErrorResponse(err error) *ErrorResponse { 301 e, ok := err.(*ErrorResponse) 302 if !ok { 303 return &ErrorResponse{Status: 500, Code: "internal_error", Detail: err.Error()} 304 } 305 return e 306 } 307 308 // If you're curious - simplifying a bit - the probability of 2 values being equal for n 6-bytes 309 // values is n^2 / 2^49. For n = 1 million this gives around 1 chance in 500. 6 bytes seems to be a 310 // good trade-off between probability of clashes and length of ID (6 * 4/3 = 8 chars) since clashes 311 // are not catastrophic. 312 func newErrorID() string { 313 b := make([]byte, 6) 314 io.ReadFull(rand.Reader, b) 315 return base64.StdEncoding.EncodeToString(b) 316 }