Skip to content

Commit 20feca1

Browse files
mpvlneild
authored andcommittedJan 29, 2019
xerrors: copied files from x/exp/errors
changed paths and removed experimental notice Also made panic test pass: the output differed for different Go versions. Change-Id: Iadd367fb253459d288c3ec7e3aaafce9af939983 Reviewed-on: https://go-review.googlesource.com/c/159777 Run-TryBot: Marcel van Lohuizen <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent faf7c54 commit 20feca1

16 files changed

+1504
-0
lines changed
 

‎adaptor.go

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors
6+
7+
import (
8+
"bytes"
9+
"fmt"
10+
"io"
11+
"reflect"
12+
"strconv"
13+
)
14+
15+
// FormatError calls the FormatError method of f with an errors.Printer
16+
// configured according to s and verb, and writes the result to s.
17+
func FormatError(f Formatter, s fmt.State, verb rune) {
18+
// Assuming this function is only called from the Format method, and given
19+
// that FormatError takes precedence over Format, it cannot be called from
20+
// any package that supports errors.Formatter. It is therefore safe to
21+
// disregard that State may be a specific printer implementation and use one
22+
// of our choice instead.
23+
24+
// limitations: does not support printing error as Go struct.
25+
26+
var (
27+
sep = " " // separator before next error
28+
p = &state{State: s}
29+
direct = true
30+
)
31+
32+
var err error = f
33+
34+
switch verb {
35+
// Note that this switch must match the preference order
36+
// for ordinary string printing (%#v before %+v, and so on).
37+
38+
case 'v':
39+
if s.Flag('#') {
40+
if stringer, ok := err.(fmt.GoStringer); ok {
41+
io.WriteString(&p.buf, stringer.GoString())
42+
goto exit
43+
}
44+
// proceed as if it were %v
45+
} else if s.Flag('+') {
46+
p.printDetail = true
47+
sep = "\n - "
48+
}
49+
case 's':
50+
case 'q', 'x', 'X':
51+
// Use an intermediate buffer in the rare cases that precision,
52+
// truncation, or one of the alternative verbs (q, x, and X) are
53+
// specified.
54+
direct = false
55+
56+
default:
57+
p.buf.WriteString("%!")
58+
p.buf.WriteRune(verb)
59+
p.buf.WriteByte('(')
60+
switch {
61+
case err != nil:
62+
p.buf.WriteString(reflect.TypeOf(f).String())
63+
default:
64+
p.buf.WriteString("<nil>")
65+
}
66+
p.buf.WriteByte(')')
67+
io.Copy(s, &p.buf)
68+
return
69+
}
70+
71+
loop:
72+
for {
73+
p.inDetail = false
74+
75+
switch v := err.(type) {
76+
case Formatter:
77+
err = v.FormatError((*printer)(p))
78+
case fmt.Formatter:
79+
v.Format(p, 'v')
80+
break loop
81+
default:
82+
io.WriteString(&p.buf, v.Error())
83+
break loop
84+
}
85+
if err == nil {
86+
break
87+
}
88+
if !p.inDetail || !p.printDetail {
89+
p.buf.WriteByte(':')
90+
}
91+
// Strip last newline of detail.
92+
if bytes.HasSuffix(p.buf.Bytes(), detailSep) {
93+
p.buf.Truncate(p.buf.Len() - len(detailSep))
94+
}
95+
p.buf.WriteString(sep)
96+
p.inDetail = false
97+
}
98+
99+
exit:
100+
width, okW := s.Width()
101+
prec, okP := s.Precision()
102+
103+
if !direct || (okW && width > 0) || okP {
104+
// Construct format string from State s.
105+
format := []byte{'%'}
106+
if s.Flag('-') {
107+
format = append(format, '-')
108+
}
109+
if s.Flag('+') {
110+
format = append(format, '+')
111+
}
112+
if s.Flag(' ') {
113+
format = append(format, ' ')
114+
}
115+
if okW {
116+
format = strconv.AppendInt(format, int64(width), 10)
117+
}
118+
if okP {
119+
format = append(format, '.')
120+
format = strconv.AppendInt(format, int64(prec), 10)
121+
}
122+
format = append(format, string(verb)...)
123+
fmt.Fprintf(s, string(format), p.buf.String())
124+
} else {
125+
io.Copy(s, &p.buf)
126+
}
127+
}
128+
129+
var detailSep = []byte("\n ")
130+
131+
// state tracks error printing state. It implements fmt.State.
132+
type state struct {
133+
fmt.State
134+
buf bytes.Buffer
135+
136+
printDetail bool
137+
inDetail bool
138+
needNewline bool
139+
}
140+
141+
func (s *state) Write(b []byte) (n int, err error) {
142+
if s.printDetail {
143+
if len(b) == 0 {
144+
return 0, nil
145+
}
146+
if s.inDetail && s.needNewline {
147+
s.needNewline = false
148+
s.buf.WriteByte(':')
149+
s.buf.Write(detailSep)
150+
if b[0] == '\n' {
151+
b = b[1:]
152+
}
153+
}
154+
k := 0
155+
for i, c := range b {
156+
if c == '\n' {
157+
s.buf.Write(b[k:i])
158+
s.buf.Write(detailSep)
159+
k = i + 1
160+
}
161+
}
162+
s.buf.Write(b[k:])
163+
s.needNewline = !s.inDetail
164+
} else if !s.inDetail {
165+
s.buf.Write(b)
166+
}
167+
return len(b), nil
168+
}
169+
170+
// printer wraps a state to implement an xerrors.Printer.
171+
type printer state
172+
173+
func (s *printer) Print(args ...interface{}) {
174+
if !s.inDetail || s.printDetail {
175+
fmt.Fprint((*state)(s), args...)
176+
}
177+
}
178+
179+
func (s *printer) Printf(format string, args ...interface{}) {
180+
if !s.inDetail || s.printDetail {
181+
fmt.Fprintf((*state)(s), format, args...)
182+
}
183+
}
184+
185+
func (s *printer) Detail() bool {
186+
s.inDetail = true
187+
return s.printDetail
188+
}

