github.com/ManabuSeki/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  }