-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy patherrs.go
298 lines (255 loc) · 7.42 KB
/
errs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// Package errs provides a simple error package with stack traces.
package errs
import (
"fmt"
"io"
"runtime"
)
// Namer is implemented by all errors returned in this package. It returns a
// name for the class of error it is, and a boolean indicating if the name is
// valid.
type Namer interface{ Name() (string, bool) }
// Causer is implemented by all errors returned in this package. It returns
// the underlying cause of the error, or nil if there is no underlying cause.
//
// Deprecated: check for the 'Unwrap()' interface from the stdlib errors package
// instead.
type Causer interface{ Cause() error }
// New returns an error not contained in any class. This is the same as calling
// fmt.Errorf(...) except it captures a stack trace on creation.
func New(format string, args ...interface{}) error {
return (*Class).create(nil, 3, fmt.Errorf(format, args...))
}
// Wrap returns an error not contained in any class. It just associates a stack
// trace with the error. Wrap returns nil if err is nil.
func Wrap(err error) error {
return (*Class).create(nil, 3, err)
}
// WrapP stores into the error pointer if it contains a non-nil error an error not
// contained in any class. It just associates a stack trace with the error. WrapP
// does nothing if the pointer or pointed at error is nil.
func WrapP(err *error) {
if err != nil && *err != nil {
*err = (*Class).create(nil, 3, *err)
}
}
// Often, we call Unwrap as much as possible. Since comparing arbitrary
// interfaces with equality isn't panic safe, we only loop up to 100
// times to ensure that a poor implementation that causes a cycle does
// not run forever.
const maxUnwrap = 100
// Unwrap returns the final, most underlying error, if any, or just the error.
//
// Deprecated: Prefer errors.Is() and errors.As().
func Unwrap(err error) error {
for i := 0; err != nil && i < maxUnwrap; i++ {
var nerr error
switch e := err.(type) {
case Causer:
nerr = e.Cause()
case interface{ Unwrap() error }:
nerr = e.Unwrap()
case interface{ Ungroup() []error }:
// consider the first error to be the "main" error.
errs := e.Ungroup()
if len(errs) > 0 {
nerr = errs[0]
}
case interface{ Unwrap() []error }:
// consider the first error to be the "main" error.
errs := e.Unwrap()
if len(errs) > 0 {
nerr = errs[0]
}
}
if nerr == nil {
return err
}
err = nerr
}
return err
}
// Classes returns all the classes that have wrapped the error.
func Classes(err error) (classes []*Class) {
IsFunc(err, func(err error) bool {
if e, ok := err.(*errorT); ok {
classes = append(classes, e.class)
}
return false
})
return classes
}
// IsFunc checks if any of the underlying errors matches the func
func IsFunc(err error, is func(err error) bool) bool {
for {
if is(err) {
return true
}
switch u := err.(type) {
case interface{ Unwrap() error }:
err = u.Unwrap()
case Causer:
err = u.Cause()
case interface{ Ungroup() []error }:
for _, err := range u.Ungroup() {
if IsFunc(err, is) {
return true
}
}
return false
case interface{ Unwrap() []error }:
for _, err := range u.Unwrap() {
if IsFunc(err, is) {
return true
}
}
return false
default:
return false
}
}
}
//
// error classes
//
// Class represents a class of errors. You can construct errors, and check if
// errors are part of the class.
type Class string
// Has returns true if the passed in error (or any error wrapped by it) has
// this class.
func (c *Class) Has(err error) bool {
return IsFunc(err, func(err error) bool {
errt, ok := err.(*errorT)
return ok && errt.class == c
})
}
// New constructs an error with the format string that will be contained by
// this class. This is the same as calling Wrap(fmt.Errorf(...)).
func (c *Class) New(format string, args ...interface{}) error {
return c.create(3, fmt.Errorf(format, args...))
}
// Wrap returns a new error based on the passed in error that is contained in
// this class. Wrap returns nil if err is nil.
func (c *Class) Wrap(err error) error {
return c.create(3, err)
}
// WrapP stores into the error pointer if it contains a non-nil error an error contained
// in this class. WrapP does nothing if the pointer or pointed at error is nil.
func (c *Class) WrapP(err *error) {
if err != nil && *err != nil {
*err = c.create(3, *err)
}
}
// Instance creates a class membership object which implements the error
// interface and allows errors.Is() to check whether given errors are
// (or contain) an instance of this class.
//
// This makes possible a construct like the following:
//
// if errors.Is(err, MyClass.Instance()) {
// fmt.Printf("err is an instance of MyClass")
// }
//
// ..without requiring the Class type to implement the error interface itself,
// as that would open the door to sundry misunderstandings and misusage.
func (c *Class) Instance() error {
return (*classMembershipChecker)(c)
}
// create constructs the error, or just adds the class to the error, keeping
// track of the stack if it needs to construct it.
func (c *Class) create(depth int, err error) error {
if err == nil {
return nil
}
var pcs []uintptr
if err, ok := err.(*errorT); ok {
if c == nil || err.class == c {
return err
}
pcs = err.pcs
}
errt := &errorT{
class: c,
err: err,
pcs: pcs,
}
if errt.pcs == nil {
errt.pcs = make([]uintptr, 64)
n := runtime.Callers(depth, errt.pcs)
errt.pcs = errt.pcs[:n:n]
}
return errt
}
type classMembershipChecker Class
func (cmc *classMembershipChecker) Error() string {
panic("classMembershipChecker used as concrete error! don't do that")
}
//
// errors
//
// errorT is the type of errors returned from this package.
type errorT struct {
class *Class
err error
pcs []uintptr
}
var ( // ensure *errorT implements the helper interfaces.
_ Namer = (*errorT)(nil)
_ Causer = (*errorT)(nil)
_ error = (*errorT)(nil)
)
// Stack returns the pcs for the stack trace associated with the error.
func (e *errorT) Stack() []uintptr { return e.pcs }
// errorT implements the error interface.
func (e *errorT) Error() string {
return fmt.Sprintf("%v", e)
}
// Format handles the formatting of the error. Using a "+" on the format string
// specifier will also write the stack trace.
func (e *errorT) Format(f fmt.State, c rune) {
sep := ""
if e.class != nil && *e.class != "" {
fmt.Fprintf(f, "%s", string(*e.class))
sep = ": "
}
if text := e.err.Error(); len(text) > 0 {
fmt.Fprintf(f, "%s%v", sep, text)
}
if f.Flag(int('+')) {
summarizeStack(f, e.pcs)
}
}
// Cause implements the interface wrapping errors were previously
// expected to implement to allow getting at underlying causes.
func (e *errorT) Cause() error {
return e.err
}
// Unwrap returns the immediate underlying error.
func (e *errorT) Unwrap() error {
return e.err
}
// Name returns the name for the error, which is the first wrapping class.
func (e *errorT) Name() (string, bool) {
if e.class == nil {
return "", false
}
return string(*e.class), true
}
// Is determines whether an error is an instance of the given error class.
//
// Use with (*Class).Instance().
func (e *errorT) Is(err error) bool {
cmc, ok := err.(*classMembershipChecker)
return ok && e.class == (*Class)(cmc)
}
// summarizeStack writes stack line entries to the writer.
func summarizeStack(w io.Writer, pcs []uintptr) {
frames := runtime.CallersFrames(pcs)
for {
frame, more := frames.Next()
if !more {
return
}
fmt.Fprintf(w, "\n\t%s:%d", frame.Function, frame.Line)
}
}