‎doc.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package xerrors implements functions to manipulate errors.
6+
//
7+
// This package supports transitioning to the Go 2 proposal for error values:
8+
// https://golang.org/design/29934-error-values
9+
//
10+
// Most of the functions and types in this package will be incorporated into the
11+
// standard library's errors package in Go 1.13; the behavior of this package's
12+
// Errorf function will be incorporated into the standard library's fmt.Errorf.
13+
// Use this package to get equivalent behavior in all supported Go versions. For
14+
// example, create errors using
15+
//
16+
// xerrors.New("write failed")
17+
//
18+
// or
19+
//
20+
// xerrors.Errorf("while reading: %v", err)
21+
//
22+
// If you want your error type to participate in the new formatting
23+
// implementation for %v and %+v, provide it with a Format method that calls
24+
// xerrors.FormatError, as shown in the example for FormatError.
25+
package xerrors // import "golang.org/x/xerrors"

‎errors.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2011 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors
6+
7+
// errorString is a trivial implementation of error.
8+
type errorString struct {
9+
s string
10+
frame Frame
11+
}
12+
13+
// New returns an error that formats as the given text.
14+
//
15+
// The returned error contains a Frame set to the caller's location and
16+
// implements Formatter to show this information when printed with details.
17+
func New(text string) error {
18+
return &errorString{text, Caller(1)}
19+
}
20+
21+
func (e *errorString) Error() string {
22+
return e.s
23+
}
24+
25+
func (e *errorString) FormatError(p Printer) (next error) {
26+
p.Print(e.s)
27+
e.frame.Format(p)
28+
return nil
29+
}

