github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/errors/errors.go (about) 1 // Copyright 2018 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 // Package errors implements an error type that defines standard 6 // interpretable error codes for common error conditions. Errors also 7 // contain interpretable severities, so that error-producing 8 // operations can be retried in consistent ways. Errors returned by 9 // this package can also be chained: thus attributing one error to 10 // another. It is inspired by the error packages of both the Upspin 11 // and Reflow projects, but generalizes and simplifies these. 12 // 13 // Errors are safely serialized with Gob, and can thus retain 14 // semantics across process boundaries. 15 // 16 // TODO(marius): standardize translating AWS errors into *errors.Error. 17 package errors 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/gob" 23 "errors" 24 "fmt" 25 "os" 26 "runtime" 27 "strings" 28 "syscall" 29 30 "github.com/Schaudge/grailbase/log" 31 "v.io/v23/verror" 32 ) 33 34 func init() { 35 gob.Register(new(Error)) 36 } 37 38 // Separator defines the separation string inserted between 39 // chained errors in error messages. 40 var Separator = ":\n\t" 41 42 // Kind defines the type of error. Kinds are semantically 43 // meaningful, and may be interpreted by the receiver of an error 44 // (e.g., to determine whether an operation should be retried). 45 type Kind int 46 47 const ( 48 // Other indicates an unknown error. 49 Other Kind = iota 50 // Canceled indicates a context cancellation. 51 Canceled 52 // Timeout indicates an operation time out. 53 Timeout 54 // NotExist indicates a nonexistent resources. 55 NotExist 56 // NotAllowed indicates a permission failure. 57 NotAllowed 58 // NotSupported indicates an unsupported operation. 59 NotSupported 60 // Exists indicates that a resource already exists. 61 Exists 62 // Integrity indicates an integrity failure. 63 Integrity 64 // Unavailable indicates that a resource was unavailable. 65 Unavailable 66 // Invalid indicates that the caller supplied invalid parameters. 67 Invalid 68 // Net indicates a network error. 69 Net 70 // TooManyTries indicates a retry budget was exhausted. 71 TooManyTries 72 // Precondition indicates that a precondition was not met. 73 Precondition 74 // OOM indicates that an OOM condition was encountered. 75 OOM 76 // Remote indicates an error returned by an RPC, as distinct from errors in 77 // the machinery to execute the RPC, e.g. network issues, machine health, 78 // etc. 79 Remote 80 // ResourcesExhausted indicates that there were insufficient resources. 81 ResourcesExhausted 82 83 maxKind 84 ) 85 86 var kinds = map[Kind]string{ 87 Other: "unknown error", 88 Canceled: "operation was canceled", 89 Timeout: "operation timed out", 90 NotExist: "resource does not exist", 91 NotAllowed: "access denied", 92 NotSupported: "operation not supported", 93 Exists: "resource already exists", 94 Integrity: "integrity error", 95 Unavailable: "resource unavailable", 96 Invalid: "invalid argument", 97 Net: "network error", 98 TooManyTries: "too many tries", 99 Precondition: "precondition failed", 100 OOM: "out of memory", 101 Remote: "remote error", 102 ResourcesExhausted: "resources exhausted", 103 } 104 105 // kindStdErrs maps some Kinds to the standard library's equivalent. 106 var kindStdErrs = map[Kind]error{ 107 Canceled: context.Canceled, 108 Timeout: context.DeadlineExceeded, 109 NotExist: os.ErrNotExist, 110 NotAllowed: os.ErrPermission, 111 Exists: os.ErrExist, 112 Invalid: os.ErrInvalid, 113 } 114 115 // String returns a human-readable explanation of the error kind k. 116 func (k Kind) String() string { 117 return kinds[k] 118 } 119 120 var kindErrnos = map[Kind]syscall.Errno{ 121 Canceled: syscall.EINTR, 122 Timeout: syscall.ETIMEDOUT, 123 NotExist: syscall.ENOENT, 124 NotAllowed: syscall.EACCES, 125 // We map to ENOTSUP instead of ENOSYS, as ENOTSUP is more granular, 126 // signifying that there may be configurations of a functionality that may 127 // be supported. ENOSYS, in contrast, signals that an entire 128 // function(ality) is not supported. If we need to express the distinction 129 // in the future, we can add a new kind. 130 NotSupported: syscall.ENOTSUP, 131 Exists: syscall.EEXIST, 132 Unavailable: syscall.EAGAIN, 133 Invalid: syscall.EINVAL, 134 Net: syscall.ENETUNREACH, 135 TooManyTries: syscall.EINVAL, 136 Precondition: syscall.EAGAIN, 137 OOM: syscall.ENOMEM, 138 Remote: syscall.EREMOTE, 139 } 140 141 // Errno maps k to an equivalent Errno or returns false if there's no good match. 142 func (k Kind) Errno() (syscall.Errno, bool) { 143 errno, ok := kindErrnos[k] 144 return errno, ok 145 } 146 147 // Severity defines an Error's severity. An Error's severity determines 148 // whether an error-producing operation may be retried or not. 149 type Severity int 150 151 const ( 152 // Retriable indicates that the failing operation can be safely retried, 153 // regardless of application context. 154 Retriable Severity = -2 155 // Temporary indicates that the underlying error condition is likely 156 // temporary, and can be possibly be retried. However, such errors 157 // should be retried in an application specific context. 158 Temporary Severity = -1 159 // Unknown indicates the error's severity is unknown. This is the default 160 // severity level. 161 Unknown Severity = 0 162 // Fatal indicates that the underlying error condition is unrecoverable; 163 // retrying is unlikely to help. 164 Fatal Severity = 1 165 ) 166 167 var severities = map[Severity]string{ 168 Retriable: "retriable", 169 Temporary: "temporary", 170 Unknown: "unknown", 171 Fatal: "fatal", 172 } 173 174 // String returns a human-readable explanation of the error severity s. 175 func (s Severity) String() string { 176 return severities[s] 177 } 178 179 // Error is the standard error type, carrying a kind (error code), 180 // message (error message), and potentially an underlying error. 181 // Errors should be constructed by errors.E, which interprets 182 // arguments according to a set of rules. 183 // 184 // Errors may be serialized and deserialized with gob. When this is 185 // done, underlying errors do not survive in full fidelity: they are 186 // converted to their error strings and returned as opaque errors. 187 type Error struct { 188 // Kind is the error's type. 189 Kind Kind 190 // Severity is an optional severity. 191 Severity Severity 192 // Message is an optional error message associated with this error. 193 Message string 194 // Err is the error that caused this error, if any. 195 // Errors can form chains through Err: the full chain is printed 196 // by Error(). 197 Err error 198 } 199 200 // E constructs a new errors from the provided arguments. It is meant 201 // as a convenient way to construct, annotate, and wrap errors. 202 // 203 // Arguments are interpreted according to their types: 204 // 205 // - Kind: sets the Error's kind 206 // - Severity: set the Error's severity 207 // - string: sets the Error's message; multiple strings are 208 // separated by a single space 209 // - *Error: copies the error and sets the error's cause 210 // - error: sets the Error's cause 211 // 212 // If an unrecognized argument type is encountered, an error with 213 // kind Invalid is returned. 214 // 215 // If a kind is not provided, but an underlying error is, E attempts to 216 // interpret the underlying error according to a set of conventions, 217 // in order: 218 // 219 // - If the os.IsNotExist(error) returns true, its kind is set to NotExist. 220 // - If the error is context.Canceled, its kind is set to Canceled. 221 // - If the error implements interface { Timeout() bool } and 222 // Timeout() returns true, then its kind is set to Timeout 223 // - If the error implements interface { Temporary() bool } and 224 // Temporary() returns true, then its severity is set to at least 225 // Temporary. 226 // 227 // If the underlying error is another *Error, and a kind is not provided, 228 // the returned error inherits that error's kind. 229 func E(args ...interface{}) error { 230 if len(args) == 0 { 231 panic("no args") 232 } 233 e := new(Error) 234 var msg strings.Builder 235 for _, arg := range args { 236 switch arg := arg.(type) { 237 case Kind: 238 e.Kind = arg 239 case Severity: 240 e.Severity = arg 241 case string: 242 if msg.Len() > 0 { 243 msg.WriteString(" ") 244 } 245 msg.WriteString(arg) 246 case *Error: 247 copy := *arg 248 if len(args) == 1 { 249 // In this case, we're not adding anything new; 250 // just return the copy. 251 return © 252 } 253 e.Err = © 254 case error: 255 e.Err = arg 256 default: 257 _, file, line, _ := runtime.Caller(1) 258 log.Error.Printf("errors.E: bad call (type %T) from %s:%d: %v", arg, file, line, arg) 259 return &Error{ 260 Kind: Invalid, 261 Message: fmt.Sprintf("unknown type %T, value %v in error call", arg, arg), 262 } 263 } 264 } 265 e.Message = msg.String() 266 if e.Err == nil { 267 return e 268 } 269 switch prev := e.Err.(type) { 270 case *Error: 271 // TODO(marius): consider collapsing e.Err altogether if 272 // all we're doing is adding a Kind and/or Severity. 273 if prev.Kind == e.Kind || e.Kind == Other { 274 e.Kind = prev.Kind 275 prev.Kind = Other 276 } 277 if prev.Severity == e.Severity || e.Severity == Unknown { 278 e.Severity = prev.Severity 279 prev.Severity = Unknown 280 } 281 default: 282 // Classify common error types. 283 if err, ok := e.Err.(interface { 284 Temporary() bool 285 }); ok && err.Temporary() && e.Severity == Unknown { 286 e.Severity = Temporary 287 } 288 if e.Kind != Other { 289 break 290 } 291 // Note: Loop over kind instead of kindStdErrs for determinism. 292 for kind := Kind(0); kind < maxKind; kind++ { 293 stdErr := kindStdErrs[kind] 294 if stdErr != nil && errors.Is(e.Err, stdErr) { 295 e.Kind = kind 296 break 297 } 298 } 299 if e.Kind != Other { 300 break 301 } 302 // Interpret verror errors. 303 if err, ok := asVerrorE(e.Err); ok { 304 // TODO: Kill this workaround for chained ErrNoAccess errors. See 305 // https://github.com/vanadium/core/pull/282 . Once we upgrade 306 // verror to a version whose error.Is supports matching of chained 307 // errors, we can kill the string check. 308 // 309 // Separately, we can consider expanding to map more verror error 310 // types. 311 if errors.Is(err, verror.ErrNoAccess) || 312 strings.Contains(err.Error(), string(verror.ErrNoAccess.ID)) { 313 e.Kind = NotAllowed 314 } 315 } 316 if e.Kind != Other { 317 break 318 } 319 if isTimeoutErr(e.Err) { 320 e.Kind = Timeout 321 } 322 } 323 return e 324 } 325 326 func isTimeoutErr(err error) bool { 327 t, ok := err.(interface{ Timeout() bool }) 328 return ok && t.Timeout() 329 } 330 331 // Recover recovers any error into an *Error. If the passed-in Error is already 332 // an error, it is simply returned; otherwise it is wrapped in an error. 333 func Recover(err error) *Error { 334 if err == nil { 335 return nil 336 } 337 if err, ok := err.(*Error); ok { 338 return err 339 } 340 return E(err).(*Error) 341 } 342 343 // Error returns a human readable string describing this error. 344 // It uses the separator defined by errors.Separator. 345 func (e *Error) Error() string { 346 if e == nil { 347 return "<nil>" 348 } 349 var b bytes.Buffer 350 e.writeError(&b) 351 return b.String() 352 } 353 354 func (e *Error) writeError(b *bytes.Buffer) { 355 if e.Message != "" { 356 pad(b, ": ") 357 b.WriteString(e.Message) 358 } 359 if e.Kind != Other { 360 pad(b, ": ") 361 b.WriteString(e.Kind.String()) 362 } 363 if e.Severity != Unknown { 364 pad(b, " ") 365 b.WriteByte('(') 366 b.WriteString(e.Severity.String()) 367 b.WriteByte(')') 368 } 369 370 if e.Err == nil { 371 return 372 } 373 if err, ok := e.Err.(*Error); ok { 374 pad(b, Separator) 375 b.WriteString(err.Error()) 376 } else { 377 pad(b, ": ") 378 b.WriteString(e.Err.Error()) 379 } 380 } 381 382 // Timeout tells whether this error is a timeout error. 383 func (e *Error) Timeout() bool { 384 return e.Kind == Timeout 385 } 386 387 // Temporary tells whether this error is temporary. 388 func (e *Error) Temporary() bool { 389 return e.Severity <= Temporary 390 } 391 392 // Unwrap returns e's cause, if any, or nil. It lets the standard library's 393 // errors.Unwrap work with *Error. 394 func (e *Error) Unwrap() error { 395 return e.Err 396 } 397 398 // Is tells whether e.Kind is equivalent to err. 399 // 400 // This implements interoperability with the standard library's errors.Is: 401 // errors.Is(e, errors.Canceled) 402 // works if e.Kind corresponds (in this example, Canceled). This is useful when 403 // passing *Error to third-party libraries, for example. Users should still 404 // prefer this package's Is for their own tests because it's less prone to error 405 // (type checking disallows accidentally swapped arguments). 406 // 407 // Note: This match does not recurse into err's cause, if any; see the standard 408 // library's errors.Is for how this is used. 409 func (e *Error) Is(err error) bool { 410 if err == nil { 411 return false 412 } 413 if err == kindStdErrs[e.Kind] { 414 return true 415 } 416 if e.Kind == Timeout && isTimeoutErr(err) { 417 return true 418 } 419 return false 420 } 421 422 type gobError struct { 423 Kind Kind 424 Severity Severity 425 Message string 426 Next *gobError 427 Err string 428 } 429 430 func (ge *gobError) toError() *Error { 431 e := &Error{ 432 Kind: ge.Kind, 433 Severity: ge.Severity, 434 Message: ge.Message, 435 } 436 if ge.Next != nil { 437 e.Err = ge.Next.toError() 438 } else if ge.Err != "" { 439 e.Err = errors.New(ge.Err) 440 } 441 return e 442 } 443 444 func (e *Error) toGobError() *gobError { 445 ge := &gobError{ 446 Kind: e.Kind, 447 Severity: e.Severity, 448 Message: e.Message, 449 } 450 if e.Err == nil { 451 return ge 452 } 453 switch arg := e.Err.(type) { 454 case *Error: 455 ge.Next = arg.toGobError() 456 default: 457 ge.Err = arg.Error() 458 } 459 return ge 460 } 461 462 // GobEncode encodes the error for gob. Since underlying errors may 463 // be interfaces unknown to gob, Error's gob encoding replaces these 464 // with error strings. 465 func (e *Error) GobEncode() ([]byte, error) { 466 var b bytes.Buffer 467 err := gob.NewEncoder(&b).Encode(e.toGobError()) 468 return b.Bytes(), err 469 } 470 471 // GobDecode decodes an error encoded by GobEncode. 472 func (e *Error) GobDecode(p []byte) error { 473 var ge gobError 474 if err := gob.NewDecoder(bytes.NewBuffer(p)).Decode(&ge); err != nil { 475 return err 476 } 477 *e = *ge.toError() 478 return nil 479 } 480 481 // Is tells whether an error has a specified kind, except for the 482 // indeterminate kind Other. In the case an error has kind Other, the 483 // chain is traversed until a non-Other error is encountered. 484 // 485 // This is similar to the standard library's errors.Is(err, target). That 486 // traverses err's chain looking for one that matches target, where target may 487 // be os.ErrNotExist, etc. *Error has an explicit Kind instead of an error-typed 488 // target, but (*Error).Is defines an error -> Kind relationship to allow 489 // interoperability. 490 func Is(kind Kind, err error) bool { 491 if err == nil { 492 return false 493 } 494 return is(kind, Recover(err)) 495 } 496 497 func is(kind Kind, e *Error) bool { 498 if e.Kind != Other { 499 return e.Kind == kind 500 } 501 if e.Err != nil { 502 if e2, ok := e.Err.(*Error); ok { 503 return is(kind, e2) 504 } 505 } 506 return false 507 } 508 509 // IsTemporary tells whether the provided error is likely temporary. 510 func IsTemporary(err error) bool { 511 return Recover(err).Temporary() 512 } 513 514 // Match tells whether every nonempty field in err1 515 // matches the corresponding fields in err2. The comparison 516 // recurses on chained errors. Match is designed to aid in 517 // testing errors. 518 func Match(err1, err2 error) bool { 519 var ( 520 e1 = Recover(err1) 521 e2 = Recover(err2) 522 ) 523 if e1.Kind != Other && e1.Kind != e2.Kind { 524 return false 525 } 526 if e1.Severity != Unknown && e1.Severity != e2.Severity { 527 return false 528 } 529 if e1.Message != "" && e1.Message != e2.Message { 530 return false 531 } 532 if e1.Err != nil { 533 if e2.Err == nil { 534 return false 535 } 536 switch e1.Err.(type) { 537 case *Error: 538 return Match(e1.Err, e2.Err) 539 default: 540 return e1.Err.Error() == e2.Err.Error() 541 } 542 } 543 return true 544 } 545 546 // Visit calls the given function for every error object in the chain, including 547 // itself. Recursion stops after the function finds an error object of type 548 // other than *Error. 549 func Visit(err error, callback func(err error)) { 550 callback(err) 551 for { 552 next, ok := err.(*Error) 553 if !ok { 554 break 555 } 556 err = next.Err 557 callback(err) 558 } 559 } 560 561 // New is synonymous with errors.New, and is provided here so that 562 // users need only import one errors package. 563 func New(msg string) error { 564 return errors.New(msg) 565 } 566 567 func pad(b *bytes.Buffer, s string) { 568 if b.Len() == 0 { 569 return 570 } 571 b.WriteString(s) 572 } 573 574 func asVerrorE(err error) (verror.E, bool) { 575 switch e := err.(type) { 576 case verror.E: 577 return e, true 578 case *verror.E: 579 if e != nil { 580 return *e, true 581 } 582 } 583 return verror.E{}, false 584 }