github.com/mitranim/gg@v0.1.17/err.go (about) 1 package gg 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 r "reflect" 8 ) 9 10 const ( 11 ErrInvalidInput ErrStr = `invalid input` 12 ErrNyi ErrStr = `not yet implemented` 13 ) 14 15 /* 16 More powerful alternative to standard library errors. Supports stack traces and 17 error wrapping. Provides a convenient builder API. 18 */ 19 type Err struct { 20 Msg string 21 Cause error 22 Trace *Trace // By pointer to allow `==` without panics. 23 } 24 25 // Implement `error`. 26 func (self Err) Error() string { return self.String() } 27 28 // Implement a hidden interface for compatibility with `"errors".Unwrap`. 29 func (self Err) Unwrap() error { return self.Cause } 30 31 // Implement a hidden interface for compatibility with `"errors".Is`. 32 func (self Err) Is(err error) bool { 33 val, ok := err.(Err) 34 if ok { 35 return self.Msg == val.Msg && errors.Is(self.Cause, val.Cause) 36 } 37 return errors.Is(self.Cause, err) 38 } 39 40 /* 41 Implement `Errer`. If the receiver is a zero value, returns nil. Otherwise casts 42 the receiver to an error. 43 */ 44 func (self Err) Err() error { 45 if IsZero(self) { 46 return nil 47 } 48 return self 49 } 50 51 // Implement `fmt.Stringer`. 52 func (self Err) String() string { 53 if self.Cause == nil { 54 return self.Msg 55 } 56 if self.Msg == `` { 57 return self.Cause.Error() 58 } 59 return AppenderString(self) 60 } 61 62 // Implement `AppenderTo`, appending the same representation as `.Error`. 63 func (self Err) AppendTo(inout []byte) []byte { 64 buf := Buf(inout) 65 66 if self.Cause == nil { 67 buf.AppendString(self.Msg) 68 return buf 69 } 70 71 if self.Msg == `` { 72 buf.AppendError(self.Cause) 73 return buf 74 } 75 76 buf.AppendString(self.Msg) 77 buf = errAppendInner(buf, self.Cause) 78 return buf 79 } 80 81 /* 82 Returns a text representation of the full error message with the stack trace, 83 if any. The method's name is chosen for consistency with the getter 84 `Error.prototype.stack` in JS, which behaves exactly like this method. 85 */ 86 func (self Err) Stack() string { return ToString(self.AppendStack(nil)) } 87 88 /* 89 Appends a text representation of the full error message with the stack trace, if 90 any. The representation is the same as in `.Stack`. 91 */ 92 func (self Err) AppendStack(inout []byte) []byte { 93 buf := Buf(inout) 94 cause := self.Cause 95 causeTraced := IsErrTraced(cause) 96 97 if self.Msg == `` { 98 if cause == nil { 99 return PtrGet(self.Trace).AppendIndent(buf, 0) 100 } 101 102 if !causeTraced { 103 buf.AppendError(cause) 104 buf = errAppendTraceIndent(buf, PtrGet(self.Trace)) 105 return buf 106 } 107 108 buf.Fprintf(`%+v`, cause) 109 return buf 110 } 111 112 if !causeTraced { 113 buf.AppendString(self.Msg) 114 buf = errAppendInner(buf, cause) 115 buf = errAppendTraceIndent(buf, PtrGet(self.Trace)) 116 return buf 117 } 118 119 buf.AppendString(self.Msg) 120 121 if PtrGet(self.Trace).IsNotEmpty() { 122 buf = errAppendTraceIndent(buf, PtrGet(self.Trace)) 123 if cause != nil { 124 buf.AppendNewline() 125 buf.AppendNewline() 126 buf.AppendString(`cause:`) 127 buf.AppendNewline() 128 } 129 } else if cause != nil { 130 buf.AppendString(`: `) 131 } 132 133 { 134 val, _ := cause.(interface{ AppendStack([]byte) []byte }) 135 if val != nil { 136 return val.AppendStack(buf) 137 } 138 } 139 140 buf.Fprintf(`%+v`, cause) 141 return buf 142 } 143 144 // Implement `fmt.Formatter`. 145 func (self Err) Format(out fmt.State, verb rune) { 146 if out.Flag('+') { 147 _, _ = out.Write(self.AppendStack(nil)) 148 return 149 } 150 151 if out.Flag('#') { 152 type Error Err 153 fmt.Fprintf(out, `%#v`, Error(self)) 154 return 155 } 156 157 _, _ = io.WriteString(out, self.Error()) 158 } 159 160 /* 161 Implement `StackTraced`, which allows to retrieve stack traces from nested 162 errors. 163 */ 164 func (self Err) StackTrace() []uintptr { return PtrGet(self.Trace).Prim() } 165 166 // Returns a modified version where `.Msg` is set to the input. 167 func (self Err) Msgd(val string) Err { 168 self.Msg = val 169 return self 170 } 171 172 // Returns a modified version where `.Msg` is set from `fmt.Sprintf`. 173 func (self Err) Msgf(pat string, arg ...any) Err { 174 self.Msg = fmt.Sprintf(pat, NoEscUnsafe(arg)...) 175 return self 176 } 177 178 /* 179 Returns a modified version where `.Msg` is set to a concatenation of strings 180 generated from the arguments, via `Str`. See `StringCatch` for the encoding 181 rules. 182 */ 183 func (self Err) Msgv(src ...any) Err { 184 self.Msg = Str(src...) 185 return self 186 } 187 188 // Returns a modified version with the given `.Cause`. 189 func (self Err) Caused(val error) Err { 190 self.Cause = val 191 return self 192 } 193 194 /* 195 Returns a modified version where `.Trace` is initialized idempotently if 196 `.Trace` was nil. Skips the given amount of stack frames when capturing the 197 trace, where 1 corresponds to the caller's frame. 198 */ 199 func (self Err) TracedAt(skip int) Err { 200 if self.Trace == nil { 201 self.Trace = Ptr(CaptureTrace(skip + 1)) 202 } 203 return self 204 } 205 206 /* 207 Returns a modified version where `.Trace` is initialized idempotently if neither 208 the error nor `.Cause` had a trace. Skips the given amount of stack frames when 209 capturing the trace, where 1 corresponds to the caller's frame. 210 */ 211 func (self Err) TracedOptAt(skip int) Err { 212 if self.IsTraced() { 213 return self 214 } 215 return self.TracedAt(skip + 1) 216 } 217 218 // True if either the error or its cause has a non-empty stack trace. 219 func (self Err) IsTraced() bool { 220 return PtrGet(self.Trace).IsNotEmpty() || IsErrTraced(self.Cause) 221 } 222 223 /* 224 Shortcut for combining multiple errors via `Errs.Err`. Does NOT generate a stack 225 trace or modify the errors in any way. 226 */ 227 func ErrMul(src ...error) error { return Errs(src).Err() } 228 229 /* 230 Combines multiple errors. Used by `Conc`. Caution: although this implements the 231 `error` interface, avoid casting this to `error`. Even when the slice is nil, 232 the resulting interface value would be non-nil, which is incorrect. Instead, 233 call the method `Errs.Err`, which will correctly return a nil interface value 234 when all errors are nil. 235 */ 236 type Errs []error 237 238 // Implement `error`. 239 func (self Errs) Error() string { return self.String() } 240 241 // Implement a hidden interface for compatibility with `"errors".Unwrap`. 242 func (self Errs) Unwrap() error { return self.First() } 243 244 // Implement a hidden interface for compatibility with `"errors".Is`. 245 func (self Errs) Is(err error) bool { 246 return Some(self, func(val error) bool { 247 return val != nil && errors.Is(val, err) 248 }) 249 } 250 251 // Implement a hidden interface for compatibility with `"errors".As`. 252 func (self Errs) As(out any) bool { 253 return Some(self, func(val error) bool { 254 return errors.As(val, out) 255 }) 256 } 257 258 /* 259 Returns the first error that satisfies the given test function, by calling 260 `ErrFind` on each element. Order is depth-first rather than breadth-first. 261 */ 262 func (self Errs) Find(fun func(error) bool) error { 263 if fun != nil { 264 for _, val := range self { 265 val = ErrFind(val, fun) 266 if val != nil { 267 return val 268 } 269 } 270 } 271 return nil 272 } 273 274 /* 275 Shortcut for `.Find(fun) != nil`. Returns true if at least one error satisfies 276 the given predicate function, using `ErrFind` to unwrap. 277 */ 278 func (self Errs) Some(fun func(error) bool) bool { return self.Find(fun) != nil } 279 280 // If there are any non-nil errors, panics with a stack trace. 281 func (self Errs) Try() { Try(self.Err()) } 282 283 /* 284 Implement `Errer`. If there are any non-nil errors, returns a non-nil error, 285 unwrapping if possible. Otherwise returns nil. Does NOT generate a stack trace 286 or modify the errors in any way. 287 */ 288 func (self Errs) Err() error { 289 switch self.LenNotNil() { 290 case 0: 291 return nil 292 293 case 1: 294 return self.First() 295 296 default: 297 return self 298 } 299 } 300 301 // Counts nil errors. 302 func (self Errs) LenNil() int { return Count(self, IsErrNil) } 303 304 // Counts non-nil errors. 305 func (self Errs) LenNotNil() int { return Count(self, IsErrNotNil) } 306 307 // True if there are no non-nil errors. Inverse of `.IsNotEmpty`. 308 func (self Errs) IsEmpty() bool { return self.LenNotNil() <= 0 } 309 310 // True if there are any non-nil errors. Inverse of `.IsEmpty`. 311 func (self Errs) IsNotEmpty() bool { return self.LenNotNil() > 0 } 312 313 // First non-nil error. 314 func (self Errs) First() error { return Find(self, IsErrNotNil) } 315 316 // Returns an error message. Same as `.Error`. 317 func (self Errs) String() string { 318 /** 319 TODO also implement `fmt.Formatter` with support for %+v, show stacktraces for 320 inner errors. 321 */ 322 323 switch self.LenNotNil() { 324 case 0: 325 return `` 326 327 case 1: 328 return self.First().Error() 329 330 default: 331 return ToString(self.AppendTo(nil)) 332 } 333 } 334 335 /* 336 Appends a text representation of the error or errors. The text is the same as 337 returned by `.Error`. 338 */ 339 func (self Errs) AppendTo(buf []byte) []byte { 340 switch self.LenNotNil() { 341 case 0: 342 return buf 343 344 case 1: 345 buf := Buf(buf) 346 buf.AppendError(self.First()) 347 return buf 348 349 default: 350 return self.append(buf) 351 } 352 } 353 354 func (self Errs) append(buf Buf) Buf { 355 buf.AppendString(`multiple errors`) 356 357 for _, val := range self { 358 if val == nil { 359 continue 360 } 361 buf.AppendString(`; `) 362 buf.AppendError(val) 363 } 364 365 return buf 366 } 367 368 /* 369 Implementation of `error` that wraps an arbitrary value. Useful in panic 370 recovery. Used internally by `AnyErr` and some other error-related functions. 371 */ 372 type ErrAny struct{ Val any } 373 374 // Implement `error`. 375 func (self ErrAny) Error() string { return fmt.Sprint(self.Val) } 376 377 // Implement a hidden interface in "errors". 378 func (self ErrAny) Unwrap() error { return AnyAs[error](self.Val) } 379 380 /* 381 String typedef that implements `error`. Errors of this type can be defined as 382 constants. 383 */ 384 type ErrStr string 385 386 // Implement `error`. 387 func (self ErrStr) Error() string { return string(self) } 388 389 // Implement `fmt.Stringer`. 390 func (self ErrStr) String() string { return string(self) } 391 392 // Self-explanatory. 393 func IsErrNil(val error) bool { return val == nil } 394 395 // Self-explanatory. 396 func IsErrNotNil(val error) bool { return val != nil } 397 398 /* 399 True if the error has a stack trace. Shortcut for `ErrTrace(val).IsNotEmpty()`. 400 */ 401 func IsErrTraced(val error) bool { return ErrTrace(val).IsNotEmpty() } 402 403 /* 404 Creates an error where the message is generated by passing the arguments to 405 `fmt.Sprintf`, with a stack trace. Also see `Errv`. 406 */ 407 func Errf(pat string, arg ...any) Err { return Err{}.Msgf(pat, arg...).TracedAt(1) } 408 409 /* 410 Creates an error where the message is generated by passing the arguments to 411 `Str`, with a stack trace. Suffix "v" is short for "vector", alluding to how 412 all arguments are treated equally, as opposed to "f" ("format") where the first 413 argument is a formatting pattern. 414 */ 415 func Errv(val ...any) Err { return Err{}.Msgv(val...).TracedAt(1) } 416 417 /* 418 Wraps the given error, prepending the given message and idempotently adding a 419 stack trace. The message is converted to a string via `Str(msg...)`. 420 */ 421 func Wrap(err error, msg ...any) error { 422 if err == nil { 423 return nil 424 } 425 return Err{}.Caused(err).Msgv(msg...).TracedOptAt(1) 426 } 427 428 /* 429 Wraps the given error, prepending the given message and idempotently adding a 430 stack trace. The pattern argument must be a hardcoded pattern string compatible 431 with `fmt.Sprintf` and other similar functions. If the pattern argument is an 432 expression rather than a hardcoded string, use `Wrap` instead. 433 */ 434 func Wrapf(err error, pat string, arg ...any) error { 435 if err == nil { 436 return nil 437 } 438 return Err{}.Caused(err).Msgf(pat, arg...).TracedOptAt(1) 439 } 440 441 /* 442 Idempotently converts the input to an error. If the input is nil, the output is 443 nil. If the input implements `error`, it's returned as-is. If the input does 444 not implement `error`, it's converted to `ErrStr` or wrapped with `ErrAny`. 445 Does NOT generate a stack trace or modify an underlying `error` in any way. 446 See `AnyErrTraced` for that. 447 */ 448 func AnyErr(val any) error { 449 switch val := val.(type) { 450 case nil: 451 return nil 452 case error: 453 return val 454 case string: 455 return ErrStr(val) 456 default: 457 return ErrAny{val} 458 } 459 } 460 461 // Same as `AnyTraceAt(val, 1)`. 462 func AnyTrace(val any) Trace { 463 /** 464 Note for attentive readers: 1 in the comment and 2 here is intentional. 465 It's required for the equivalence between `AnyTraceAt(val, 1)` and 466 `AnyTrace(val)` at the call site. 467 */ 468 return AnyTraceAt(val, 2) 469 } 470 471 /* 472 If the input implements `error`, tries to find its stack trace via `ErrTrace`. 473 If no trace is found, generates a new trace, skipping the given amount of 474 frames. Suitable for `any` values returned by `recover`. The given value is 475 used only as a possible trace carrier, and its other properties are ignored. 476 Also see `ErrTrace` which is similar but does not capture a new trace. 477 */ 478 func AnyTraceAt(val any, skip int) Trace { 479 out := ErrTrace(AnyAs[error](val)) 480 if out != nil { 481 return out 482 } 483 return CaptureTrace(skip + 1) 484 } 485 486 /* 487 Returns the stack trace of the given error, unwrapping it as much as necessary. 488 Uses the `StackTraced` interface to detect the trace; the interface is 489 implemented by the type `Err` provided by this library, and by trace-enabled 490 errors in "github.com/pkg/errors". Does NOT generate a new trace. Also see 491 `ErrStack` which returns a string that includes both the error message and the 492 trace's representation, and `AnyTraceAt` which is suitable for use with 493 `recover` and idempotently adds a trace if one is missing. 494 */ 495 func ErrTrace(val error) Trace { 496 for val != nil { 497 impl, _ := val.(StackTraced) 498 if impl != nil { 499 out := impl.StackTrace() 500 if out != nil { 501 return ToTrace(out) 502 } 503 } 504 val = errors.Unwrap(val) 505 } 506 return nil 507 } 508 509 /* 510 Returns a string that includes both the message and the representation of the 511 trace of the given error, if possible. If the error is nil, the output is zero. 512 Does not capture a new trace. Also see `ErrTrace` which returns the `Trace` of 513 the given error, if possible. The name of this function is consistent with the 514 method `Err.Stack`. 515 */ 516 func ErrStack(err error) string { return Err{Cause: err}.Stack() } 517 518 // Same as `ErrTracedAt(val, 1)`. 519 func ErrTraced(err error) error { 520 // See `AnyTrace` for notes on 1 vs 2. 521 return ErrTracedAt(err, 2) 522 } 523 524 // Idempotently adds a stack trace, skipping the given number of frames. 525 func ErrTracedAt(err error, skip int) error { 526 if err == nil { 527 return nil 528 } 529 if IsErrTraced(err) { 530 return err 531 } 532 return errTracedAt(err, skip+1) 533 } 534 535 // Outlined to avoid deoptimization of `ErrTracedAt` observed in benchmarks. 536 func errTracedAt(err error, skip int) error { 537 if err == nil { 538 return nil 539 } 540 val, ok := err.(Err) 541 if ok { 542 return val.TracedAt(skip + 1) 543 } 544 return Err{}.Caused(err).TracedAt(skip + 1) 545 } 546 547 // Same as `AnyErrTracedAt(val, 1)`. 548 func AnyErrTraced(val any) error { 549 // See `AnyTrace` for notes on 1 vs 2. 550 return AnyErrTracedAt(val, 2) 551 } 552 553 /* 554 Converts an arbitrary value to an error. Idempotently adds a stack trace. 555 If the input is a non-nil non-error, it's wrapped into `ErrAny`. 556 */ 557 func AnyErrTracedAt(val any, skip int) error { 558 switch val := val.(type) { 559 case nil: 560 return nil 561 case error: 562 return ErrTracedAt(val, skip+1) 563 case string: 564 return Err{Msg: val}.TracedAt(skip + 1) 565 default: 566 return Err{Cause: ErrAny{val}}.TracedAt(skip + 1) 567 } 568 } 569 570 /* 571 Similar to `AnyErrTracedAt`, but always returns a value of the concrete type 572 `Err`. If the input is nil, the output is zero. Otherwise the output is always 573 non-zero. The message is derived from the input. The stack trace is reused from 574 the input if possible, otherwise it's generated here, skipping the given amount 575 of stack frames. 576 */ 577 func AnyToErrTracedAt(val any, skip int) (_ Err) { 578 switch val := val.(type) { 579 case nil: 580 return 581 case Err: 582 return val.TracedOptAt(skip + 1) 583 case string: 584 return Err{Msg: val}.TracedAt(skip + 1) 585 case error: 586 return Err{}.Caused(val).TracedOptAt(skip + 1) 587 default: 588 return Err{Cause: ErrAny{val}}.TracedAt(skip + 1) 589 } 590 } 591 592 // If the error is nil, returns ``. Otherwise uses `.Error`. 593 func ErrString(val error) string { 594 if val != nil { 595 return val.Error() 596 } 597 return `` 598 } 599 600 /* 601 Returns an error that describes a failure to convert the given input to the 602 given output type. Used internally in various conversions. 603 */ 604 func ErrConv(src any, typ r.Type) error { 605 return Errf( 606 `unable to convert value %v of type %v to type %v`, 607 src, r.TypeOf(src), typ, 608 ) 609 } 610 611 /* 612 Returns an error that describes a failure to decode the given string into the 613 given output type. Used internally in various conversions. 614 */ 615 func ErrParse[A Text](err error, src A, typ r.Type) error { 616 return Wrapf(err, `unable to decode %q into type %v`, src, typ) 617 } 618 619 /* 620 Shortcut for flushing errors out of error containers such as `context.Context` 621 or `sql.Rows`. If the inner error is non-nil, panics, idempotently adding a 622 stack trace. Otherwise does nothing. 623 */ 624 func ErrOk[A Errer](val A) { TryErr(ErrTracedAt(val.Err(), 1)) } 625 626 /* 627 Safely compares two error values, avoiding panics due to `==` on incomparable 628 underlying types. Returns true if both errors are nil, or if the underlying 629 types are comparable and the errors are `==`, or if the errors are identical 630 via `Is`. 631 */ 632 func ErrEq(err0, err1 error) bool { 633 if err0 == nil && err1 == nil { 634 return true 635 } 636 if err0 == nil || err1 == nil { 637 return false 638 } 639 if r.TypeOf(err0).Comparable() && r.TypeOf(err1).Comparable() { 640 return err0 == err1 641 } 642 return Is(err0, err1) 643 } 644 645 /* 646 Similar to `errors.As`. Differences: 647 648 * Instead of taking a pointer and returning a boolean, this returns the 649 unwrapped error value. On success, output is non-zero. On failure, output 650 is zero. 651 * Automatically tries non-pointer and pointer versions of the given type. The 652 caller should always specify a non-pointer type. This provides nil-safety 653 for types that implement `error` on the pointer type. The caller doesn't 654 have to remember whether to use pointer or non-pointer. 655 */ 656 func ErrAs[ 657 Tar any, 658 Ptr interface { 659 *Tar 660 error 661 }, 662 ](src error) Tar { 663 var tar Tar 664 if AnyIs[error](tar) && errors.As(src, &tar) { 665 return tar 666 } 667 668 var ptr Ptr 669 if errors.As(src, &ptr) { 670 return PtrGet((*Tar)(ptr)) 671 } 672 673 return Zero[Tar]() 674 } 675 676 /* 677 Somewhat analogous to `errors.Is` and `errors.As`, but instead of comparing an 678 error to another error value or checking its type, uses a predicate function. 679 Uses `errors.Unwrap` to traverse the error chain and returns the outermost 680 error that satisfies the predicate, or nil. 681 */ 682 func ErrFind(err error, fun func(error) bool) error { 683 if fun == nil { 684 return nil 685 } 686 687 for err != nil { 688 impl, _ := err.(ErrFinder) 689 if impl != nil { 690 return impl.Find(fun) 691 } 692 693 if fun(err) { 694 return err 695 } 696 697 next := errors.Unwrap(err) 698 if ErrEq(next, err) { 699 break 700 } 701 err = next 702 } 703 704 return nil 705 } 706 707 /* 708 Shortcut that returns true if `ErrFind` is non-nil for the given error and 709 predicate function. 710 */ 711 func ErrSome(err error, fun func(error) bool) bool { 712 return ErrFind(err, fun) != nil 713 }