‎errors_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2011 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors_test
6+
7+
import (
8+
"fmt"
9+
"testing"
10+
11+
"golang.org/x/xerrors"
12+
)
13+
14+
func TestNewEqual(t *testing.T) {
15+
// Different allocations should not be equal.
16+
if xerrors.New("abc") == xerrors.New("abc") {
17+
t.Errorf(`New("abc") == New("abc")`)
18+
}
19+
if xerrors.New("abc") == xerrors.New("xyz") {
20+
t.Errorf(`New("abc") == New("xyz")`)
21+
}
22+
23+
// Same allocation should be equal to itself (not crash).
24+
err := xerrors.New("jkl")
25+
if err != err {
26+
t.Errorf(`err != err`)
27+
}
28+
}
29+
30+
func TestErrorMethod(t *testing.T) {
31+
err := xerrors.New("abc")
32+
if err.Error() != "abc" {
33+
t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc")
34+
}
35+
}
36+
37+
func ExampleNew() {
38+
err := xerrors.New("emit macho dwarf: elf header corrupted")
39+
if err != nil {
40+
fmt.Print(err)
41+
}
42+
// Output: emit macho dwarf: elf header corrupted
43+
}
44+
45+
// The fmt package's Errorf function lets us use the package's formatting
46+
// features to create descriptive error messages.
47+
func ExampleNew_errorf() {
48+
const name, id = "bimmler", 17
49+
err := fmt.Errorf("user %q (id %d) not found", name, id)
50+
if err != nil {
51+
fmt.Print(err)
52+
}
53+
// Output: user "bimmler" (id 17) not found
54+
}

‎example_As_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors_test
6+
7+
import (
8+
"fmt"
9+
"os"
10+
11+
"golang.org/x/xerrors"
12+
)
13+
14+
func ExampleAs() {
15+
_, err := os.Open("non-existing")
16+
if err != nil {
17+
var pathError *os.PathError
18+
if xerrors.As(err, &pathError) {
19+
fmt.Println("Failed at path:", pathError.Path)
20+
}
21+
}
22+
23+
// Output:
24+
// Failed at path: non-existing
25+
}

‎example_FormatError_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors_test
6+
7+
import (
8+
"fmt"
9+
10+
"golang.org/x/xerrors"
11+
)
12+
13+
type MyError2 struct {
14+
Message string
15+
frame xerrors.Frame
16+
}
17+
18+
func (m *MyError2) Error() string {
19+
return m.Message
20+
}
21+
22+
func (m *MyError2) Format(f fmt.State, c rune) { // implements fmt.Formatter
23+
xerrors.FormatError(m, f, c)
24+
}
25+
26+
func (m *MyError2) FormatError(p xerrors.Printer) error { // implements xerrors.Formatter
27+
p.Print(m.Message)
28+
if p.Detail() {
29+
m.frame.Format(p)
30+
}
31+
return nil
32+
}
33+
34+
func ExampleFormatError() {
35+
err := &MyError2{Message: "oops", frame: xerrors.Caller(1)}
36+
fmt.Printf("%v\n", err)
37+
fmt.Println()
38+
fmt.Printf("%+v\n", err)
39+
}

‎example_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2012 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors_test
6+
7+
import (
8+
"fmt"
9+
"time"
10+
)
11+
12+
// MyError is an error implementation that includes a time and message.
13+
type MyError struct {
14+
When time.Time
15+
What string
16+
}
17+
18+
func (e MyError) Error() string {
19+
return fmt.Sprintf("%v: %v", e.When, e.What)
20+
}
21+
22+
func oops() error {
23+
return MyError{
24+
time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
25+
"the file system has gone away",
26+
}
27+
}
28+
29+
func Example() {
30+
if err := oops(); err != nil {
31+
fmt.Println(err)
32+
}
33+
// Output: 1989-03-15 22:30:00 +0000 UTC: the file system has gone away
34+
}

