123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- 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
- }
|