github.com/gocaveman/caveman@v0.0.0-20191211162744-0ddf99dbdf6e/weberrors/weberrors.go (about) 1 /* 2 Package weberrors augments errors with an additional code, data and message for convenience while writing web applications. 3 It also provides wrapping and unwrapping where a Cause() method undicates an underlying error. 4 5 The New() method will wrap the error your provide and return a new one with the provided code, message and data. 6 These fields correspond to the JSON RPC 2.0 error code, message and data fields. 7 They are also very useful when returning errors from REST calls. 8 9 The Causer interface specifies the method Cause() error which can be used to 10 retrieve an underlying error - it's cause. Any time an error is wrapped by a method in this package 11 the returned error will implement Causer. 12 13 The methods ErrorCode(error) int, ErrorMessage(error) string and ErrorData(error) interface{} 14 provide access to each of the fields wrapped with a New() call, and will "unwrap" the error by calling Cause() as necessary 15 to extract the needed value. The httpapi uses these methods to extract information when 16 17 Note that weberrors.ErrorMessage(err) and err.Error() will generally return two completely different things - the former 18 being the public-facing error message returned to the caller and the latter being the internal error message which is usually 19 either checked for and handled or logged. 20 21 // start with this error 22 var baseErr = fmt.Errorf("base error") 23 24 // wrap it with web info 25 var webErr = weberrors.New(baseErr, 26 501, 27 "Something went wrong", 28 "additional data") 29 30 // also specify the location 31 var finalErr = weberrors.ErrLoc(webErr) 32 33 // let's see what info we can extract from it 34 fmt.Printf("Error: %v\n", finalErr) 35 fmt.Printf("Error Cause: %v\n", weberrors.RootCause(finalErr)) 36 fmt.Printf("Error Code: %v\n", weberrors.ErrorCode(finalErr)) 37 fmt.Printf("Error Message: %v\n", weberrors.ErrorMessage(finalErr)) 38 fmt.Printf("Error Data: %v\n", weberrors.ErrorData(finalErr)) 39 40 */ 41 package weberrors 42 43 import ( 44 "fmt" 45 "math/rand" 46 "net/http" 47 "runtime" 48 "time" 49 ) 50 51 // NewCode returns an error with the cause and code you provide. 52 func NewCode(err error, code int) error { 53 if err == nil { 54 panic("nil cause error passed to weberrors.NewCode") 55 } 56 return &errCode{ 57 Err: err, 58 Code: code, 59 Headers: setErrID(nil), 60 } 61 } 62 63 type errCode struct { 64 Err error `json:"-"` 65 Code int `json:"code,omitempty"` 66 Headers http.Header `json:"-"` 67 } 68 69 func (c *errCode) Cause() error { 70 return c.Err 71 } 72 73 func (c *errCode) Error() string { 74 if c.Err != nil { 75 return c.Err.Error() 76 } 77 return "" 78 } 79 80 func (c *errCode) ErrorHeaders() http.Header { 81 return c.Headers 82 } 83 84 // New returns a error instance with the cause error, code, message, data and headers you provide. 85 func New(err error, code int, message string, data interface{}, headers http.Header) error { 86 if err == nil { 87 panic("nil cause error passed to weberrors.New") 88 } 89 return &detail{ 90 Err: err, 91 Code: code, 92 Message: message, 93 Data: data, 94 Headers: setErrID(headers), 95 } 96 } 97 98 type detail struct { 99 Err error `json:"-"` 100 Code int `json:"code,omitempty"` 101 Message string `json:"message,omitempty"` 102 Data interface{} `json:"data,omitempty"` 103 Headers http.Header `json:"-"` 104 } 105 106 // Cause returns the underlying error (field Err). 107 // We use the name Cause from github.com/pkg/errors, since 108 // the Go2 Error Value Draft Proposal, which suggests the name Unwrap, is not standardized yet 109 // (see https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md). 110 func (d *detail) Cause() error { 111 return d.Err 112 } 113 114 // Error implements the error interface by returning the Err.Error(). 115 // This corresponds to the internal message. 116 func (d *detail) Error() string { 117 if d.Err != nil { 118 return d.Err.Error() 119 } 120 return d.Message 121 } 122 123 // ErrorCode returns the error code. 124 func (d *detail) ErrorCode() int { 125 return d.Code 126 } 127 128 // ErrorMessage returns the public error message. 129 func (d *detail) ErrorMessage() string { 130 return d.Message 131 } 132 133 // ErrorData returns the data. 134 func (d *detail) ErrorData() interface{} { 135 return d.Data 136 } 137 138 // ErrorHeaders returns additional headers to be set on an HTTP response 139 func (d *detail) ErrorHeaders() http.Header { 140 return d.Headers 141 } 142 143 // ErrorCoder has an ErrorCode() method. 144 type ErrorCoder interface { 145 ErrorCode() int 146 } 147 148 // ErrorCode unwraps the error provided and returns the result of calling ErrorCode() on the first error that has implements it. 149 // Errors are unwrapped by calling Cause(). 150 func ErrorCode(err error) int { 151 152 for err != nil { 153 ec, ok := err.(ErrorCoder) 154 if ok { 155 return ec.ErrorCode() 156 } 157 c, ok := err.(Causer) 158 if !ok { 159 break 160 } 161 err = c.Cause() 162 } 163 164 return 0 165 } 166 167 // ErrorMessager has an ErrorMessage() method. 168 type ErrorMessager interface { 169 ErrorMessage() string 170 } 171 172 // ErrorMessage unwraps the error provided and returns the result of calling ErrorMessage() on the first error that has implements it. 173 // Errors are unwrapped by calling Cause(). 174 func ErrorMessage(err error) string { 175 176 for err != nil { 177 ec, ok := err.(ErrorMessager) 178 if ok { 179 return ec.ErrorMessage() 180 } 181 c, ok := err.(Causer) 182 if !ok { 183 break 184 } 185 err = c.Cause() 186 } 187 188 return "" 189 } 190 191 // ErrorDataer has an ErrorData() method. 192 type ErrorDataer interface { 193 ErrorData() interface{} 194 } 195 196 // ErrorData unwraps the error provided and returns the result of calling ErrorData() on the first error that has implements it. 197 // Errors are unwrapped by calling Cause(). 198 func ErrorData(err error) interface{} { 199 200 for err != nil { 201 ec, ok := err.(ErrorDataer) 202 if ok { 203 return ec.ErrorData() 204 } 205 c, ok := err.(Causer) 206 if !ok { 207 break 208 } 209 err = c.Cause() 210 } 211 212 return nil 213 } 214 215 // ErrorHeaderser is the interface for ErrorHeaders. 216 // The name is so bad it's good. A bit like Plan 9 from Outer Space. 217 type ErrorHeaderser interface { 218 ErrorHeaders() http.Header 219 } 220 221 // ErrorHeaders unwraps the error provided and returns a composite of all of the headers found during the 222 // unwrapping, with higher level values overwritting lower level ones. 223 func ErrorHeaders(err error) http.Header { 224 225 var ret http.Header 226 addRet := func(h http.Header) { 227 if len(h) == 0 { 228 return 229 } 230 if ret == nil { 231 ret = make(http.Header) 232 } 233 // write each key that doesn't exist in the return 234 for k, v := range h { 235 if _, ok := ret[k]; !ok { 236 ret[k] = v 237 } 238 } 239 } 240 241 for err != nil { 242 eh, ok := err.(ErrorHeaderser) 243 if ok { 244 addRet(eh.ErrorHeaders()) // use whatever headers are there 245 } 246 c, ok := err.(Causer) 247 if !ok { 248 break 249 } 250 err = c.Cause() 251 } 252 253 return ret 254 } 255 256 // RootCause calls Cause() recursively until it finds an error that doesn't implement it and returns that. 257 // If Cause() returns nil then that will be returned. 258 func RootCause(err error) error { 259 for err != nil { 260 c, ok := err.(Causer) 261 if !ok { 262 break 263 } 264 err = c.Cause() 265 if err == c.(error) { // avoid infinite loop 266 break 267 } 268 } 269 return err 270 } 271 272 // Causer allows you to get the underlying error that caused this one, if available. 273 // (It incidentally is compatibile with github.com/pkg/errors). It is recommended 274 // that only errors which have a cause implement this interface. 275 type Causer interface { 276 Cause() error 277 } 278 279 type errPrefix struct { 280 cause error 281 prefix string 282 } 283 284 // Cause returns the underlying error. 285 func (ep *errPrefix) Cause() error { 286 return ep.cause 287 } 288 289 // Error returns the cause error prefixed by the strinct provided 290 func (ep *errPrefix) Error() string { 291 return ep.prefix + ep.cause.Error() 292 } 293 294 // ErrPrefix returns an error whose Error() method return will have the specified prefix. 295 // The error provided must not be nil and is set as the cause for the error returned. 296 func ErrPrefix(prefix string, err error) error { 297 return &errPrefix{ 298 prefix: prefix, 299 cause: err, 300 } 301 } 302 303 type errLoc struct { 304 cause error 305 location string 306 } 307 308 // Cause returns the underlying error. 309 func (el *errLoc) Cause() error { 310 return el.cause 311 } 312 313 // Error returns the cause error prefixed by location information (file and line number). 314 func (el *errLoc) Error() string { 315 return el.location + ": " + el.cause.Error() 316 } 317 318 // ErrLoc wraps an error so it's Error() method will return the same text prefixed 319 // with the file and line number it was called from. A nil error value will return nil. 320 // The returned value also has a Cause() method which will return the underlying error. 321 func ErrLoc(err error) error { 322 323 if err == nil { 324 return nil 325 } 326 327 _, file, line, _ := runtime.Caller(1) 328 return &errLoc{ 329 cause: err, 330 location: fmt.Sprintf("%s:%v", file, line), 331 } 332 } 333 334 var rnd = rand.New(rand.NewSource(time.Now().Unix())) 335 336 func errID() string { 337 return fmt.Sprintf("%020d", rnd.Uint64()) 338 } 339 340 func setErrID(h http.Header) http.Header { 341 if h == nil { 342 h = make(http.Header) 343 } 344 if h.Get("X-Id") == "" { 345 h.Set("X-Id", errID()) 346 } 347 return h 348 }