errors: add Is, As, Wrapper, Unwrap, Opaque
See golang.org/s/go2design for the designs of these. Opaque is new - it provides a way to force an error not to be unwrappable. Change-Id: I3adff37acd93e4c649d0b31a81af2a15940d5f41 Reviewed-on: https://go-review.googlesource.com/c/144197 Run-TryBot: Marcel van Lohuizen <mpvl@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
This commit is contained in:
Родитель
ecff09132b
Коммит
b566bde8a3
|
@ -6,7 +6,10 @@ package errors_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/errors"
|
||||
)
|
||||
|
||||
// MyError is an error implementation that includes a time and message.
|
||||
|
@ -32,3 +35,16 @@ func Example() {
|
|||
}
|
||||
// Output: 1989-03-15 22:30:00 +0000 UTC: the file system has gone away
|
||||
}
|
||||
|
||||
func ExampleAs() {
|
||||
_, err := os.Open("non-existing")
|
||||
if err != nil {
|
||||
var pathError *os.PathError
|
||||
if errors.As(err, &pathError) {
|
||||
fmt.Println("Failed at path:", pathError.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Failed at path: non-existing
|
||||
}
|
||||
|
|
|
@ -60,6 +60,10 @@ func (e *withChain) Format(p errors.Printer) (next error) {
|
|||
return e.err
|
||||
}
|
||||
|
||||
func (e *withChain) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func fmtError(p *pp, verb rune, err error) (handled bool) {
|
||||
var (
|
||||
sep = ": " // separator before next error
|
||||
|
|
|
@ -73,6 +73,9 @@ func TestErrorFormatter(t *testing.T) {
|
|||
&wrapped{"and another\none", nil}}
|
||||
fallback = &wrapped{"fallback", os.ErrNotExist}
|
||||
oldAndNew = &wrapped{"new style", formatError("old style")}
|
||||
opaque = &wrapped{"outer",
|
||||
errors.Opaque(&wrapped{"mid",
|
||||
&wrapped{"inner", nil}})}
|
||||
)
|
||||
testCases := []struct {
|
||||
err error
|
||||
|
@ -93,21 +96,21 @@ func TestErrorFormatter(t *testing.T) {
|
|||
}, {
|
||||
err: elephant,
|
||||
fmt: "%+v",
|
||||
want: `can't adumbrate elephant
|
||||
somefile.go:123
|
||||
--- out of peanuts
|
||||
the elephant is on strike
|
||||
and the 12 monkeys
|
||||
are laughing`,
|
||||
want: "can't adumbrate elephant" +
|
||||
"\n somefile.go:123" +
|
||||
"\n--- out of peanuts" +
|
||||
"\n the elephant is on strike" +
|
||||
"\n and the 12 monkeys" +
|
||||
"\n are laughing",
|
||||
}, {
|
||||
err: transition,
|
||||
fmt: "%+v",
|
||||
want: `elephant still on strike
|
||||
somefile.go:123
|
||||
--- out of peanuts
|
||||
the elephant is on strike
|
||||
and the 12 monkeys
|
||||
are laughing`,
|
||||
want: "elephant still on strike" +
|
||||
"\n somefile.go:123" +
|
||||
"\n--- out of peanuts" +
|
||||
"\n the elephant is on strike" +
|
||||
"\n and the 12 monkeys" +
|
||||
"\n are laughing",
|
||||
}, {
|
||||
err: simple,
|
||||
fmt: "%#v",
|
||||
|
@ -124,6 +127,19 @@ func TestErrorFormatter(t *testing.T) {
|
|||
err: fallback,
|
||||
fmt: "%+v",
|
||||
want: "fallback\n somefile.go:123\n--- file does not exist",
|
||||
}, {
|
||||
err: opaque,
|
||||
fmt: "%s",
|
||||
want: "outer: mid: inner",
|
||||
}, {
|
||||
err: opaque,
|
||||
fmt: "%+v",
|
||||
want: "outer" +
|
||||
"\n somefile.go:123" +
|
||||
"\n--- mid" +
|
||||
"\n somefile.go:123" +
|
||||
"\n--- inner" +
|
||||
"\n somefile.go:123",
|
||||
}, {
|
||||
err: oldAndNew,
|
||||
fmt: "%v",
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
// 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 (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// An 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) Format(p Printer) (next error) {
|
||||
if f, ok := e.error.(Formatter); ok {
|
||||
return f.Format(p)
|
||||
}
|
||||
p.Print(e.error)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unwrap returns the next error in err's chain.
|
||||
// If there is no next error, Unwrap returns nil.
|
||||
func Unwrap(err error) error {
|
||||
u, ok := err.(Wrapper)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return u.Unwrap()
|
||||
}
|
||||
|
||||
// Is returns true if any error in err's chain is equal to target.
|
||||
func Is(err, target error) bool {
|
||||
if target == nil {
|
||||
return err == target
|
||||
}
|
||||
for {
|
||||
if err == target {
|
||||
return true
|
||||
}
|
||||
if err = Unwrap(err); err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// As finds the first error in err's chain that matches a type to which target
|
||||
// points, and if so, sets the target to its value and reports success.
|
||||
//
|
||||
// As will panic if target is nil.
|
||||
func As(err error, target interface{}) bool {
|
||||
if target == nil {
|
||||
panic("errors: target cannot be nil")
|
||||
}
|
||||
typ := reflect.TypeOf(target)
|
||||
if typ.Kind() != reflect.Ptr {
|
||||
panic("errors: target must be a pointer")
|
||||
}
|
||||
targetType := typ.Elem()
|
||||
for {
|
||||
if reflect.TypeOf(err) == targetType {
|
||||
reflect.ValueOf(target).Elem().Set(reflect.ValueOf(err))
|
||||
return true
|
||||
}
|
||||
if err = Unwrap(err); err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
// 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 (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/exp/errors"
|
||||
"golang.org/x/exp/errors/fmt"
|
||||
)
|
||||
|
||||
func TestIs(t *testing.T) {
|
||||
err1 := errors.New("1")
|
||||
erra := fmt.Errorf("wrap 2: %v", err1)
|
||||
errb := fmt.Errorf("wrap 3: %v", erra)
|
||||
erro := errors.Opaque(err1)
|
||||
errco := fmt.Errorf("opaque: %v", erro)
|
||||
|
||||
err3 := errors.New("3")
|
||||
|
||||
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},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAs(t *testing.T) {
|
||||
var errT errorT
|
||||
var errP *os.PathError
|
||||
_, errF := os.Open("non-existing")
|
||||
erro := errors.Opaque(errT)
|
||||
|
||||
testCases := []struct {
|
||||
err error
|
||||
target interface{}
|
||||
match bool
|
||||
}{{
|
||||
fmt.Errorf("pittied the fool: %v", errorT{}),
|
||||
&errT,
|
||||
true,
|
||||
}, {
|
||||
errF,
|
||||
&errP,
|
||||
true,
|
||||
}, {
|
||||
erro,
|
||||
&errT,
|
||||
false,
|
||||
}, {
|
||||
errT,
|
||||
&errP,
|
||||
false,
|
||||
}, {
|
||||
wrapped{nil},
|
||||
&errT,
|
||||
false,
|
||||
}}
|
||||
for _, tc := range testCases {
|
||||
name := fmt.Sprintf("As(Errorf(..., %v), %v)", 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 TestUnwrap(t *testing.T) {
|
||||
err1 := errors.New("1")
|
||||
erra := fmt.Errorf("wrap 2: %v", err1)
|
||||
erro := errors.Opaque(err1)
|
||||
|
||||
testCases := []struct {
|
||||
err error
|
||||
want error
|
||||
}{
|
||||
{nil, nil},
|
||||
{wrapped{nil}, nil},
|
||||
{err1, nil},
|
||||
{erra, err1},
|
||||
{fmt.Errorf("wrap 3: %v", erra), erra},
|
||||
|
||||
{erro, nil},
|
||||
{fmt.Errorf("opaque: %v", 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) {
|
||||
got := fmt.Errorf("foo: %+v", errors.Opaque(errorT{}))
|
||||
want := "foo: errorT"
|
||||
if got.Error() != want {
|
||||
t.Errorf("error without Format: got %v; want %v", got, want)
|
||||
}
|
||||
|
||||
got = fmt.Errorf("foo: %+v", errors.Opaque(errorD{}))
|
||||
want = "foo: errorD\n detail"
|
||||
if got.Error() != want {
|
||||
t.Errorf("error with Format: got %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type errorT struct{}
|
||||
|
||||
func (errorT) Error() string { return "errorT" }
|
||||
|
||||
type errorD struct{}
|
||||
|
||||
func (errorD) Error() string { return "errorD" }
|
||||
|
||||
func (errorD) Format(p errors.Printer) error {
|
||||
p.Print("errorD")
|
||||
p.Detail()
|
||||
p.Print("detail")
|
||||
return nil
|
||||
}
|
||||
|
||||
type wrapped struct{ error }
|
||||
|
||||
func (wrapped) Error() string { return "wrapped" }
|
||||
|
||||
func (wrapped) Unwrap() error { return nil }
|
Загрузка…
Ссылка в новой задаче