| package errcode |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "strings" |
| ) |
| |
| // ErrorCoder is the base interface for ErrorCode and Error allowing |
| // users of each to just call ErrorCode to get the real ID of each |
| type ErrorCoder interface { |
| ErrorCode() ErrorCode |
| } |
| |
| // ErrorCode represents the error type. The errors are serialized via strings |
| // and the integer format may change and should *never* be exported. |
| type ErrorCode int |
| |
| var _ error = ErrorCode(0) |
| |
| // ErrorCode just returns itself |
| func (ec ErrorCode) ErrorCode() ErrorCode { |
| return ec |
| } |
| |
| // Error returns the ID/Value |
| func (ec ErrorCode) Error() string { |
| // NOTE(stevvooe): Cannot use message here since it may have unpopulated args. |
| return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1)) |
| } |
| |
| // Descriptor returns the descriptor for the error code. |
| func (ec ErrorCode) Descriptor() ErrorDescriptor { |
| d, ok := errorCodeToDescriptors[ec] |
| |
| if !ok { |
| return ErrorCodeUnknown.Descriptor() |
| } |
| |
| return d |
| } |
| |
| // String returns the canonical identifier for this error code. |
| func (ec ErrorCode) String() string { |
| return ec.Descriptor().Value |
| } |
| |
| // Message returned the human-readable error message for this error code. |
| func (ec ErrorCode) Message() string { |
| return ec.Descriptor().Message |
| } |
| |
| // MarshalText encodes the receiver into UTF-8-encoded text and returns the |
| // result. |
| func (ec ErrorCode) MarshalText() (text []byte, err error) { |
| return []byte(ec.String()), nil |
| } |
| |
| // UnmarshalText decodes the form generated by MarshalText. |
| func (ec *ErrorCode) UnmarshalText(text []byte) error { |
| desc, ok := idToDescriptors[string(text)] |
| |
| if !ok { |
| desc = ErrorCodeUnknown.Descriptor() |
| } |
| |
| *ec = desc.Code |
| |
| return nil |
| } |
| |
| // WithMessage creates a new Error struct based on the passed-in info and |
| // overrides the Message property. |
| func (ec ErrorCode) WithMessage(message string) Error { |
| return Error{ |
| Code: ec, |
| Message: message, |
| } |
| } |
| |
| // WithDetail creates a new Error struct based on the passed-in info and |
| // set the Detail property appropriately |
| func (ec ErrorCode) WithDetail(detail interface{}) Error { |
| return Error{ |
| Code: ec, |
| Message: ec.Message(), |
| }.WithDetail(detail) |
| } |
| |
| // WithArgs creates a new Error struct and sets the Args slice |
| func (ec ErrorCode) WithArgs(args ...interface{}) Error { |
| return Error{ |
| Code: ec, |
| Message: ec.Message(), |
| }.WithArgs(args...) |
| } |
| |
| // Error provides a wrapper around ErrorCode with extra Details provided. |
| type Error struct { |
| Code ErrorCode `json:"code"` |
| Message string `json:"message"` |
| Detail interface{} `json:"detail,omitempty"` |
| |
| // TODO(duglin): See if we need an "args" property so we can do the |
| // variable substitution right before showing the message to the user |
| } |
| |
| var _ error = Error{} |
| |
| // ErrorCode returns the ID/Value of this Error |
| func (e Error) ErrorCode() ErrorCode { |
| return e.Code |
| } |
| |
| // Error returns a human readable representation of the error. |
| func (e Error) Error() string { |
| return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message) |
| } |
| |
| // WithDetail will return a new Error, based on the current one, but with |
| // some Detail info added |
| func (e Error) WithDetail(detail interface{}) Error { |
| return Error{ |
| Code: e.Code, |
| Message: e.Message, |
| Detail: detail, |
| } |
| } |
| |
| // WithArgs uses the passed-in list of interface{} as the substitution |
| // variables in the Error's Message string, but returns a new Error |
| func (e Error) WithArgs(args ...interface{}) Error { |
| return Error{ |
| Code: e.Code, |
| Message: fmt.Sprintf(e.Code.Message(), args...), |
| Detail: e.Detail, |
| } |
| } |
| |
| // ErrorDescriptor provides relevant information about a given error code. |
| type ErrorDescriptor struct { |
| // Code is the error code that this descriptor describes. |
| Code ErrorCode |
| |
| // Value provides a unique, string key, often captilized with |
| // underscores, to identify the error code. This value is used as the |
| // keyed value when serializing api errors. |
| Value string |
| |
| // Message is a short, human readable decription of the error condition |
| // included in API responses. |
| Message string |
| |
| // Description provides a complete account of the errors purpose, suitable |
| // for use in documentation. |
| Description string |
| |
| // HTTPStatusCode provides the http status code that is associated with |
| // this error condition. |
| HTTPStatusCode int |
| } |
| |
| // ParseErrorCode returns the value by the string error code. |
| // `ErrorCodeUnknown` will be returned if the error is not known. |
| func ParseErrorCode(value string) ErrorCode { |
| ed, ok := idToDescriptors[value] |
| if ok { |
| return ed.Code |
| } |
| |
| return ErrorCodeUnknown |
| } |
| |
| // Errors provides the envelope for multiple errors and a few sugar methods |
| // for use within the application. |
| type Errors []error |
| |
| var _ error = Errors{} |
| |
| func (errs Errors) Error() string { |
| switch len(errs) { |
| case 0: |
| return "<nil>" |
| case 1: |
| return errs[0].Error() |
| default: |
| msg := "errors:\n" |
| for _, err := range errs { |
| msg += err.Error() + "\n" |
| } |
| return msg |
| } |
| } |
| |
| // Len returns the current number of errors. |
| func (errs Errors) Len() int { |
| return len(errs) |
| } |
| |
| // MarshalJSON converts slice of error, ErrorCode or Error into a |
| // slice of Error - then serializes |
| func (errs Errors) MarshalJSON() ([]byte, error) { |
| var tmpErrs struct { |
| Errors []Error `json:"errors,omitempty"` |
| } |
| |
| for _, daErr := range errs { |
| var err Error |
| |
| switch daErr.(type) { |
| case ErrorCode: |
| err = daErr.(ErrorCode).WithDetail(nil) |
| case Error: |
| err = daErr.(Error) |
| default: |
| err = ErrorCodeUnknown.WithDetail(daErr) |
| |
| } |
| |
| // If the Error struct was setup and they forgot to set the |
| // Message field (meaning its "") then grab it from the ErrCode |
| msg := err.Message |
| if msg == "" { |
| msg = err.Code.Message() |
| } |
| |
| tmpErrs.Errors = append(tmpErrs.Errors, Error{ |
| Code: err.Code, |
| Message: msg, |
| Detail: err.Detail, |
| }) |
| } |
| |
| return json.Marshal(tmpErrs) |
| } |
| |
| // UnmarshalJSON deserializes []Error and then converts it into slice of |
| // Error or ErrorCode |
| func (errs *Errors) UnmarshalJSON(data []byte) error { |
| var tmpErrs struct { |
| Errors []Error |
| } |
| |
| if err := json.Unmarshal(data, &tmpErrs); err != nil { |
| return err |
| } |
| |
| var newErrs Errors |
| for _, daErr := range tmpErrs.Errors { |
| // If Message is empty or exactly matches the Code's message string |
| // then just use the Code, no need for a full Error struct |
| if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) { |
| // Error's w/o details get converted to ErrorCode |
| newErrs = append(newErrs, daErr.Code) |
| } else { |
| // Error's w/ details are untouched |
| newErrs = append(newErrs, Error{ |
| Code: daErr.Code, |
| Message: daErr.Message, |
| Detail: daErr.Detail, |
| }) |
| } |
| } |
| |
| *errs = newErrs |
| return nil |
| } |