‎fmt.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors
6+
7+
import (
8+
"fmt"
9+
"strings"
10+
11+
"golang.org/x/xerrors/internal"
12+
)
13+
14+
// Errorf formats according to a format specifier and returns the string as a
15+
// value that satisfies error.
16+
//
17+
// The returned error includes the file and line number of the caller when
18+
// formatted with additional detail enabled. If the last argument is an error
19+
// the returned error's Format method will return it if the format string ends
20+
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
21+
// format string ends with ": %w", the returned error implements Wrapper
22+
// with an Unwrap method returning it.
23+
func Errorf(format string, a ...interface{}) error {
24+
err, wrap := lastError(format, a)
25+
format = formatPlusW(format)
26+
if err == nil {
27+
return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
28+
}
29+
30+
// TODO: this is not entirely correct. The error value could be
31+
// printed elsewhere in format if it mixes numbered with unnumbered
32+
// substitutions. With relatively small changes to doPrintf we can
33+
// have it optionally ignore extra arguments and pass the argument
34+
// list in its entirety.
35+
msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
36+
frame := Frame{}
37+
if internal.EnableTrace {
38+
frame = Caller(1)
39+
}
40+
if wrap {
41+
return &wrapError{msg, err, frame}
42+
}
43+
return &noWrapError{msg, err, frame}
44+
}
45+
46+
// formatPlusW is used to avoid the vet check that will barf at %w.
47+
func formatPlusW(s string) string {
48+
return s
49+
}
50+
51+
func lastError(format string, a []interface{}) (err error, wrap bool) {
52+
wrap = strings.HasSuffix(format, ": %w")
53+
if !wrap &&
54+
!strings.HasSuffix(format, ": %s") &&
55+
!strings.HasSuffix(format, ": %v") {
56+
return nil, false
57+
}
58+
59+
if len(a) == 0 {
60+
return nil, false
61+
}
62+
63+
err, ok := a[len(a)-1].(error)
64+
if !ok {
65+
return nil, false
66+
}
67+
68+
return err, wrap
69+
}
70+
71+
type noWrapError struct {
72+
msg string
73+
err error
74+
frame Frame
75+
}
76+
77+
func (e *noWrapError) Error() string {
78+
return fmt.Sprint(e)
79+
}
80+
81+
func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
82+
83+
func (e *noWrapError) FormatError(p Printer) (next error) {
84+
p.Print(e.msg)
85+
e.frame.Format(p)
86+
return e.err
87+
}
88+
89+
type wrapError struct {
90+
msg string
91+
err error
92+
frame Frame
93+
}
94+
95+
func (e *wrapError) Error() string {
96+
return fmt.Sprint(e)
97+
}
98+
99+
func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
100+
101+
func (e *wrapError) FormatError(p Printer) (next error) {
102+
p.Print(e.msg)
103+
e.frame.Format(p)
104+
return e.err
105+
}
106+
107+
func (e *wrapError) Unwrap() error {
108+
return e.err
109+
}

‎fmt_test.go

+542
Large diffs are not rendered by default.

‎format.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors
6+
7+
// A Formatter formats error messages.
8+
type Formatter interface {
9+
error
10+
11+
// FormatError prints the receiver's first error and returns the next error in
12+
// the error chain, if any.
13+
FormatError(p Printer) (next error)
14+
}
15+
16+
// A Printer formats error messages.
17+
//
18+
// The most common implementation of Printer is the one provided by package fmt
19+
// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message
20+
// typically provide their own implementations.
21+
type Printer interface {
22+
// Print appends args to the message output.
23+
Print(args ...interface{})
24+
25+
// Printf writes a formatted string.
26+
Printf(format string, args ...interface{})
27+
28+
// Detail reports whether error detail is requested.
29+
// After the first call to Detail, all text written to the Printer
30+
// is formatted as additional detail, or ignored when
31+
// detail has not been requested.
32+
// If Detail returns false, the caller can avoid printing the detail at all.
33+
Detail() bool
34+
}

