github.com/mitranim/gg@v0.1.17/try.go (about)

     1  package gg
     2  
     3  import "os"
     4  
     5  /*
     6  If the error is nil, returns void. If the error is non-nil, panics with that
     7  error, idempotently adding a stack trace.
     8  */
     9  func Try(err error) {
    10  	if err != nil {
    11  		panic(ErrTracedAt(err, 1))
    12  	}
    13  }
    14  
    15  /*
    16  If the error is nil, returns void. If the error is non-nil, panics with that
    17  error, idempotently adding a stack trace and skipping the given number of stack
    18  frames.
    19  */
    20  func TryAt(err error, skip int) {
    21  	if err != nil {
    22  		panic(ErrTracedAt(err, skip+1))
    23  	}
    24  }
    25  
    26  /*
    27  If the error is nil, returns void. If the error is non-nil, panics with that
    28  exact error. Unlike `Try` and other similar functions, this function does NOT
    29  generate a stack trace or modify the error in any way. Most of the time, you
    30  should prefer `Try` and other similar functions. This function is intended
    31  for "internal" library code, for example to avoid the stack trace check when
    32  the trace is guaranteed, or to avoid generating the trace when the error is
    33  meant to be caught before being returned to the caller.
    34  */
    35  func TryErr(err error) {
    36  	if err != nil {
    37  		panic(err)
    38  	}
    39  }
    40  
    41  /*
    42  Like `Try`, but for multiple errors. Uses `Errs.Err` to combine the errors.
    43  If the resulting error is nil, returns void. If the resulting error is non-nil,
    44  panics with that error, idempotently adding a stack trace.
    45  */
    46  func TryMul(src ...error) {
    47  	err := ErrMul(src...)
    48  	if err != nil {
    49  		panic(ErrTracedAt(err, 1))
    50  	}
    51  }
    52  
    53  /*
    54  If the error is nil, returns the given value. If the error is non-nil, panics
    55  with that error, idempotently adding a stack trace.
    56  */
    57  func Try1[A any](val A, err error) A {
    58  	if err != nil {
    59  		panic(ErrTracedAt(err, 1))
    60  	}
    61  	return val
    62  }
    63  
    64  /*
    65  If the error is nil, returns the given values. If the error is non-nil, panics
    66  with that error, idempotently adding a stack trace.
    67  */
    68  func Try2[A, B any](one A, two B, err error) (A, B) {
    69  	if err != nil {
    70  		panic(ErrTracedAt(err, 1))
    71  	}
    72  	return one, two
    73  }
    74  
    75  /*
    76  If the error is nil, returns the given values. If the error is non-nil, panics
    77  with that error, idempotently adding a stack trace.
    78  */
    79  func Try3[A, B, C any](one A, two B, three C, err error) (A, B, C) {
    80  	if err != nil {
    81  		panic(ErrTracedAt(err, 1))
    82  	}
    83  	return one, two, three
    84  }
    85  
    86  /*
    87  Shortcut for use with `recover()`. Useful for writing deferrable functions that
    88  deal with panics. If the given recovered value is nil, this does nothing.
    89  Otherwise converts it to an error, idempotently generating a stack trace, and
    90  panics with the resulting traced error. Usage:
    91  
    92  	gg.TryAny(recover())
    93  */
    94  func TryAny(val any) {
    95  	if val != nil {
    96  		panic(AnyErrTracedAt(val, 1))
    97  	}
    98  }
    99  
   100  /*
   101  Shortcut for use with `recover()`. Useful for writing deferrable functions that
   102  deal with panics. If the given recovered value is nil, this does nothing.
   103  Otherwise converts it to an error, idempotently generating a stack trace,
   104  skipping the given number of frames, and panics with the resulting traced
   105  error. Usage:
   106  
   107  	gg.TryAnyAt(recover(), 1)
   108  */
   109  func TryAnyAt(val any, skip int) {
   110  	if val != nil {
   111  		panic(AnyErrTracedAt(val, skip+1))
   112  	}
   113  }
   114  
   115  /*
   116  If the given error is nil, does nothing. Otherwise wraps the error with `Wrap`
   117  and the given message, and panics with the resulting error.
   118  */
   119  func TryWrap(err error, msg ...any) {
   120  	if err != nil {
   121  		panic(Wrap(err, msg...))
   122  	}
   123  }
   124  
   125  /*
   126  If the given error is nil, does nothing. Otherwise wraps the error with `Wrapf`
   127  and the given message, and panics with the resulting error.
   128  */
   129  func TryWrapf(err error, pat string, arg ...any) {
   130  	if err != nil {
   131  		panic(Wrapf(err, pat, arg...))
   132  	}
   133  }
   134  
   135  /*
   136  Must be deferred. Recovers from panics, writing the resulting error, if any, to
   137  the given pointer. Should be used together with "try"-style functions.
   138  Idempotently adds a stack trace.
   139  */
   140  func Rec(out *error) {
   141  	if out == nil {
   142  		return
   143  	}
   144  
   145  	err := AnyErrTracedAt(recover(), 1)
   146  	if err != nil {
   147  		*out = err
   148  	}
   149  }
   150  
   151  /*
   152  Must be deferred. Recovers from panics, writing the resulting error, if any, to
   153  the given pointer. Should be used together with "try"-style functions. Unlike
   154  `Rec` and other similar functions, this function does NOT generate a stack
   155  trace or modify the error in any way. Most of the time, you should prefer `Rec`
   156  and other similar functions. This function is intended for "internal" library
   157  code, for example to avoid the stack trace check when the trace is guaranteed,
   158  or to avoid generating the trace when the error is meant to be caught before
   159  being returned to the caller.
   160  */
   161  func RecErr(out *error) {
   162  	if out == nil {
   163  		return
   164  	}
   165  
   166  	err := AnyErr(recover())
   167  	if err != nil {
   168  		*out = err
   169  	}
   170  }
   171  
   172  /*
   173  Must be deferred. Same as `Rec`, but skips the given amount of stack frames when
   174  capturing a trace.
   175  */
   176  func RecN(out *error, skip int) {
   177  	if out == nil {
   178  		return
   179  	}
   180  
   181  	err := AnyErrTracedAt(recover(), skip+1)
   182  	if err != nil {
   183  		*out = err
   184  	}
   185  }
   186  
   187  /*
   188  Must be deferred. Filtered version of `Rec`. Recovers from panics that
   189  satisfy the provided test. Re-panics on non-nil errors that don't satisfy the
   190  test. Does NOT check errors that are returned normally, without a panic.
   191  Idempotently adds a stack trace.
   192  */
   193  func RecOnly(ptr *error, test func(error) bool) {
   194  	err := AnyErrTracedAt(recover(), 1)
   195  	if err == nil {
   196  		return
   197  	}
   198  
   199  	*ptr = err
   200  	if test != nil && test(err) {
   201  		return
   202  	}
   203  
   204  	panic(err)
   205  }
   206  
   207  /*
   208  Must be deferred. Recovery for background goroutines which are not allowed to
   209  crash. Calls the provided function ONLY if the error is non-nil.
   210  */
   211  func RecWith(fun func(error)) {
   212  	err := AnyErrTracedAt(recover(), 1)
   213  	if err != nil && fun != nil {
   214  		fun(err)
   215  	}
   216  }
   217  
   218  /*
   219  Runs the given function, converting a panic to an error.
   220  Idempotently adds a stack trace.
   221  */
   222  func Catch(fun func()) (err error) {
   223  	defer RecN(&err, 1)
   224  	if fun != nil {
   225  		fun()
   226  	}
   227  	return
   228  }
   229  
   230  /*
   231  Runs the given function with the given input, converting a panic to an error.
   232  Idempotently adds a stack trace. Compare `Catch01` and `Catch11`.
   233  */
   234  func Catch10[A any](fun func(A), val A) (err error) {
   235  	defer RecN(&err, 1)
   236  	if fun != nil {
   237  		fun(val)
   238  	}
   239  	return
   240  }
   241  
   242  /*
   243  Runs the given function, returning the function's result along with its panic
   244  converted to an error. Idempotently adds a stack trace. Compare `Catch10` and
   245  `Catch11`.
   246  */
   247  func Catch01[A any](fun func() A) (val A, err error) {
   248  	defer RecN(&err, 1)
   249  	if fun != nil {
   250  		val = fun()
   251  	}
   252  	return
   253  }
   254  
   255  /*
   256  Runs the given function with the given input, returning the function's result
   257  along with its panic converted to an error. Idempotently adds a stack trace.
   258  Compare `Catch10` and `Catch01`.
   259  */
   260  func Catch11[A, B any](fun func(A) B, val0 A) (val1 B, err error) {
   261  	defer RecN(&err, 1)
   262  	if fun != nil {
   263  		val1 = fun(val0)
   264  	}
   265  	return
   266  }
   267  
   268  /*
   269  Runs a given function, converting a panic to an error IF the error satisfies
   270  the provided test. Idempotently adds a stack trace.
   271  */
   272  func CatchOnly(test func(error) bool, fun func()) (err error) {
   273  	defer RecOnly(&err, test)
   274  	if fun != nil {
   275  		fun()
   276  	}
   277  	return
   278  }
   279  
   280  /*
   281  Shortcut for `Catch() != nil`. Useful when you want to handle all errors while
   282  ignoring their content.
   283  */
   284  func Caught(fun func()) bool {
   285  	return Catch(fun) != nil
   286  }
   287  
   288  /*
   289  Shortcut for `CatchOnly() != nil`. Useful when you want to handle a specific
   290  error while ignoring its content.
   291  */
   292  func CaughtOnly(test func(error) bool, fun func()) bool {
   293  	return CatchOnly(test, fun) != nil
   294  }
   295  
   296  /*
   297  Must be deferred. Catches panics; ignores errors that satisfy the provided
   298  test; re-panics on other non-nil errors. Idempotently adds a stack trace.
   299  */
   300  func SkipOnly(test func(error) bool) {
   301  	err := AnyErrTracedAt(recover(), 1)
   302  	if err != nil && test != nil && test(err) {
   303  		return
   304  	}
   305  	Try(err)
   306  }
   307  
   308  // Runs a function, catching and ignoring ALL panics.
   309  func Skipping(fun func()) {
   310  	defer Skip()
   311  	if fun != nil {
   312  		fun()
   313  	}
   314  }
   315  
   316  /*
   317  Runs a function, catching and ignoring only the panics that satisfy the provided
   318  test. Idempotently adds a stack trace.
   319  */
   320  func SkippingOnly(test func(error) bool, fun func()) {
   321  	defer SkipOnly(test)
   322  	if fun != nil {
   323  		fun()
   324  	}
   325  }
   326  
   327  // Must be deferred. Catches and ignores ALL panics.
   328  func Skip() { _ = recover() }
   329  
   330  /*
   331  Must be deferred. Tool for adding a stack trace to an arbitrary panic. Unlike
   332  the "rec" functions, this does NOT prevent the panic from propagating. It
   333  simply ensures that there's a stack trace, then re-panics.
   334  
   335  Caution: due to idiosyncrasies of `recover()`, this works ONLY when deferred
   336  directly. Anything other than `defer gg.Traced()` will NOT work.
   337  */
   338  func Traced() { TryErr(AnyErrTracedAt(recover(), 1)) }
   339  
   340  /*
   341  Must be deferred. Version of `Traced` that skips the given number of stack
   342  frames when generating a stack trace.
   343  */
   344  func TracedAt(skip int) { Try(AnyErrTracedAt(recover(), skip+1)) }
   345  
   346  /*
   347  Must be deferred. Runs the function only if there's no panic. Idempotently adds
   348  a stack trace.
   349  */
   350  func Ok(fun func()) {
   351  	Try(AnyErrTracedAt(recover(), 1))
   352  	if fun != nil {
   353  		fun()
   354  	}
   355  }
   356  
   357  /*
   358  Must be deferred. Runs the function ONLY if there's an ongoing panic, and then
   359  re-panics. Idempotently adds a stack trace.
   360  */
   361  func Fail(fun func(error)) {
   362  	err := AnyErrTracedAt(recover(), 1)
   363  	if err != nil && fun != nil {
   364  		fun(err)
   365  	}
   366  	Try(err)
   367  }
   368  
   369  /*
   370  Must be deferred. Always runs the given function, passing either the current
   371  panic or nil. If the error is non-nil, re-panics.
   372  */
   373  func Finally(fun func(error)) {
   374  	err := AnyErrTracedAt(recover(), 1)
   375  	if fun != nil {
   376  		fun(err)
   377  	}
   378  	Try(err)
   379  }
   380  
   381  /*
   382  Must be deferred. Short for "transmute" or "transform". Catches an ongoing
   383  panic, transforms the error by calling the provided function, and then
   384  re-panics via `Try`. Idempotently adds a stack trace.
   385  */
   386  func Trans(fun func(error) error) {
   387  	err := AnyErrTracedAt(recover(), 1)
   388  	if err != nil && fun != nil {
   389  		err = fun(err)
   390  	}
   391  	Try(err)
   392  }
   393  
   394  /*
   395  Runs a function, "transmuting" or "transforming" the resulting panic by calling
   396  the provided transformer. See `Trans`.
   397  */
   398  func Transing(trans func(error) error, fun func()) {
   399  	defer Trans(trans)
   400  	if fun != nil {
   401  		fun()
   402  	}
   403  }
   404  
   405  /*
   406  Must be deferred. Similar to `Trans`, but transforms only non-nil errors that
   407  satisfy the given predicate. Idempotently adds a stack trace.
   408  */
   409  func TransOnly(test func(error) bool, trans func(error) error) {
   410  	err := AnyErrTracedAt(recover(), 1)
   411  	if err != nil && test != nil && trans != nil && test(err) {
   412  		err = trans(err)
   413  	}
   414  	Try(err)
   415  }
   416  
   417  /*
   418  Must be deferred. Wraps non-nil panics, prepending the error message and
   419  idempotently adding a stack trace. The message is converted to a string via
   420  `Str(msg...)`. Usage:
   421  
   422  	defer gg.Detail(`unable to do X`)
   423  	defer gg.Detail(`unable to do A with B `, someEntity.Id)
   424  */
   425  func Detail(msg ...any) {
   426  	Try(Wrap(AnyErr(recover()), msg...))
   427  }
   428  
   429  /*
   430  Must be deferred. Wraps non-nil panics, prepending the error message and
   431  idempotently adding a stack trace. Usage:
   432  
   433  	defer gg.Detailf(`unable to %v`, `do X`)
   434  
   435  The first argument must be a hardcoded pattern string compatible with
   436  `fmt.Sprintf` and other similar functions. If the first argument is an
   437  expression rather than a hardcoded string, use `Detail` instead.
   438  */
   439  func Detailf(pat string, arg ...any) {
   440  	Try(Wrapf(AnyErr(recover()), pat, arg...))
   441  }
   442  
   443  /*
   444  Must be deferred. Wraps non-nil panics, prepending the error message, ONLY if
   445  they satisfy the provided test. Idempotently adds a stack trace.
   446  */
   447  func DetailOnlyf(test func(error) bool, pat string, arg ...any) {
   448  	err := AnyErrTracedAt(recover(), 1)
   449  	if err != nil && test != nil && test(err) {
   450  		err = Wrapf(err, pat, arg...)
   451  	}
   452  	Try(err)
   453  }
   454  
   455  /*
   456  Must be deferred in your `main` function. If there is a panic, this prints a
   457  stack trace and kills the process with exit code 1. This is very similar to the
   458  default Go behavior, the only difference being how the resulting panic trace is
   459  formatted. This prevents Go from printing the default, very informative but
   460  difficult to read panic trace, replacing it with a less informative but much
   461  easier to read trace. See our types `Err` and `Trace`. Usage example:
   462  
   463  	func main() {
   464  		defer gg.Fatal()
   465  		// Perform actions that may panic.
   466  	}
   467  */
   468  func Fatal() {
   469  	val := recover()
   470  	if val != nil {
   471  		Nop2(os.Stderr.Write(AnyToErrTracedAt(val, 1).AppendStack(nil)))
   472  		os.Exit(1)
   473  	}
   474  }