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  }