‎frame.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors
6+
7+
import (
8+
"runtime"
9+
)
10+
11+
// A Frame contains part of a call stack.
12+
type Frame struct {
13+
// Make room for three PCs: the one we were asked for, what it called,
14+
// and possibly a PC for skipPleaseUseCallersFrames. See:
15+
// https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
16+
frames [3]uintptr
17+
}
18+
19+
// Caller returns a Frame that describes a frame on the caller's stack.
20+
// The argument skip is the number of frames to skip over.
21+
// Caller(0) returns the frame for the caller of Caller.
22+
func Caller(skip int) Frame {
23+
var s Frame
24+
runtime.Callers(skip+1, s.frames[:])
25+
return s
26+
}
27+
28+
// location reports the file, line, and function of a frame.
29+
//
30+
// The returned function may be "" even if file and line are not.
31+
func (f Frame) location() (function, file string, line int) {
32+
frames := runtime.CallersFrames(f.frames[:])
33+
if _, ok := frames.Next(); !ok {
34+
return "", "", 0
35+
}
36+
fr, ok := frames.Next()
37+
if !ok {
38+
return "", "", 0
39+
}
40+
return fr.Function, fr.File, fr.Line
41+
}
42+
43+
// Format prints the stack as error detail.
44+
// It should be called from an error's Format implementation
45+
// after printing any other error detail.
46+
func (f Frame) Format(p Printer) {
47+
if p.Detail() {
48+
function, file, line := f.location()
49+
if function != "" {
50+
p.Printf("%s\n ", function)
51+
}
52+
if file != "" {
53+
p.Printf("%s:%d\n", file, line)
54+
}
55+
}
56+
}

‎go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module golang.org/x/xerrors

‎internal/internal.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package internal
6+
7+
// EnableTrace indicates whether stack information should be recorded in errors.
8+
var EnableTrace = true

‎stack_test.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors_test
6+
7+
import (
8+
"bytes"
9+
"fmt"
10+
"math/big"
11+
"testing"
12+
13+
"golang.org/x/xerrors"
14+
"golang.org/x/xerrors/internal"
15+
)
16+
17+
type myType struct{}
18+
19+
func (myType) Format(s fmt.State, v rune) {
20+
s.Write(bytes.Repeat([]byte("Hi! "), 10))
21+
}
22+
23+
func BenchmarkErrorf(b *testing.B) {
24+
err := xerrors.New("foo")
25+
// pi := big.NewFloat(3.14) // Something expensive.
26+
num := big.NewInt(5)
27+
args := func(a ...interface{}) []interface{} { return a }
28+
benchCases := []struct {
29+
name string
30+
format string
31+
args []interface{}
32+
}{
33+
{"no_format", "msg: %v", args(err)},
34+
{"with_format", "failed %d times: %v", args(5, err)},
35+
{"method: mytype", "pi: %v", args("myfile.go", myType{}, err)},
36+
{"method: number", "pi: %v", args("myfile.go", num, err)},
37+
}
38+
for _, bc := range benchCases {
39+
b.Run(bc.name, func(b *testing.B) {
40+
b.Run("ExpWithTrace", func(b *testing.B) {
41+
for i := 0; i < b.N; i++ {
42+
xerrors.Errorf(bc.format, bc.args...)
43+
}
44+
})
45+
b.Run("ExpNoTrace", func(b *testing.B) {
46+
internal.EnableTrace = false
47+
defer func() { internal.EnableTrace = true }()
48+
49+
for i := 0; i < b.N; i++ {
50+
xerrors.Errorf(bc.format, bc.args...)
51+
}
52+
})
53+
b.Run("Core", func(b *testing.B) {
54+
for i := 0; i < b.N; i++ {
55+
fmt.Errorf(bc.format, bc.args...)
56+
}
57+
})
58+
})
59+
}
60+
}

