-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Unwrap, Is and As are as defined in proposal Issue golang#29934. Also add Opaque for enforcing an error cannot be unwrapped. Change-Id: I4f3feaa42e3ee7477b588164ac622ba4d5e77cad Reviewed-on: https://go-review.googlesource.com/c/163558 Run-TryBot: Marcel van Lohuizen <[email protected]> Reviewed-by: Damien Neil <[email protected]>
- Loading branch information
Showing
4 changed files
with
380 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// Copyright 2018 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package errors | ||
|
||
import ( | ||
"internal/reflectlite" | ||
) | ||
|
||
// A Wrapper provides context around another error. | ||
type Wrapper interface { | ||
// Unwrap returns the next error in the error chain. | ||
// If there is no next error, Unwrap returns nil. | ||
Unwrap() error | ||
} | ||
|
||
// Opaque returns an error with the same error formatting as err | ||
// but that does not match err and cannot be unwrapped. | ||
func Opaque(err error) error { | ||
return noWrapper{err} | ||
} | ||
|
||
type noWrapper struct { | ||
error | ||
} | ||
|
||
func (e noWrapper) FormatError(p Printer) (next error) { | ||
if f, ok := e.error.(Formatter); ok { | ||
return f.FormatError(p) | ||
} | ||
p.Print(e.error) | ||
return nil | ||
} | ||
|
||
// Unwrap returns the result of calling the Unwrap method on err, if err | ||
// implements Unwrap. Otherwise, Unwrap returns nil. | ||
func Unwrap(err error) error { | ||
u, ok := err.(Wrapper) | ||
if !ok { | ||
return nil | ||
} | ||
return u.Unwrap() | ||
} | ||
|
||
// Is reports whether any error in err's chain matches target. | ||
// | ||
// 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. | ||
func Is(err, target error) bool { | ||
if target == nil { | ||
return err == target | ||
} | ||
for { | ||
if err == target { | ||
return true | ||
} | ||
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { | ||
return true | ||
} | ||
// TODO: consider supporing 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 { | ||
return false | ||
} | ||
} | ||
} | ||
|
||
// As finds the first error in err's chain that matches the type to which target | ||
// points, and if so, sets the target to its value and returns true. An error | ||
// matches a type if it is assignable to the target type, or if it has a method | ||
// As(interface{}) bool such that As(target) returns true. As will panic if | ||
// target is not a non-nil pointer to a type which implements error or is of | ||
// interface type. | ||
// | ||
// The As method should set the target to its value and return true if err | ||
// matches the type to which target points. | ||
func As(err error, target interface{}) bool { | ||
if target == nil { | ||
panic("errors: target cannot be nil") | ||
} | ||
val := reflectlite.ValueOf(target) | ||
typ := val.Type() | ||
if typ.Kind() != reflectlite.Ptr || val.IsNil() { | ||
panic("errors: target must be a non-nil pointer") | ||
} | ||
if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) { | ||
panic("errors: *target must be interface or implement error") | ||
} | ||
targetType := typ.Elem() | ||
for { | ||
if reflectlite.TypeOf(err).AssignableTo(targetType) { | ||
val.Elem().Set(reflectlite.ValueOf(err)) | ||
return true | ||
} | ||
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { | ||
return true | ||
} | ||
if err = Unwrap(err); err == nil { | ||
return false | ||
} | ||
} | ||
} | ||
|
||
var errorType = reflectlite.TypeOf((*error)(nil)).Elem() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
// Copyright 2018 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package errors_test | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestIs(t *testing.T) { | ||
err1 := errors.New("1") | ||
erra := wrapped{"wrap 2", err1} | ||
errb := wrapped{"wrap 3", erra} | ||
erro := errors.Opaque(err1) | ||
errco := wrapped{"opaque", erro} | ||
|
||
err3 := errors.New("3") | ||
|
||
poser := &poser{"either 1 or 3", func(err error) bool { | ||
return err == err1 || err == err3 | ||
}} | ||
|
||
testCases := []struct { | ||
err error | ||
target error | ||
match bool | ||
}{ | ||
{nil, nil, true}, | ||
{err1, nil, false}, | ||
{err1, err1, true}, | ||
{erra, err1, true}, | ||
{errb, err1, true}, | ||
{errco, erro, true}, | ||
{errco, err1, false}, | ||
{erro, erro, true}, | ||
{err1, err3, false}, | ||
{erra, err3, false}, | ||
{errb, err3, false}, | ||
{poser, err1, true}, | ||
{poser, err3, true}, | ||
{poser, erra, false}, | ||
{poser, errb, false}, | ||
{poser, erro, false}, | ||
{poser, errco, false}, | ||
} | ||
for _, tc := range testCases { | ||
t.Run("", func(t *testing.T) { | ||
if got := errors.Is(tc.err, tc.target); got != tc.match { | ||
t.Errorf("Is(%v, %v) = %v, want %v", tc.err, tc.target, got, tc.match) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
type poser struct { | ||
msg string | ||
f func(error) bool | ||
} | ||
|
||
func (p *poser) Error() string { return p.msg } | ||
func (p *poser) Is(err error) bool { return p.f(err) } | ||
func (p *poser) As(err interface{}) bool { | ||
switch x := err.(type) { | ||
case **poser: | ||
*x = p | ||
case *errorT: | ||
*x = errorT{} | ||
case **os.PathError: | ||
*x = &os.PathError{} | ||
default: | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
func TestAs(t *testing.T) { | ||
var errT errorT | ||
var errP *os.PathError | ||
var timeout interface{ Timeout() bool } | ||
var p *poser | ||
_, errF := os.Open("non-existing") | ||
|
||
testCases := []struct { | ||
err error | ||
target interface{} | ||
match bool | ||
}{{ | ||
wrapped{"pittied the fool", errorT{}}, | ||
&errT, | ||
true, | ||
}, { | ||
errF, | ||
&errP, | ||
true, | ||
}, { | ||
errors.Opaque(errT), | ||
&errT, | ||
false, | ||
}, { | ||
errorT{}, | ||
&errP, | ||
false, | ||
}, { | ||
wrapped{"wrapped", nil}, | ||
&errT, | ||
false, | ||
}, { | ||
&poser{"error", nil}, | ||
&errT, | ||
true, | ||
}, { | ||
&poser{"path", nil}, | ||
&errP, | ||
true, | ||
}, { | ||
&poser{"oh no", nil}, | ||
&p, | ||
true, | ||
}, { | ||
errors.New("err"), | ||
&timeout, | ||
false, | ||
}, { | ||
errF, | ||
&timeout, | ||
true, | ||
}, { | ||
wrapped{"path error", errF}, | ||
&timeout, | ||
true, | ||
}} | ||
for i, tc := range testCases { | ||
name := fmt.Sprintf("%d:As(Errorf(..., %v), %v)", i, tc.err, tc.target) | ||
t.Run(name, func(t *testing.T) { | ||
match := errors.As(tc.err, tc.target) | ||
if match != tc.match { | ||
t.Fatalf("match: got %v; want %v", match, tc.match) | ||
} | ||
if !match { | ||
return | ||
} | ||
if tc.target == nil { | ||
t.Fatalf("non-nil result after match") | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestAsValidation(t *testing.T) { | ||
var s string | ||
testCases := []interface{}{ | ||
nil, | ||
(*int)(nil), | ||
"error", | ||
&s, | ||
} | ||
err := errors.New("error") | ||
for _, tc := range testCases { | ||
t.Run(fmt.Sprintf("%T(%v)", tc, tc), func(t *testing.T) { | ||
defer func() { | ||
recover() | ||
}() | ||
if errors.As(err, tc) { | ||
t.Errorf("As(err, %T(%v)) = true, want false", tc, tc) | ||
return | ||
} | ||
t.Errorf("As(err, %T(%v)) did not panic", tc, tc) | ||
}) | ||
} | ||
} | ||
|
||
func TestUnwrap(t *testing.T) { | ||
err1 := errors.New("1") | ||
erra := wrapped{"wrap 2", err1} | ||
erro := errors.Opaque(err1) | ||
|
||
testCases := []struct { | ||
err error | ||
want error | ||
}{ | ||
{nil, nil}, | ||
{wrapped{"wrapped", nil}, nil}, | ||
{err1, nil}, | ||
{erra, err1}, | ||
{wrapped{"wrap 3", erra}, erra}, | ||
|
||
{erro, nil}, | ||
{wrapped{"opaque", erro}, erro}, | ||
} | ||
for _, tc := range testCases { | ||
if got := errors.Unwrap(tc.err); got != tc.want { | ||
t.Errorf("Unwrap(%v) = %v, want %v", tc.err, got, tc.want) | ||
} | ||
} | ||
} | ||
|
||
func TestOpaque(t *testing.T) { | ||
someError := errors.New("some error") | ||
testCases := []struct { | ||
err error | ||
next error | ||
}{ | ||
{errorT{}, nil}, | ||
{wrapped{"b", nil}, nil}, | ||
{wrapped{"c", someError}, someError}, | ||
} | ||
for _, tc := range testCases { | ||
t.Run("", func(t *testing.T) { | ||
opaque := errors.Opaque(tc.err) | ||
|
||
f, ok := opaque.(errors.Formatter) | ||
if !ok { | ||
t.Fatal("Opaque error does not implement Formatter") | ||
} | ||
var p printer | ||
next := f.FormatError(&p) | ||
if next != tc.next { | ||
t.Errorf("next was %v; want %v", next, tc.next) | ||
} | ||
if got, want := p.buf.String(), tc.err.Error(); got != want { | ||
t.Errorf("error was %q; want %q", got, want) | ||
} | ||
if got := errors.Unwrap(opaque); got != nil { | ||
t.Errorf("Unwrap returned non-nil error (%v)", got) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
type errorT struct{} | ||
|
||
func (errorT) Error() string { return "errorT" } | ||
|
||
type wrapped struct { | ||
msg string | ||
err error | ||
} | ||
|
||
func (e wrapped) Error() string { return e.msg } | ||
|
||
func (e wrapped) Unwrap() error { return e.err } | ||
|
||
func (e wrapped) FormatError(p errors.Printer) error { | ||
p.Print(e.msg) | ||
return e.err | ||
} | ||
|
||
type printer struct { | ||
errors.Printer | ||
buf bytes.Buffer | ||
} | ||
|
||
func (p *printer) Print(args ...interface{}) { fmt.Fprint(&p.buf, args...) } |
Oops, something went wrong.