1package errcode 2 3import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7) 8 9// ErrorCoder is the base interface for ErrorCode and Error allowing 10// users of each to just call ErrorCode to get the real ID of each 11type ErrorCoder interface { 12 ErrorCode() ErrorCode 13} 14 15// ErrorCode represents the error type. The errors are serialized via strings 16// and the integer format may change and should *never* be exported. 17type ErrorCode int 18 19var _ error = ErrorCode(0) 20 21// ErrorCode just returns itself 22func (ec ErrorCode) ErrorCode() ErrorCode { 23 return ec 24} 25 26// Error returns the ID/Value 27func (ec ErrorCode) Error() string { 28 // NOTE(stevvooe): Cannot use message here since it may have unpopulated args. 29 return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1)) 30} 31 32// Descriptor returns the descriptor for the error code. 33func (ec ErrorCode) Descriptor() ErrorDescriptor { 34 d, ok := errorCodeToDescriptors[ec] 35 36 if !ok { 37 return ErrorCodeUnknown.Descriptor() 38 } 39 40 return d 41} 42 43// String returns the canonical identifier for this error code. 44func (ec ErrorCode) String() string { 45 return ec.Descriptor().Value 46} 47 48// Message returned the human-readable error message for this error code. 49func (ec ErrorCode) Message() string { 50 return ec.Descriptor().Message 51} 52 53// MarshalText encodes the receiver into UTF-8-encoded text and returns the 54// result. 55func (ec ErrorCode) MarshalText() (text []byte, err error) { 56 return []byte(ec.String()), nil 57} 58 59// UnmarshalText decodes the form generated by MarshalText. 60func (ec *ErrorCode) UnmarshalText(text []byte) error { 61 desc, ok := idToDescriptors[string(text)] 62 63 if !ok { 64 desc = ErrorCodeUnknown.Descriptor() 65 } 66 67 *ec = desc.Code 68 69 return nil 70} 71 72// WithMessage creates a new Error struct based on the passed-in info and 73// overrides the Message property. 74func (ec ErrorCode) WithMessage(message string) Error { 75 return Error{ 76 Code: ec, 77 Message: message, 78 } 79} 80 81// WithDetail creates a new Error struct based on the passed-in info and 82// set the Detail property appropriately 83func (ec ErrorCode) WithDetail(detail interface{}) Error { 84 return Error{ 85 Code: ec, 86 Message: ec.Message(), 87 }.WithDetail(detail) 88} 89 90// WithArgs creates a new Error struct and sets the Args slice 91func (ec ErrorCode) WithArgs(args ...interface{}) Error { 92 return Error{ 93 Code: ec, 94 Message: ec.Message(), 95 }.WithArgs(args...) 96} 97 98// Error provides a wrapper around ErrorCode with extra Details provided. 99type Error struct { 100 Code ErrorCode `json:"code"` 101 Message string `json:"message"` 102 Detail interface{} `json:"detail,omitempty"` 103 104 // TODO(duglin): See if we need an "args" property so we can do the 105 // variable substitution right before showing the message to the user 106} 107 108var _ error = Error{} 109 110// ErrorCode returns the ID/Value of this Error 111func (e Error) ErrorCode() ErrorCode { 112 return e.Code 113} 114 115// Error returns a human readable representation of the error. 116func (e Error) Error() string { 117 return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message) 118} 119 120// WithDetail will return a new Error, based on the current one, but with 121// some Detail info added 122func (e Error) WithDetail(detail interface{}) Error { 123 return Error{ 124 Code: e.Code, 125 Message: e.Message, 126 Detail: detail, 127 } 128} 129 130// WithArgs uses the passed-in list of interface{} as the substitution 131// variables in the Error's Message string, but returns a new Error 132func (e Error) WithArgs(args ...interface{}) Error { 133 return Error{ 134 Code: e.Code, 135 Message: fmt.Sprintf(e.Code.Message(), args...), 136 Detail: e.Detail, 137 } 138} 139 140// ErrorDescriptor provides relevant information about a given error code. 141type ErrorDescriptor struct { 142 // Code is the error code that this descriptor describes. 143 Code ErrorCode 144 145 // Value provides a unique, string key, often captilized with 146 // underscores, to identify the error code. This value is used as the 147 // keyed value when serializing api errors. 148 Value string 149 150 // Message is a short, human readable decription of the error condition 151 // included in API responses. 152 Message string 153 154 // Description provides a complete account of the errors purpose, suitable 155 // for use in documentation. 156 Description string 157 158 // HTTPStatusCode provides the http status code that is associated with 159 // this error condition. 160 HTTPStatusCode int 161} 162 163// ParseErrorCode returns the value by the string error code. 164// `ErrorCodeUnknown` will be returned if the error is not known. 165func ParseErrorCode(value string) ErrorCode { 166 ed, ok := idToDescriptors[value] 167 if ok { 168 return ed.Code 169 } 170 171 return ErrorCodeUnknown 172} 173 174// Errors provides the envelope for multiple errors and a few sugar methods 175// for use within the application. 176type Errors []error 177 178var _ error = Errors{} 179 180func (errs Errors) Error() string { 181 switch len(errs) { 182 case 0: 183 return "<nil>" 184 case 1: 185 return errs[0].Error() 186 default: 187 msg := "errors:\n" 188 for _, err := range errs { 189 msg += err.Error() + "\n" 190 } 191 return msg 192 } 193} 194 195// Len returns the current number of errors. 196func (errs Errors) Len() int { 197 return len(errs) 198} 199 200// MarshalJSON converts slice of error, ErrorCode or Error into a 201// slice of Error - then serializes 202func (errs Errors) MarshalJSON() ([]byte, error) { 203 var tmpErrs struct { 204 Errors []Error `json:"errors,omitempty"` 205 } 206 207 for _, daErr := range errs { 208 var err Error 209 210 switch daErr := daErr.(type) { 211 case ErrorCode: 212 err = daErr.WithDetail(nil) 213 case Error: 214 err = daErr 215 default: 216 err = ErrorCodeUnknown.WithDetail(daErr) 217 218 } 219 220 // If the Error struct was setup and they forgot to set the 221 // Message field (meaning its "") then grab it from the ErrCode 222 msg := err.Message 223 if msg == "" { 224 msg = err.Code.Message() 225 } 226 227 tmpErrs.Errors = append(tmpErrs.Errors, Error{ 228 Code: err.Code, 229 Message: msg, 230 Detail: err.Detail, 231 }) 232 } 233 234 return json.Marshal(tmpErrs) 235} 236 237// UnmarshalJSON deserializes []Error and then converts it into slice of 238// Error or ErrorCode 239func (errs *Errors) UnmarshalJSON(data []byte) error { 240 var tmpErrs struct { 241 Errors []Error 242 } 243 244 if err := json.Unmarshal(data, &tmpErrs); err != nil { 245 return err 246 } 247 248 var newErrs Errors 249 for _, daErr := range tmpErrs.Errors { 250 // If Message is empty or exactly matches the Code's message string 251 // then just use the Code, no need for a full Error struct 252 if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) { 253 // Error's w/o details get converted to ErrorCode 254 newErrs = append(newErrs, daErr.Code) 255 } else { 256 // Error's w/ details are untouched 257 newErrs = append(newErrs, Error{ 258 Code: daErr.Code, 259 Message: daErr.Message, 260 Detail: daErr.Detail, 261 }) 262 } 263 } 264 265 *errs = newErrs 266 return nil 267} 268