‎wrap.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors
6+
7+
import (
8+
"reflect"
9+
)
10+
11+
// A Wrapper provides context around another error.
12+
type Wrapper interface {
13+
// Unwrap returns the next error in the error chain.
14+
// If there is no next error, Unwrap returns nil.
15+
Unwrap() error
16+
}
17+
18+
// Opaque returns an error with the same error formatting as err
19+
// but that does not match err and cannot be unwrapped.
20+
func Opaque(err error) error {
21+
return noWrapper{err}
22+
}
23+
24+
type noWrapper struct {
25+
error
26+
}
27+
28+
func (e noWrapper) FormatError(p Printer) (next error) {
29+
if f, ok := e.error.(Formatter); ok {
30+
return f.FormatError(p)
31+
}
32+
p.Print(e.error)
33+
return nil
34+
}
35+
36+
// Unwrap returns the result of calling the Unwrap method on err, if err implements
37+
// Unwrap. Otherwise, Unwrap returns nil.
38+
func Unwrap(err error) error {
39+
u, ok := err.(Wrapper)
40+
if !ok {
41+
return nil
42+
}
43+
return u.Unwrap()
44+
}
45+
46+
// Is reports whether any error in err's chain matches target.
47+
//
48+
// An error is considered to match a target if it is equal to that target or if
49+
// it implements a method Is(error) bool such that Is(target) returns true.
50+
func Is(err, target error) bool {
51+
if target == nil {
52+
return err == target
53+
}
54+
for {
55+
if err == target {
56+
return true
57+
}
58+
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
59+
return true
60+
}
61+
// TODO: consider supporing target.Is(err). This would allow
62+
// user-definable predicates, but also may allow for coping with sloppy
63+
// APIs, thereby making it easier to get away with them.
64+
if err = Unwrap(err); err == nil {
65+
return false
66+
}
67+
}
68+
}
69+
70+
// As finds the first error in err's chain that matches the type to which target
71+
// points, and if so, sets the target to its value and and returns true. An error
72+
// matches a type if it is of the same type, or if it has a method As(interface{}) bool
73+
// such that As(target) returns true. As will panic if target is nil or not a pointer.
74+
//
75+
// The As method should set the target to its value and return true if err
76+
// matches the type to which target points.
77+
func As(err error, target interface{}) bool {
78+
if target == nil {
79+
panic("errors: target cannot be nil")
80+
}
81+
typ := reflect.TypeOf(target)
82+
if typ.Kind() != reflect.Ptr {
83+
panic("errors: target must be a pointer")
84+
}
85+
targetType := typ.Elem()
86+
for {
87+
if reflect.TypeOf(err) == targetType {
88+
reflect.ValueOf(target).Elem().Set(reflect.ValueOf(err))
89+
return true
90+
}
91+
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
92+
return true
93+
}
94+
if err = Unwrap(err); err == nil {
95+
return false
96+
}
97+
}
98+
}

