github.com/furusax0621/goa-v1@v1.4.3/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 // ErrMethodNotAllowed is the error returned to requests that match the path of a registered 73 // handler but not the HTTP method. 74 ErrMethodNotAllowed = NewErrorClass("method_not_allowed", 405) 75 76 // ErrInternal is the class of error used for uncaught errors. 77 ErrInternal = NewErrorClass("internal", 500) 78 ) 79 80 type ( 81 // ErrorClass is an error generating function. 82 // It accepts a message and optional key value pairs and produces errors that implement 83 // ServiceError. 84 // If the message is a string or a fmt.Stringer then the string value is used. 85 // If the message is an error then the string returned by Error() is used. 86 // Otherwise the string produced using fmt.Sprintf("%v") is used. 87 // The optional key value pairs are intended to provide additional contextual information 88 // and are returned to the client. 89 ErrorClass func(message interface{}, keyvals ...interface{}) error 90 91 // ServiceError is the interface implemented by all errors created using a ErrorClass 92 // function. 93 ServiceError interface { 94 // ServiceError extends the error interface 95 error 96 // ResponseStatus dictates the status used to build the response sent to the client. 97 ResponseStatus() int 98 // Token is a unique value associated with the occurrence of the error. 99 Token() string 100 } 101 102 // ServiceMergeableError is the interface implemented by ServiceErrors that can merge 103 // another error into a combined error. 104 ServiceMergeableError interface { 105 // ServiceMergeableError extends from the ServiceError interface. 106 ServiceError 107 108 // Merge updates an error by combining another error into it. 109 Merge(other error) error 110 } 111 112 // ErrorResponse contains the details of a error response. It implements ServiceError. 113 // This struct is mainly intended for clients to decode error responses. 114 ErrorResponse struct { 115 // ID is the unique error instance identifier. 116 ID string `json:"id" yaml:"id" xml:"id" form:"id"` 117 // Code identifies the class of errors. 118 Code string `json:"code" yaml:"code" xml:"code" form:"code"` 119 // Status is the HTTP status code used by responses that cary the error. 120 Status int `json:"status" yaml:"status" xml:"status" form:"status"` 121 // Detail describes the specific error occurrence. 122 Detail string `json:"detail" yaml:"detail" xml:"detail" form:"detail"` 123 // Meta contains additional key/value pairs useful to clients. 124 Meta map[string]interface{} `json:"meta,omitempty" yaml:"meta,omitempty" xml:"meta,omitempty" form:"meta,omitempty"` 125 } 126 ) 127 128 // NewErrorClass creates a new error class. 129 // It is the responsibility of the client to guarantee uniqueness of code. 130 func NewErrorClass(code string, status int) ErrorClass { 131 return func(message interface{}, keyvals ...interface{}) error { 132 var msg string 133 switch actual := message.(type) { 134 case string: 135 msg = actual 136 case error: 137 msg = actual.Error() 138 case fmt.Stringer: 139 msg = actual.String() 140 default: 141 msg = fmt.Sprintf("%v", actual) 142 } 143 var meta map[string]interface{} 144 l := len(keyvals) 145 if l > 0 { 146 meta = make(map[string]interface{}) 147 } 148 for i := 0; i < l; i += 2 { 149 k := keyvals[i] 150 var v interface{} = "MISSING" 151 if i+1 < l { 152 v = keyvals[i+1] 153 } 154 meta[fmt.Sprintf("%v", k)] = v 155 } 156 return &ErrorResponse{ID: newErrorID(), Code: code, Status: status, Detail: msg, Meta: meta} 157 } 158 } 159 160 // MissingPayloadError is the error produced when a request is missing a required payload. 161 func MissingPayloadError() error { 162 return ErrInvalidRequest("missing required payload") 163 } 164 165 // InvalidParamTypeError is the error produced when the type of a parameter does not match the type 166 // defined in the design. 167 func InvalidParamTypeError(name string, val interface{}, expected string) error { 168 msg := fmt.Sprintf("invalid value %#v for parameter %#v, must be a %s", val, name, expected) 169 return ErrInvalidRequest(msg, "param", name, "value", val, "expected", expected) 170 } 171 172 // MissingParamError is the error produced for requests that are missing path or querystring 173 // parameters. 174 func MissingParamError(name string) error { 175 msg := fmt.Sprintf("missing required parameter %#v", name) 176 return ErrInvalidRequest(msg, "name", name) 177 } 178 179 // InvalidAttributeTypeError is the error produced when the type of payload field does not match 180 // the type defined in the design. 181 func InvalidAttributeTypeError(ctx string, val interface{}, expected string) error { 182 msg := fmt.Sprintf("type of %s must be %s but got value %#v", ctx, expected, val) 183 return ErrInvalidRequest(msg, "attribute", ctx, "value", val, "expected", expected) 184 } 185 186 // MissingAttributeError is the error produced when a request payload is missing a required field. 187 func MissingAttributeError(ctx, name string) error { 188 msg := fmt.Sprintf("attribute %#v of %s is missing and required", name, ctx) 189 return ErrInvalidRequest(msg, "attribute", name, "parent", ctx) 190 } 191 192 // MissingHeaderError is the error produced when a request is missing a required header. 193 func MissingHeaderError(name string) error { 194 msg := fmt.Sprintf("missing required HTTP header %#v", name) 195 return ErrInvalidRequest(msg, "name", name) 196 } 197 198 // InvalidEnumValueError is the error produced when the value of a parameter or payload field does 199 // not match one the values defined in the design Enum validation. 200 func InvalidEnumValueError(ctx string, val interface{}, allowed []interface{}) error { 201 elems := make([]string, len(allowed)) 202 for i, a := range allowed { 203 elems[i] = fmt.Sprintf("%#v", a) 204 } 205 msg := fmt.Sprintf("value of %s must be one of %s but got value %#v", ctx, strings.Join(elems, ", "), val) 206 return ErrInvalidRequest(msg, "attribute", ctx, "value", val, "expected", strings.Join(elems, ", ")) 207 } 208 209 // InvalidFormatError is the error produced when the value of a parameter or payload field does not 210 // match the format validation defined in the design. 211 func InvalidFormatError(ctx, target string, format Format, formatError error) error { 212 msg := fmt.Sprintf("%s must be formatted as a %s but got value %#v, %s", ctx, format, target, formatError.Error()) 213 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "expected", format, "error", formatError.Error()) 214 } 215 216 // InvalidPatternError is the error produced when the value of a parameter or payload field does 217 // not match the pattern validation defined in the design. 218 func InvalidPatternError(ctx, target string, pattern string) error { 219 msg := fmt.Sprintf("%s must match the regexp %#v but got value %#v", ctx, pattern, target) 220 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "regexp", pattern) 221 } 222 223 // InvalidRangeError is the error produced when the value of a parameter or payload field does 224 // not match the range validation defined in the design. value may be a int or a float64. 225 func InvalidRangeError(ctx string, target interface{}, value interface{}, min bool) error { 226 comp := "greater than or equal to" 227 if !min { 228 comp = "less than or equal to" 229 } 230 msg := fmt.Sprintf("%s must be %s %v but got value %#v", ctx, comp, value, target) 231 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "comp", comp, "expected", value) 232 } 233 234 // InvalidLengthError is the error produced when the value of a parameter or payload field does 235 // not match the length validation defined in the design. 236 func InvalidLengthError(ctx string, target interface{}, ln, value int, min bool) error { 237 comp := "greater than or equal to" 238 if !min { 239 comp = "less than or equal to" 240 } 241 msg := fmt.Sprintf("length of %s must be %s %d but got value %#v (len=%d)", ctx, comp, value, target, ln) 242 return ErrInvalidRequest(msg, "attribute", ctx, "value", target, "len", ln, "comp", comp, "expected", value) 243 } 244 245 // NoAuthMiddleware is the error produced when goa is unable to lookup a auth middleware for a 246 // security scheme defined in the design. 247 func NoAuthMiddleware(schemeName string) error { 248 msg := fmt.Sprintf("Auth middleware for security scheme %s is not mounted", schemeName) 249 return ErrNoAuthMiddleware(msg, "scheme", schemeName) 250 } 251 252 // MethodNotAllowedError is the error produced to requests that match the path of a registered 253 // handler but not the HTTP method. 254 func MethodNotAllowedError(method string, allowed []string) error { 255 var plural string 256 if len(allowed) > 1 { 257 plural = " one of" 258 } 259 msg := fmt.Sprintf("Method %s must be%s %s", method, plural, strings.Join(allowed, ", ")) 260 return ErrMethodNotAllowed(msg, "method", method, "allowed", strings.Join(allowed, ", ")) 261 } 262 263 // Error returns the error occurrence details. 264 func (e *ErrorResponse) Error() string { 265 msg := fmt.Sprintf("[%s] %d %s: %s", e.ID, e.Status, e.Code, e.Detail) 266 for k, v := range e.Meta { 267 msg += ", " + fmt.Sprintf("%s: %v", k, v) 268 } 269 return msg 270 } 271 272 // ResponseStatus is the status used to build responses. 273 func (e *ErrorResponse) ResponseStatus() int { return e.Status } 274 275 // Token is the unique error occurrence identifier. 276 func (e *ErrorResponse) Token() string { return e.ID } 277 278 // MergeErrors updates an error by merging another into it. It first converts other into a 279 // ServiceError if not already one - producing an internal error in that case. The merge algorithm 280 // is: 281 // 282 // * If any of e or other implements ServiceMergableError, it is handled by its Merge method. 283 // 284 // * If any of e or other is an internal error then the result is an internal error 285 // 286 // * If the status or code of e and other don't match then the result is a 400 "bad_request" 287 // 288 // The Detail field is updated by concatenating the Detail fields of e and other separated 289 // by a semi-colon. The MetaValues field of is updated by merging the map of other MetaValues 290 // into e's where values in e with identical keys to values in other get overwritten. 291 // 292 // Merge returns the updated error. This is useful in case the error was initially nil in 293 // which case other is returned. 294 func MergeErrors(err, other error) error { 295 if err == nil { 296 if other == nil { 297 return nil 298 } 299 return asServiceError(other) 300 } 301 if other == nil { 302 return asServiceError(err) 303 } 304 305 // If either error is a mergable error. 306 if me, ok := err.(ServiceMergeableError); ok { 307 return me.Merge(other) 308 } 309 if mo, ok := other.(ServiceMergeableError); ok { 310 return mo.Merge(err) 311 } 312 313 e := asErrorResponse(err) 314 o := asErrorResponse(other) 315 switch { 316 case e.Status == 500 || o.Status == 500: 317 if e.Status != 500 { 318 e.Status = 500 319 e.Code = "internal_error" 320 } 321 case e.Status != o.Status || e.Code != o.Code: 322 e.Status = 400 323 e.Code = "bad_request" 324 } 325 e.Detail = e.Detail + "; " + o.Detail 326 327 if e.Meta == nil && len(o.Meta) > 0 { 328 e.Meta = make(map[string]interface{}) 329 } 330 for k, v := range o.Meta { 331 e.Meta[k] = v 332 } 333 return e 334 } 335 336 func asServiceError(err error) ServiceError { 337 e, ok := err.(ServiceError) 338 if !ok { 339 return asErrorResponse(err) 340 } 341 return e 342 } 343 344 func asErrorResponse(err error) *ErrorResponse { 345 e, ok := err.(*ErrorResponse) 346 if !ok { 347 return &ErrorResponse{Status: 500, Code: "internal_error", Detail: err.Error()} 348 } 349 return e 350 } 351 352 // If you're curious - simplifying a bit - the probability of 2 values being equal for n 6-bytes 353 // values is n^2 / 2^49. For n = 1 million this gives around 1 chance in 500. 6 bytes seems to be a 354 // good trade-off between probability of clashes and length of ID (6 * 4/3 = 8 chars) since clashes 355 // are not catastrophic. 356 func newErrorID() string { 357 b := make([]byte, 6) 358 io.ReadFull(rand.Reader, b) 359 return base64.StdEncoding.EncodeToString(b) 360 }