-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherror.go
More file actions
442 lines (368 loc) · 12.5 KB
/
Copy patherror.go
File metadata and controls
442 lines (368 loc) · 12.5 KB
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
package errors
import (
"errors"
"fmt"
"iter"
"maps"
"reflect"
"runtime"
"slices"
"strings"
)
// maxFrames is the number of stack frames an Error keeps, innermost first.
const maxFrames = 32
// Error is an error enriched with structured key-value fields, the causes it
// was attached, and a captured stack trace. All methods that add data return
// a new copy; the original is never mutated.
type Error struct {
err error
data map[string]any
causes []error
stack []uintptr
}
// New creates an Error from the given text and captures the current stack trace.
func New(text string) *Error {
return &Error{
err: errors.New(text),
stack: callers(1),
}
}
// Sentinel creates an Error from the given text without a stack trace. It is
// meant for package-level errors: the stack trace is captured later, where the
// sentinel is first derived from by Wrap, Newf, WithField, WithFields or
// WithCause, so it points at the place the error was raised rather than at
// package initialisation.
func Sentinel(text string) *Error {
return &Error{
err: errors.New(text),
}
}
// Newf creates an Error from a formatted string. When the format wraps an
// *Error with %w, its fields, causes, and stack trace are inherited, so adding
// context to an error never hides what it already carries. Otherwise, or when
// the wrapped *Error has no stack trace, the current stack trace is captured.
func Newf(format string, v ...any) *Error {
return derive(fmt.Errorf(format, v...))
}
// Wrap converts an error into an *Error. If err is nil, Wrap returns nil. If
// err is already an *Error it is returned unchanged, or as a copy with the
// current stack trace when it has none. If an *Error is found by repeatedly
// unwrapping err, its fields, causes, and stack trace are inherited. Otherwise
// the current stack trace is captured.
//
// Warning: the returned *Error nil is a typed nil pointer. When assigned to
// or returned as an error interface it will not equal nil. Prefer checking the
// error before passing it to Wrap rather than checking the result afterwards.
func Wrap(err error) *Error {
if err == nil {
return nil
}
if e, ok := err.(*Error); ok {
return e.traced(1)
}
return derive(err)
}
// derive builds an Error around err, inheriting the fields, causes, and stack
// trace of its ancestor. When there is none, or it has no stack trace, the
// stack trace is captured at the caller of the exported function.
func derive(err error) *Error {
inner := ancestor(err)
if inner == nil {
return &Error{
err: err,
stack: callers(2),
}
}
derived := *inner
derived.err = err
derived.stack = inner.trace(2)
return &derived
}
// ancestor returns the first *Error reached by repeatedly unwrapping err with
// Unwrap() error. Errors wrapping several errors at once are not entered, so
// wrapping a collection never adopts the data of one of its members.
func ancestor(err error) *Error {
for err != nil {
if e, ok := err.(*Error); ok {
return e
}
wrapper, ok := err.(interface{ Unwrap() error })
if !ok {
return nil
}
err = wrapper.Unwrap()
}
return nil
}
// Error returns the error message string. A nil *Error reports "<nil>".
func (e *Error) Error() string {
if e == nil || e.err == nil {
return "<nil>"
}
return e.err.Error()
}
// Fields returns a shallow copy of the structured key-value data attached to
// this error. Adding or removing keys does not affect the error; values that
// are maps, slices or pointers remain shared.
func (e *Error) Fields() map[string]any {
if e == nil {
return nil
}
return maps.Clone(e.data)
}
// WithField returns a copy of the error with the given key-value field added.
// The original error is not modified. When the error has no stack trace the
// current one is captured. A nil *Error returns nil, so chaining after
// Wrap(nil) does not panic.
func (e *Error) WithField(key string, value any) *Error {
return e.withFields(map[string]any{key: value}, 1)
}
// WithFields returns a copy of the error with the given fields merged in.
// The original error is not modified. When the error has no stack trace the
// current one is captured, even when values is empty; otherwise an empty
// values returns the receiver as is. A nil *Error returns nil, so chaining
// after Wrap(nil) does not panic.
func (e *Error) WithFields(values map[string]any) *Error {
return e.withFields(values, 1)
}
// withFields implements WithField and WithFields, capturing a missing stack
// trace skip frames above the caller.
func (e *Error) withFields(values map[string]any, skip int) *Error {
if e == nil {
return nil
}
if len(values) == 0 {
return e.traced(skip + 1)
}
data := make(map[string]any, len(e.data)+len(values))
maps.Copy(data, e.data)
maps.Copy(data, values)
derived := *e
derived.data = data
derived.stack = e.trace(skip + 1)
return &derived
}
// WithCause returns a copy of the error with the given cause attached after
// the causes it already carries, so adding a cause never hides another one.
// Every cause is returned by Unwrap, making it visible to errors.Is and
// errors.As. A nil cause is ignored. When the error has no stack trace the
// current one is captured. A nil *Error returns nil, so chaining after
// Wrap(nil) does not panic.
func (e *Error) WithCause(err error) *Error {
if e == nil {
return nil
}
if err == nil {
return e.traced(1)
}
derived := *e
derived.causes = append(slices.Clip(e.causes), err)
derived.stack = e.trace(1)
return &derived
}
// Causes returns a copy of the causes attached with WithCause, in the order
// they were attached. A nil *Error or one without causes returns nil.
func (e *Error) Causes() []error {
if e == nil {
return nil
}
return slices.Clone(e.causes)
}
// Unwrap returns the underlying error created by New, Sentinel, Newf or Wrap, followed
// by the causes attached with WithCause. All stay visible to errors.Is and
// errors.As, so attaching a cause never hides the wrapped error.
// A nil or zero-value *Error returns nil.
func (e *Error) Unwrap() []error {
if e == nil || e.err == nil {
return nil
}
return append([]error{e.err}, e.causes...)
}
// Is reports whether e matches target. Two *Error values match when the
// underlying error of target is found in the chain of the underlying error of
// e, and every field present in target also appears in e with the same value.
// WithField, WithFields and WithCause keep the underlying error, and Wrap and
// Newf keep it in the chain, so errors.Is finds a sentinel Error anywhere,
// optionally scoped by fields. Causes are never entered by the match, so the
// fields of e only scope errors it was derived from; a sentinel attached as a
// cause is still found by errors.Is, but only with the fields it carries
// itself. Only target itself is inspected; neither its cause nor the errors
// wrapped by it are.
//
// Field values of different types never match. Uncomparable values, including
// comparable types holding an uncomparable dynamic value, are compared with
// reflect.DeepEqual, so function fields only match when both are nil.
// Underlying errors of an uncomparable type never match, and neither does a
// zero-value Error.
func (e *Error) Is(target error) bool {
err, ok := target.(*Error)
if !ok || e == nil || err == nil {
return false
}
if !contains(e.err, err.err) {
return false
}
for k, v := range err.data {
value, ok := e.data[k]
if !ok || !equal(value, v) {
return false
}
}
return true
}
// Format implements fmt.Formatter. The message is printed like a plain string,
// so %s, %q, %x, and %v honor width, precision, and flags. %+v additionally
// prints the fields, the stack trace, and the causes, formatting the underlying
// error and every cause with %+v as well, and %#v prints the error in Go
// syntax.
func (e *Error) Format(s fmt.State, verb rune) {
format(e, s, verb)
}
// GoString implements fmt.GoStringer for debugging output.
func (e *Error) GoString() string {
if e == nil {
return "(*errors.Error)(nil)"
}
return fmt.Sprintf("&errors.Error{err:%#v, data:%#v, causes:%#v}", e.err, e.data, e.causes)
}
// StackTrace returns a copy of the program counters captured when the error
// was created, innermost call first and at most 32 of them. See [Error.Frames]
// for the resolved call frames and [Error.Truncated] to learn whether deeper
// calls were dropped.
func (e *Error) StackTrace() []uintptr {
if e == nil {
return nil
}
return slices.Clone(e.stackTrace())
}
// Truncated reports whether the stack was deeper than the 32 program counters
// kept, so the outermost calls are missing from StackTrace and Frames.
func (e *Error) Truncated() bool {
return e != nil && len(e.stack) > maxFrames
}
// Frames resolves the captured stack trace into call frames, innermost call
// first. A program counter inside an inlined call resolves to several frames,
// so there may be more frames than program counters. The sequence is empty
// when no stack was captured.
func (e *Error) Frames() iter.Seq[runtime.Frame] {
return func(yield func(runtime.Frame) bool) {
if e == nil || len(e.stack) == 0 {
return
}
frames := runtime.CallersFrames(e.stackTrace())
for {
frame, more := frames.Next()
if !yield(frame) || !more {
return
}
}
}
}
// details renders the underlying error with its details together with the
// fields, the stack trace, and the causes, as printed by the %+v verb. A
// truncated stack trace ends with an ellipsis.
func (e *Error) details() string {
if e == nil || e.err == nil {
return e.Error()
}
b := &strings.Builder{}
fmt.Fprintf(b, "%+v", e.err)
for _, k := range slices.Sorted(maps.Keys(e.data)) {
fmt.Fprintf(b, "\n\t%s=%v", k, e.data[k])
}
for frame := range e.Frames() {
fmt.Fprintf(b, "\n\t%s\n\t\t%s:%d", frame.Function, frame.File, frame.Line)
}
if e.Truncated() {
b.WriteString("\n\t...")
}
for _, cause := range e.causes {
fmt.Fprintf(b, "\ncaused by: %+v", cause)
}
return b.String()
}
// traced returns e when it carries a stack trace, or a copy with the stack
// trace captured skip frames above the caller when it has none.
func (e *Error) traced(skip int) *Error {
if e == nil || len(e.stack) > 0 {
return e
}
derived := *e
derived.stack = callers(skip + 1)
return &derived
}
// stackTrace returns the kept program counters without the extra one captured
// to detect truncation.
func (e *Error) stackTrace() []uintptr {
return e.stack[:min(len(e.stack), maxFrames)]
}
// trace returns the stack trace of e, or captures the current one skip frames
// above the caller when e has none.
func (e *Error) trace(skip int) []uintptr {
if len(e.stack) > 0 {
return e.stack
}
return callers(skip + 1)
}
// callers returns the program counters starting skip frames above the caller.
// One more than maxFrames is captured so that a truncated stack can be told
// apart from one that is exactly maxFrames deep.
func callers(skip int) []uintptr {
stack := make([]uintptr, maxFrames+1)
n := runtime.Callers(skip+2, stack)
return stack[:n]
}
// equal compares two field values without panicking on uncomparable types,
// including comparable types that hold an uncomparable dynamic value.
func equal(a, b any) bool {
if a == nil || b == nil {
return a == b
}
va, vb := reflect.ValueOf(a), reflect.ValueOf(b)
if va.Type() != vb.Type() {
return false
}
if va.Comparable() {
return a == b
}
return reflect.DeepEqual(a, b)
}
// contains reports whether target is found in the chain of err. It follows
// the chain like errors.Is, except that an *Error is entered through its
// underlying error only, never through its cause. A nil target or one holding
// an uncomparable value, including a comparable type with an uncomparable
// dynamic value, is never found, so the comparison cannot panic.
func contains(err, target error) bool {
if target == nil || !reflect.ValueOf(target).Comparable() {
return false
}
return within(err, target)
}
// within walks the chain of err looking for a target, honoring Is methods and
// both Unwrap forms, and stepping over the cause of every *Error. A typed nil
// *Error ends the chain.
func within(err, target error) bool {
if err == nil {
return false
}
if err == target {
return true
}
if e, ok := err.(*Error); ok {
return e != nil && within(e.err, target)
}
if matcher, ok := err.(interface{ Is(error) bool }); ok && matcher.Is(target) {
return true
}
switch wrapper := err.(type) {
case interface{ Unwrap() error }:
return within(wrapper.Unwrap(), target)
case interface{ Unwrap() []error }:
for _, err := range wrapper.Unwrap() {
if within(err, target) {
return true
}
}
}
return false
}