‎wrap_test.go

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package xerrors_test
6+
7+
import (
8+
"fmt"
9+
"os"
10+
"testing"
11+
12+
"golang.org/x/xerrors"
13+
)
14+
15+
func TestIs(t *testing.T) {
16+
err1 := xerrors.New("1")
17+
erra := xerrors.Errorf("wrap 2: %w", err1)
18+
errb := xerrors.Errorf("wrap 3: %w", erra)
19+
erro := xerrors.Opaque(err1)
20+
errco := xerrors.Errorf("opaque: %w", erro)
21+
err3 := xerrors.New("3")
22+
23+
poser := &poser{"either 1 or 3", func(err error) bool {
24+
return err == err1 || err == err3
25+
}}
26+
27+
testCases := []struct {
28+
err error
29+
target error
30+
match bool
31+
}{
32+
{nil, nil, true},
33+
{err1, nil, false},
34+
{err1, err1, true},
35+
{erra, err1, true},
36+
{errb, err1, true},
37+
{errco, erro, true},
38+
{errco, err1, false},
39+
{erro, erro, true},
40+
{err1, err3, false},
41+
{erra, err3, false},
42+
{errb, err3, false},
43+
{poser, err1, true},
44+
{poser, err3, true},
45+
{poser, erra, false},
46+
{poser, errb, false},
47+
{poser, erro, false},
48+
{poser, errco, false},
49+
}
50+
for _, tc := range testCases {
51+
t.Run("", func(t *testing.T) {
52+
if got := xerrors.Is(tc.err, tc.target); got != tc.match {
53+
t.Errorf("Is(%v, %v) = %v, want %v", tc.err, tc.target, got, tc.match)
54+
}
55+
})
56+
}
57+
}
58+
59+
type poser struct {
60+
msg string
61+
f func(error) bool
62+
}
63+
64+
func (p *poser) Error() string { return p.msg }
65+
func (p *poser) Is(err error) bool { return p.f(err) }
66+
func (p *poser) As(err interface{}) bool {
67+
switch x := err.(type) {
68+
case **poser:
69+
*x = p
70+
case *errorT:
71+
*x = errorT{}
72+
case **os.PathError:
73+
*x = &os.PathError{}
74+
default:
75+
return false
76+
}
77+
return true
78+
}
79+
80+
func TestAs(t *testing.T) {
81+
var errT errorT
82+
var errP *os.PathError
83+
var p *poser
84+
_, errF := os.Open("non-existing")
85+
86+
testCases := []struct {
87+
err error
88+
target interface{}
89+
match bool
90+
}{{
91+
xerrors.Errorf("pittied the fool: %w", errorT{}),
92+
&errT,
93+
true,
94+
}, {
95+
errF,
96+
&errP,
97+
true,
98+
}, {
99+
xerrors.Opaque(errT),
100+
&errT,
101+
false,
102+
}, {
103+
errorT{},
104+
&errP,
105+
false,
106+
}, {
107+
errWrap{nil},
108+
&errT,
109+
false,
110+
}, {
111+
&poser{"error", nil},
112+
&errT,
113+
true,
114+
}, {
115+
&poser{"path", nil},
116+
&errP,
117+
true,
118+
}, {
119+
&poser{"oh no", nil},
120+
&p,
121+
true,
122+
}, {
123+
&poser{"oo", nil},
124+
&errF,
125+
false,
126+
}}
127+
for _, tc := range testCases {
128+
name := fmt.Sprintf("As(Errorf(..., %v), %v)", tc.err, tc.target)
129+
t.Run(name, func(t *testing.T) {
130+
match := xerrors.As(tc.err, tc.target)
131+
if match != tc.match {
132+
t.Fatalf("match: got %v; want %v", match, tc.match)
133+
}
134+
if !match {
135+
return
136+
}
137+
if tc.target == nil {
138+
t.Fatalf("non-nil result after match")
139+
}
140+
})
141+
}
142+
}
143+
144+
func TestUnwrap(t *testing.T) {
145+
err1 := xerrors.New("1")
146+
erra := xerrors.Errorf("wrap 2: %w", err1)
147+
erro := xerrors.Opaque(err1)
148+
149+
testCases := []struct {
150+
err error
151+
want error
152+
}{
153+
{nil, nil},
154+
{errWrap{nil}, nil},
155+
{err1, nil},
156+
{erra, err1},
157+
{xerrors.Errorf("wrap 3: %w", erra), erra},
158+
159+
{erro, nil},
160+
{xerrors.Errorf("opaque: %w", erro), erro},
161+
}
162+
for _, tc := range testCases {
163+
if got := xerrors.Unwrap(tc.err); got != tc.want {
164+
t.Errorf("Unwrap(%v) = %v, want %v", tc.err, got, tc.want)
165+
}
166+
}
167+
}
168+
169+
func TestOpaque(t *testing.T) {
170+
got := fmt.Sprintf("%v", xerrors.Errorf("foo: %v", xerrors.Opaque(errorT{})))
171+
want := "foo: errorT"
172+
if got != want {
173+
t.Errorf("error without Format: got %v; want %v", got, want)
174+
}
175+
176+
got = fmt.Sprintf("%v", xerrors.Errorf("foo: %v", xerrors.Opaque(errorD{})))
177+
want = "foo: errorD"
178+
if got != want {
179+
t.Errorf("error with Format: got %v; want %v", got, want)
180+
}
181+
}
182+
183+
type errorT struct{}
184+
185+
func (errorT) Error() string { return "errorT" }
186+
187+
type errorD struct{}
188+
189+
func (errorD) Error() string { return "errorD" }
190+
191+
func (errorD) FormatError(p xerrors.Printer) error {
192+
p.Print("errorD")
193+
p.Detail()
194+
p.Print("detail")
195+
return nil
196+
}
197+
198+
type errWrap struct{ error }
199+
200+
func (errWrap) Error() string { return "wrapped" }
201+
202+
func (errWrap) Unwrap() error { return nil }

0 commit comments

Comments
 (0)
Please sign in to comment.