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:
Marcel van Lohuizen 2018-10-23 18:15:43 +02:00
Родитель ecff09132b
Коммит b566bde8a3
5 изменённых файлов: 287 добавлений и 12 удалений

Просмотреть файл

@ -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",

83
errors/wrap.go Normal file
Просмотреть файл

@ -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
}
}
}

156
errors/wrap_test.go Normal file
Просмотреть файл

@ -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 }