diff options
| author | Damien Neil <dneil@google.com> | 2022-09-22 10:43:26 -0700 |
|---|---|---|
| committer | Damien Neil <dneil@google.com> | 2022-09-29 18:40:40 +0000 |
| commit | 4a0a2b33dfa3c99250efa222439f2c27d6780e4a (patch) | |
| tree | 25b1d2541b1b1244fe86d3b7ab690bff475e7ac7 /src/errors/wrap.go | |
| parent | 36f046d934c66fb6eb47d568e04665708c096ad7 (diff) | |
| download | go-4a0a2b33dfa3c99250efa222439f2c27d6780e4a.tar.xz | |
errors, fmt: add support for wrapping multiple errors
An error which implements an "Unwrap() []error" method wraps all the
non-nil errors in the returned []error.
We replace the concept of the "error chain" inspected by errors.Is
and errors.As with the "error tree". Is and As perform a pre-order,
depth-first traversal of an error's tree. As returns the first
matching result, if any.
The new errors.Join function returns an error wrapping a list of errors.
The fmt.Errorf function now supports multiple instances of the %w verb.
For #53435.
Change-Id: Ib7402e70b68e28af8f201d2b66bd8e87ccfb5283
Reviewed-on: https://go-review.googlesource.com/c/go/+/432898
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Rob Pike <r@golang.org>
Run-TryBot: Damien Neil <dneil@google.com>
Reviewed-by: Joseph Tsai <joetsai@digital-static.net>
Diffstat (limited to 'src/errors/wrap.go')
| -rw-r--r-- | src/errors/wrap.go | 57 |
1 files changed, 44 insertions, 13 deletions
diff --git a/src/errors/wrap.go b/src/errors/wrap.go index 263ae16b48..a719655b10 100644 --- a/src/errors/wrap.go +++ b/src/errors/wrap.go @@ -11,6 +11,8 @@ import ( // Unwrap returns the result of calling the Unwrap method on err, if err's // type contains an Unwrap method returning error. // Otherwise, Unwrap returns nil. +// +// Unwrap returns nil if the Unwrap method returns []error. func Unwrap(err error) error { u, ok := err.(interface { Unwrap() error @@ -21,10 +23,11 @@ func Unwrap(err error) error { return u.Unwrap() } -// Is reports whether any error in err's chain matches target. +// Is reports whether any error in err's tree matches target. // -// The chain consists of err itself followed by the sequence of errors obtained by -// repeatedly calling Unwrap. +// The tree consists of err itself, followed by the errors obtained by repeatedly +// calling Unwrap. When err wraps multiple errors, Is examines err followed by a +// depth-first traversal of its children. // // An error is considered to match a target if it is equal to that target or if // it implements a method Is(error) bool such that Is(target) returns true. @@ -50,20 +53,31 @@ func Is(err, target error) bool { if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { return true } - // TODO: consider supporting target.Is(err). This would allow - // user-definable predicates, but also may allow for coping with sloppy - // APIs, thereby making it easier to get away with them. - if err = Unwrap(err); err == nil { + switch x := err.(type) { + case interface{ Unwrap() error }: + err = x.Unwrap() + if err == nil { + return false + } + case interface{ Unwrap() []error }: + for _, err := range x.Unwrap() { + if Is(err, target) { + return true + } + } + return false + default: return false } } } -// As finds the first error in err's chain that matches target, and if one is found, sets +// As finds the first error in err's tree that matches target, and if one is found, sets // target to that error value and returns true. Otherwise, it returns false. // -// The chain consists of err itself followed by the sequence of errors obtained by -// repeatedly calling Unwrap. +// The tree consists of err itself, followed by the errors obtained by repeatedly +// calling Unwrap. When err wraps multiple errors, As examines err followed by a +// depth-first traversal of its children. // // An error matches target if the error's concrete value is assignable to the value // pointed to by target, or if the error has a method As(interface{}) bool such that @@ -76,6 +90,9 @@ func Is(err, target error) bool { // As panics if target is not a non-nil pointer to either a type that implements // error, or to any interface type. func As(err error, target any) bool { + if err == nil { + return false + } if target == nil { panic("errors: target cannot be nil") } @@ -88,7 +105,7 @@ func As(err error, target any) bool { if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) { panic("errors: *target must be interface or implement error") } - for err != nil { + for { if reflectlite.TypeOf(err).AssignableTo(targetType) { val.Elem().Set(reflectlite.ValueOf(err)) return true @@ -96,9 +113,23 @@ func As(err error, target any) bool { if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) { return true } - err = Unwrap(err) + switch x := err.(type) { + case interface{ Unwrap() error }: + err = x.Unwrap() + if err == nil { + return false + } + case interface{ Unwrap() []error }: + for _, err := range x.Unwrap() { + if As(err, target) { + return true + } + } + return false + default: + return false + } } - return false } var errorType = reflectlite.TypeOf((*error)(nil)).Elem() |
