package errcode import ( "fmt" "net/http" "strings" ) // 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 // 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 } var ( errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{} idToDescriptors = map[string]ErrorDescriptor{} ) const ( // ErrorCodeUnknown is a catch-all for errors not defined below. ErrorCodeUnknown ErrorCode = 10000 + iota ) var errorDescriptors = []ErrorDescriptor{ { Code: ErrorCodeUnknown, Value: "UNKNOWN", Message: "unknown error", Description: `Generic error returned when the error does not have an API classification.`, HTTPStatusCode: http.StatusInternalServerError, }, } // LoadErrors will register a new set of Errors into the system func LoadErrors(errs *[]ErrorDescriptor) { for _, descriptor := range *errs { if _, ok := idToDescriptors[descriptor.Value]; ok { panic(fmt.Sprintf("ErrorValue %s is already registered", descriptor.Value)) } if _, ok := errorCodeToDescriptors[descriptor.Code]; ok { panic(fmt.Sprintf("ErrorCode %d is already registered", descriptor.Code)) } errorCodeToDescriptors[descriptor.Code] = descriptor idToDescriptors[descriptor.Value] = descriptor } } // ParseErrorCode attempts to parse the error code string, returning // ErrorCodeUnknown if the error is not known. func ParseErrorCode(s string) ErrorCode { desc, ok := idToDescriptors[s] if !ok { return ErrorCodeUnknown } return desc.Code } // 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 } // Error provides a wrapper around ErrorCode with extra Details provided. type Error struct { Code ErrorCode `json:"code"` Message string `json:"message,omitempty"` Detail interface{} `json:"detail,omitempty"` } // Error returns a human readable representation of the error. func (e Error) Error() string { return fmt.Sprintf("%s: %s", strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), e.Message) } // Errors provides the envelope for multiple errors and a few sugar methods // for use within the application. type Errors struct { Errors []Error `json:"errors,omitempty"` } // Push pushes an error on to the error stack, with the optional detail // argument. It is a programming error (ie panic) to push more than one // detail at a time. func (errs *Errors) Push(code ErrorCode, details ...interface{}) { if len(details) > 1 { panic("please specify zero or one detail items for this error") } var detail interface{} if len(details) > 0 { detail = details[0] } if err, ok := detail.(error); ok { detail = err.Error() } errs.PushErr(Error{ Code: code, Message: code.Message(), Detail: detail, }) } // PushErr pushes an error interface onto the error stack. func (errs *Errors) PushErr(err error) { switch err.(type) { case Error: errs.Errors = append(errs.Errors, err.(Error)) default: errs.Errors = append(errs.Errors, Error{Message: err.Error()}) } } func (errs *Errors) Error() string { switch errs.Len() { case 0: return "" case 1: return errs.Errors[0].Error() default: msg := "errors:\n" for _, err := range errs.Errors { msg += err.Error() + "\n" } return msg } } // Clear clears the errors. func (errs *Errors) Clear() { errs.Errors = nil } // Len returns the current number of errors. func (errs *Errors) Len() int { return len(errs.Errors) } // init loads the default errors that are part of the errcode package func init() { LoadErrors(&errorDescriptors) }