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