-
Notifications
You must be signed in to change notification settings - Fork 1
/
err.go
761 lines (656 loc) · 19.4 KB
/
err.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
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
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
package gg
import (
"errors"
"fmt"
"io"
r "reflect"
)
const (
ErrInvalidInput ErrStr = `invalid input`
ErrNyi ErrStr = `not yet implemented`
)
/*
More powerful alternative to standard library errors. Supports stack traces and
error wrapping. Provides a convenient builder API.
*/
type Err struct {
Msg string
Cause error
Trace *Trace // By pointer to allow `==` without panics.
}
// Implement `error`.
func (self Err) Error() string { return self.String() }
// Implement a hidden interface for compatibility with `"errors".Unwrap`.
func (self Err) Unwrap() error { return self.Cause }
// Implement a hidden interface for compatibility with `"errors".Is`.
func (self Err) Is(err error) bool {
val, ok := err.(Err)
if ok {
return self.Msg == val.Msg && errors.Is(self.Cause, val.Cause)
}
return errors.Is(self.Cause, err)
}
/*
Implement `Errer`. If the receiver is a zero value, returns nil. Otherwise casts
the receiver to an error.
*/
func (self Err) Err() error {
if IsZero(self) {
return nil
}
return self
}
// Implement `fmt.Stringer`.
func (self Err) String() string {
if self.Cause == nil {
return self.Msg
}
if self.Msg == `` {
return self.Cause.Error()
}
return AppenderString(self)
}
// Implement `AppenderTo`, appending the same representation as `.Error`.
func (self Err) AppendTo(inout []byte) []byte {
buf := Buf(inout)
if self.Cause == nil {
buf.AppendString(self.Msg)
return buf
}
if self.Msg == `` {
buf.AppendError(self.Cause)
return buf
}
buf.AppendString(self.Msg)
return errAppendInner(buf, self.Cause)
}
/*
Returns a text representation of the full error message with the stack trace,
if any. The method's name is chosen for consistency with the getter
`Error.prototype.stack` in JS, which behaves exactly like this method.
*/
func (self Err) Stack() string { return ToString(self.AppendStackTo(nil)) }
/*
Appends a text representation of the full error message with the stack trace, if
any. The representation is the same as in `.Stack`.
*/
func (self Err) AppendStackTo(buf []byte) []byte {
if self.Cause == nil {
return self.appendStackWithoutCause(buf)
}
return self.appendStackWithCause(buf)
}
func (self Err) appendStackWithoutCause(buf Buf) Buf {
trace := self.OwnTrace()
if self.Msg == `` {
return errAppendTraceIndent(buf, trace)
}
buf.AppendString(self.Msg)
return errAppendTraceIndentWithNewline(buf, trace)
}
func (self Err) appendStackWithCause(buf Buf) Buf {
if self.Msg == `` {
return self.appendStackWithCauseWithoutMsg(buf)
}
return self.appendStackWithCauseWithMsg(buf)
}
func (self Err) appendStackWithCauseWithoutMsg(buf Buf) Buf {
cause := self.Cause
trace := self.OwnTrace()
if trace.IsEmpty() {
buf.AppendErrorStack(cause)
return buf
}
if !IsErrTraced(cause) {
// Outer doesn't have a message and inner doesn't have a trace, so we treat
// them as complements to each other.
buf.AppendError(cause)
return errAppendTraceIndentWithNewline(buf, trace)
}
buf = errAppendTraceIndent(buf, trace)
buf.AppendNewline()
buf.AppendErrorStack(cause)
return buf
}
func (self Err) appendStackWithCauseWithMsg(buf Buf) Buf {
cause := self.Cause
trace := self.OwnTrace()
if trace.IsEmpty() {
buf.AppendString(self.Msg)
buf.AppendString(`: `)
buf.AppendErrorStack(cause)
return buf
}
if !IsErrTraced(cause) {
buf.AppendString(self.Msg)
buf = errAppendInner(buf, cause)
return errAppendTraceIndentWithNewline(buf, trace)
}
buf.AppendString(self.Msg)
buf = errAppendTraceIndentWithNewline(buf, trace)
buf.AppendNewline()
buf.AppendString(`cause: `)
buf.AppendErrorStack(cause)
return buf
}
// Implement `fmt.Formatter`.
func (self Err) Format(out fmt.State, verb rune) {
if out.Flag('+') {
_, _ = out.Write(self.AppendStackTo(nil))
return
}
if out.Flag('#') {
type Error Err
fmt.Fprintf(out, `%#v`, Error(self))
return
}
_, _ = io.WriteString(out, self.Error())
}
// Safely dereferences `.Trace`, returning nil if the pointer is nil.
func (self Err) OwnTrace() Trace { return PtrGet(self.Trace) }
/*
Implement `StackTraced`, which allows to retrieve stack traces from nested
errors.
*/
func (self Err) StackTrace() []uintptr { return self.OwnTrace().Prim() }
// Returns a modified version where `.Msg` is set to the input.
func (self Err) Msgd(val string) Err {
self.Msg = val
return self
}
// Returns a modified version where `.Msg` is set from `fmt.Sprintf`.
func (self Err) Msgf(pat string, arg ...any) Err {
self.Msg = fmt.Sprintf(pat, NoEscUnsafe(arg)...)
return self
}
/*
Returns a modified version where `.Msg` is set to a concatenation of strings
generated from the arguments, via `Str`. See `StringCatch` for the encoding
rules.
*/
func (self Err) Msgv(src ...any) Err {
self.Msg = Str(src...)
return self
}
// Returns a modified version with the given `.Cause`.
func (self Err) Caused(val error) Err {
self.Cause = val
return self
}
/*
Returns a modified version where `.Trace` is initialized idempotently if
`.Trace` was nil. Skips the given amount of stack frames when capturing the
trace, where 1 corresponds to the caller's frame.
*/
func (self Err) TracedAt(skip int) Err {
if self.Trace == nil {
self.Trace = Ptr(CaptureTrace(skip + 1))
}
return self
}
/*
Returns a modified version where `.Trace` is initialized idempotently if neither
the error nor `.Cause` had a trace. Skips the given amount of stack frames when
capturing the trace, where 1 corresponds to the caller's frame.
*/
func (self Err) TracedOptAt(skip int) Err {
if self.IsTraced() {
return self
}
return self.TracedAt(skip + 1)
}
// True if either the error or its cause has a non-empty stack trace.
func (self Err) IsTraced() bool {
return self.OwnTrace().IsNotEmpty() || IsErrTraced(self.Cause)
}
/*
Shortcut for combining multiple errors via `Errs.Err`. Does NOT generate a stack
trace or modify the errors in any way.
*/
func ErrMul(src ...error) error { return Errs(src).Err() }
/*
Combines multiple errors. Used by `Conc`. Caution: although this implements the
`error` interface, avoid casting this to `error`. Even when the slice is nil,
the resulting interface value would be non-nil, which is incorrect. Instead,
call the method `Errs.Err`, which will correctly return a nil interface value
when all errors are nil.
*/
type Errs []error
// Implement `error`.
func (self Errs) Error() string { return self.String() }
// Implement a hidden interface for compatibility with `"errors".Unwrap`.
func (self Errs) Unwrap() error { return self.First() }
// Implement a hidden interface for compatibility with `"errors".Is`.
func (self Errs) Is(err error) bool {
return Some(self, func(val error) bool {
return val != nil && errors.Is(val, err)
})
}
// Implement a hidden interface for compatibility with `"errors".As`.
func (self Errs) As(out any) bool {
return Some(self, func(val error) bool {
return errors.As(val, out)
})
}
/*
Returns the first error that satisfies the given test function, by calling
`ErrFind` on each element. Order is depth-first rather than breadth-first.
*/
func (self Errs) Find(fun func(error) bool) error {
if fun != nil {
for _, val := range self {
val = ErrFind(val, fun)
if val != nil {
return val
}
}
}
return nil
}
/*
Shortcut for `.Find(fun) != nil`. Returns true if at least one error satisfies
the given predicate function, using `ErrFind` to unwrap.
*/
func (self Errs) Some(fun func(error) bool) bool { return self.Find(fun) != nil }
// If there are any non-nil errors, panics with a stack trace.
func (self Errs) Try() { Try(self.Err()) }
/*
Implement `Errer`. If there are any non-nil errors, returns a non-nil error,
unwrapping if possible. Otherwise returns nil. Does NOT generate a stack trace
or modify the errors in any way.
*/
func (self Errs) Err() error {
switch self.LenNotNil() {
case 0:
return nil
case 1:
return self.First()
default:
return self
}
}
// Counts nil errors.
func (self Errs) LenNil() int { return Count(self, IsErrNil) }
// Counts non-nil errors.
func (self Errs) LenNotNil() int { return Count(self, IsErrNotNil) }
// True if there are no non-nil errors. Inverse of `.IsNotEmpty`.
func (self Errs) IsEmpty() bool { return self.LenNotNil() <= 0 }
// True if there are any non-nil errors. Inverse of `.IsEmpty`.
func (self Errs) IsNotEmpty() bool { return self.LenNotNil() > 0 }
// First non-nil error.
func (self Errs) First() error { return Find(self, IsErrNotNil) }
// Returns an error message. Same as `.Error`.
func (self Errs) String() string {
/**
TODO also implement `fmt.Formatter` with support for %+v, show stacktraces for
inner errors.
*/
switch self.LenNotNil() {
case 0:
return ``
case 1:
return self.First().Error()
default:
return ToString(self.AppendTo(nil))
}
}
/*
Appends a text representation of the error or errors. The text is the same as
returned by `.Error`.
*/
func (self Errs) AppendTo(buf []byte) []byte {
switch self.LenNotNil() {
case 0:
return buf
case 1:
buf := Buf(buf)
buf.AppendError(self.First())
return buf
default:
return self.append(buf)
}
}
func (self Errs) append(buf Buf) Buf {
buf.AppendString(`multiple errors`)
for _, val := range self {
if val == nil {
continue
}
buf.AppendString(`; `)
buf.AppendError(val)
}
return buf
}
/*
Adds a stack trace to each non-nil error via `WrapTracedAt`. Useful when writing
tools that internally use goroutines.
*/
func (self Errs) WrapTracedAt(skip int) {
for ind := range self {
self[ind] = WrapTracedAt(self[ind], skip)
}
}
/*
Implementation of `error` that wraps an arbitrary value. Useful in panic
recovery. Used internally by `AnyErr` and some other error-related functions.
*/
type ErrAny struct{ Val any }
// Implement `error`.
func (self ErrAny) Error() string { return fmt.Sprint(self.Val) }
// Implement a hidden interface in "errors".
func (self ErrAny) Unwrap() error { return AnyAs[error](self.Val) }
/*
String typedef that implements `error`. Errors of this type can be defined as
constants.
*/
type ErrStr string
// Implement `error`.
func (self ErrStr) Error() string { return string(self) }
// Implement `fmt.Stringer`.
func (self ErrStr) String() string { return string(self) }
// Self-explanatory.
func IsErrNil(val error) bool { return val == nil }
// Self-explanatory.
func IsErrNotNil(val error) bool { return val != nil }
/*
True if the error has a stack trace. Shortcut for `ErrTrace(val).IsNotEmpty()`.
*/
func IsErrTraced(val error) bool { return ErrTrace(val).IsNotEmpty() }
/*
Creates an error where the message is generated by passing the arguments to
`fmt.Sprintf`, with a stack trace. Also see `Errv`.
*/
func Errf(pat string, arg ...any) Err { return Err{}.Msgf(pat, arg...).TracedAt(1) }
/*
Creates an error where the message is generated by passing the arguments to
`Str`, with a stack trace. Suffix "v" is short for "vector", alluding to how
all arguments are treated equally, as opposed to "f" ("format") where the first
argument is a formatting pattern.
*/
func Errv(val ...any) Err { return Err{}.Msgv(val...).TracedAt(1) }
/*
If the error is nil, returns nil. Otherwise wraps the error, prepending the
given message and idempotently adding a stack trace. The message is converted
to a string via `Str(msg...)`.
*/
func Wrap(err error, msg ...any) error {
if err == nil {
return nil
}
return Err{}.Caused(err).Msgv(msg...).TracedOptAt(1)
}
/*
If the error is nil, returns nil. Otherwise wraps the error, prepending the
given message and idempotently adding a stack trace. The pattern argument must
be a hardcoded pattern string compatible with `fmt.Sprintf` and other similar
functions. If the pattern argument is an expression rather than a hardcoded
string, use `Wrap` instead.
*/
func Wrapf(err error, pat string, arg ...any) error {
if err == nil {
return nil
}
return Err{}.Caused(err).Msgf(pat, arg...).TracedOptAt(1)
}
/*
If the error is nil, returns nil. Otherwise wraps the error into an instance of
`Err` without a message with a new stack trace, skipping the given number of
frames. Unlike other "traced" functions, this one is NOT idempotent: it doesn't
check if the existing errors already have traces, and always adds a trace. This
is useful when writing tools that internally use goroutines, in order to
"connect" the traces between the goroutine and the caller.
*/
func WrapTracedAt(err error, skip int) error {
if err == nil {
return nil
}
return Err{Cause: err, Trace: Ptr(CaptureTrace(skip + 1))}
}
/*
Idempotently converts the input to an error. If the input is nil, the output is
nil. If the input implements `error`, it's returned as-is. If the input does
not implement `error`, it's converted to `ErrStr` or wrapped with `ErrAny`.
Does NOT generate a stack trace or modify an underlying `error` in any way.
See `AnyErrTraced` for that.
*/
func AnyErr(val any) error {
switch val := val.(type) {
case nil:
return nil
case error:
return val
case string:
return ErrStr(val)
default:
return ErrAny{val}
}
}
// Same as `AnyTraceAt(val, 1)`.
func AnyTrace(val any) Trace {
/**
Note for attentive readers: 1 in the comment and 2 here is intentional.
It's required for the equivalence between `AnyTraceAt(val, 1)` and
`AnyTrace(val)` at the call site.
*/
return AnyTraceAt(val, 2)
}
/*
If the input implements `error`, tries to find its stack trace via `ErrTrace`.
If no trace is found, generates a new trace, skipping the given amount of
frames. Suitable for `any` values returned by `recover`. The given value is
used only as a possible trace carrier, and its other properties are ignored.
Also see `ErrTrace` which is similar but does not capture a new trace.
*/
func AnyTraceAt(val any, skip int) Trace {
out := ErrTrace(AnyAs[error](val))
if out != nil {
return out
}
return CaptureTrace(skip + 1)
}
/*
Returns the stack trace of the given error, unwrapping it as much as necessary.
Uses the `StackTraced` interface to detect the trace; the interface is
implemented by the type `Err` provided by this library, and by trace-enabled
errors in "github.com/pkg/errors". Does NOT generate a new trace. Also see
`ErrStack` which returns a string that includes both the error message and the
trace's representation, and `AnyTraceAt` which is suitable for use with
`recover` and idempotently adds a trace if one is missing.
*/
func ErrTrace(val error) Trace {
for val != nil {
impl, _ := val.(StackTraced)
if impl != nil {
out := impl.StackTrace()
if out != nil {
return ToTrace(out)
}
}
val = errors.Unwrap(val)
}
return nil
}
/*
Returns a string that includes both the message and the representation of the
trace of the given error, if possible. If the error is nil, the output is zero.
Does not capture a new trace. Also see `ErrTrace` which returns the `Trace` of
the given error, if possible. The name of this function is consistent with the
method `Err.Stack`.
*/
func ErrStack(err error) string { return Err{Cause: err}.Stack() }
// Same as `ErrTracedAt(val, 1)`.
func ErrTraced(err error) error {
// See `AnyTrace` for notes on 1 vs 2.
return ErrTracedAt(err, 2)
}
// Idempotently adds a stack trace, skipping the given number of frames.
func ErrTracedAt(err error, skip int) error {
if err == nil {
return nil
}
if IsErrTraced(err) {
return err
}
return errTracedAt(err, skip+1)
}
// Outlined to avoid deoptimization of `ErrTracedAt` observed in benchmarks.
func errTracedAt(err error, skip int) error {
if err == nil {
return nil
}
val, ok := err.(Err)
if ok {
return val.TracedAt(skip + 1)
}
return Err{}.Caused(err).TracedAt(skip + 1)
}
// Same as `AnyErrTracedAt(val, 1)`.
func AnyErrTraced(val any) error {
// See `AnyTrace` for notes on 1 vs 2.
return AnyErrTracedAt(val, 2)
}
/*
Converts an arbitrary value to an error. Idempotently adds a stack trace.
If the input is a non-nil non-error, it's wrapped into `ErrAny`.
*/
func AnyErrTracedAt(val any, skip int) error {
switch val := val.(type) {
case nil:
return nil
case error:
return ErrTracedAt(val, skip+1)
case string:
return Err{Msg: val}.TracedAt(skip + 1)
default:
return Err{Cause: ErrAny{val}}.TracedAt(skip + 1)
}
}
/*
Similar to `AnyErrTracedAt`, but always returns a value of the concrete type
`Err`. If the input is nil, the output is zero. Otherwise the output is always
non-zero. The message is derived from the input. The stack trace is reused from
the input if possible, otherwise it's generated here, skipping the given amount
of stack frames.
*/
func AnyToErrTracedAt(val any, skip int) (_ Err) {
switch val := val.(type) {
case nil:
return
case Err:
return val.TracedOptAt(skip + 1)
case string:
return Err{Msg: val}.TracedAt(skip + 1)
case error:
return Err{}.Caused(val).TracedOptAt(skip + 1)
default:
return Err{Cause: ErrAny{val}}.TracedAt(skip + 1)
}
}
// If the error is nil, returns ``. Otherwise uses `.Error`.
func ErrString(val error) string {
if val != nil {
return val.Error()
}
return ``
}
/*
Returns an error that describes a failure to convert the given input to the
given output type. Used internally in various conversions.
*/
func ErrConv(src any, typ r.Type) error {
return Errf(
`unable to convert value %v of type %v to type %v`,
src, r.TypeOf(src), typ,
)
}
/*
Returns an error that describes a failure to decode the given string into the
given output type. Used internally in various conversions.
*/
func ErrParse[A Text](err error, src A, typ r.Type) error {
return Wrapf(err, `unable to decode %q into %v`, src, typ)
}
/*
Shortcut for flushing errors out of error containers such as `context.Context`
or `sql.Rows`. If the inner error is non-nil, panics, idempotently adding a
stack trace. Otherwise does nothing.
*/
func ErrOk[A Errer](val A) { TryErr(ErrTracedAt(val.Err(), 1)) }
/*
Safely compares two error values, avoiding panics due to `==` on incomparable
underlying types. Returns true if both errors are nil, or if the underlying
types are comparable and the errors are `==`, or if the errors are identical
via `Is`.
*/
func ErrEq(err0, err1 error) bool {
if err0 == nil && err1 == nil {
return true
}
if err0 == nil || err1 == nil {
return false
}
if r.TypeOf(err0).Comparable() && r.TypeOf(err1).Comparable() {
return err0 == err1
}
return Is(err0, err1)
}
/*
Similar to `errors.As`. Differences:
* Instead of taking a pointer and returning a boolean, this returns the
unwrapped error value. On success, output is non-zero. On failure, output
is zero.
* Automatically tries non-pointer and pointer versions of the given type. The
caller should always specify a non-pointer type. This provides nil-safety
for types that implement `error` on the pointer type. The caller doesn't
have to remember whether to use pointer or non-pointer.
*/
func ErrAs[
Tar any,
Ptr interface {
*Tar
error
},
](src error) Tar {
var tar Tar
if AnyIs[error](tar) && errors.As(src, &tar) {
return tar
}
var ptr Ptr
if errors.As(src, &ptr) {
return PtrGet((*Tar)(ptr))
}
return Zero[Tar]()
}
/*
Somewhat analogous to `errors.Is` and `errors.As`, but instead of comparing an
error to another error value or checking its type, uses a predicate function.
Uses `errors.Unwrap` to traverse the error chain and returns the outermost
error that satisfies the predicate, or nil.
*/
func ErrFind(err error, fun func(error) bool) error {
if fun == nil {
return nil
}
for err != nil {
impl, _ := err.(ErrFinder)
if impl != nil {
return impl.Find(fun)
}
if fun(err) {
return err
}
next := errors.Unwrap(err)
if ErrEq(next, err) {
break
}
err = next
}
return nil
}
/*
Shortcut that returns true if `ErrFind` is non-nil for the given error and
predicate function.
*/
func ErrSome(err error, fun func(error) bool) bool {
return ErrFind(err, fun) != nil
}