-
Notifications
You must be signed in to change notification settings - Fork 1
/
try.go
486 lines (433 loc) · 12 KB
/
try.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
package gg
import "os"
/*
If the error is nil, returns void. If the error is non-nil, panics with that
error, idempotently adding a stack trace.
*/
func Try(err error) {
if err != nil {
panic(ErrTracedAt(err, 1))
}
}
/*
If the error is nil, returns void. If the error is non-nil, panics with that
error, idempotently adding a stack trace and skipping the given number of stack
frames.
*/
func TryAt(err error, skip int) {
if err != nil {
panic(ErrTracedAt(err, skip+1))
}
}
/*
If the error is nil, returns void. If the error is non-nil, panics with that
exact error. Unlike `Try` and other similar functions, this function does NOT
generate a stack trace or modify the error in any way. Most of the time, you
should prefer `Try` and other similar functions. This function is intended
for "internal" library code, for example to avoid the stack trace check when
the trace is guaranteed, or to avoid generating the trace when the error is
meant to be caught before being returned to the caller.
*/
func TryErr(err error) {
if err != nil {
panic(err)
}
}
/*
Like `Try`, but for multiple errors. Uses `Errs.Err` to combine the errors.
If the resulting error is nil, returns void. If the resulting error is non-nil,
panics with that error, idempotently adding a stack trace.
*/
func TryMul(src ...error) {
err := ErrMul(src...)
if err != nil {
panic(ErrTracedAt(err, 1))
}
}
/*
If the error is nil, returns the given value. If the error is non-nil, panics
with that error, idempotently adding a stack trace.
*/
func Try1[A any](val A, err error) A {
if err != nil {
panic(ErrTracedAt(err, 1))
}
return val
}
/*
If the error is nil, returns the given values. If the error is non-nil, panics
with that error, idempotently adding a stack trace.
*/
func Try2[A, B any](one A, two B, err error) (A, B) {
if err != nil {
panic(ErrTracedAt(err, 1))
}
return one, two
}
/*
If the error is nil, returns the given values. If the error is non-nil, panics
with that error, idempotently adding a stack trace.
*/
func Try3[A, B, C any](one A, two B, three C, err error) (A, B, C) {
if err != nil {
panic(ErrTracedAt(err, 1))
}
return one, two, three
}
/*
Shortcut for use with `recover()`. Useful for writing deferrable functions that
deal with panics. If the given recovered value is nil, this does nothing.
Otherwise converts it to an error, idempotently generating a stack trace, and
panics with the resulting traced error. Usage:
gg.TryAny(recover())
*/
func TryAny(val any) {
if val != nil {
panic(AnyErrTracedAt(val, 1))
}
}
/*
Shortcut for use with `recover()`. Useful for writing deferrable functions that
deal with panics. If the given recovered value is nil, this does nothing.
Otherwise converts it to an error, idempotently generating a stack trace,
skipping the given number of frames, and panics with the resulting traced
error. Usage:
gg.TryAnyAt(recover(), 1)
*/
func TryAnyAt(val any, skip int) {
if val != nil {
panic(AnyErrTracedAt(val, skip+1))
}
}
/*
If the given error is nil, does nothing. Otherwise wraps the error with `Wrap`
and the given message, and panics with the resulting error.
*/
func TryWrap(err error, msg ...any) {
if err != nil {
panic(Wrap(err, msg...))
}
}
/*
If the given error is nil, does nothing. Otherwise wraps the error with `Wrapf`
and the given message, and panics with the resulting error.
*/
func TryWrapf(err error, pat string, arg ...any) {
if err != nil {
panic(Wrapf(err, pat, arg...))
}
}
/*
If the given function and error is non-nil, transforms the error by calling the
given function. Then if the resulting error is non-nil, panics with the that
error, idempotently adding a stack trace.
*/
func TryTrans(fun func(error) error, err error) {
if fun != nil && err != nil {
err = fun(err)
}
Try(err)
}
/*
Must be deferred. Recovers from panics, writing the resulting error, if any, to
the given pointer. Should be used together with "try"-style functions.
Idempotently adds a stack trace.
*/
func Rec(out *error) {
if out == nil {
return
}
err := AnyErrTracedAt(recover(), 1)
if err != nil {
*out = err
}
}
/*
Must be deferred. Recovers from panics, writing the resulting error, if any, to
the given pointer. Should be used together with "try"-style functions. Unlike
`Rec` and other similar functions, this function does NOT generate a stack
trace or modify the error in any way. Most of the time, you should prefer `Rec`
and other similar functions. This function is intended for "internal" library
code, for example to avoid the stack trace check when the trace is guaranteed,
or to avoid generating the trace when the error is meant to be caught before
being returned to the caller.
*/
func RecErr(out *error) {
if out == nil {
return
}
err := AnyErr(recover())
if err != nil {
*out = err
}
}
/*
Must be deferred. Same as `Rec`, but skips the given amount of stack frames when
capturing a trace.
*/
func RecN(out *error, skip int) {
if out == nil {
return
}
err := AnyErrTracedAt(recover(), skip+1)
if err != nil {
*out = err
}
}
/*
Must be deferred. Filtered version of `Rec`. Recovers from panics that
satisfy the provided test. Re-panics on non-nil errors that don't satisfy the
test. Does NOT check errors that are returned normally, without a panic.
Idempotently adds a stack trace.
*/
func RecOnly(ptr *error, test func(error) bool) {
err := AnyErrTracedAt(recover(), 1)
if err == nil {
return
}
*ptr = err
if test != nil && test(err) {
return
}
panic(err)
}
/*
Must be deferred. Recovery for background goroutines which are not allowed to
crash. Calls the provided function ONLY if the error is non-nil.
*/
func RecWith(fun func(error)) {
err := AnyErrTracedAt(recover(), 1)
if err != nil && fun != nil {
fun(err)
}
}
/*
Runs the given function, converting a panic to an error.
Idempotently adds a stack trace.
*/
func Catch(fun func()) (err error) {
defer RecN(&err, 1)
if fun != nil {
fun()
}
return
}
/*
Runs the given function with the given input, converting a panic to an error.
Idempotently adds a stack trace. Compare `Catch01` and `Catch11`.
*/
func Catch10[A any](fun func(A), val A) (err error) {
defer RecN(&err, 1)
if fun != nil {
fun(val)
}
return
}
/*
Runs the given function, returning the function's result along with its panic
converted to an error. Idempotently adds a stack trace. Compare `Catch10` and
`Catch11`.
*/
func Catch01[A any](fun func() A) (val A, err error) {
defer RecN(&err, 1)
if fun != nil {
val = fun()
}
return
}
/*
Runs the given function with the given input, returning the function's result
along with its panic converted to an error. Idempotently adds a stack trace.
Compare `Catch10` and `Catch01`.
*/
func Catch11[A, B any](fun func(A) B, val0 A) (val1 B, err error) {
defer RecN(&err, 1)
if fun != nil {
val1 = fun(val0)
}
return
}
/*
Runs a given function, converting a panic to an error IF the error satisfies
the provided test. Idempotently adds a stack trace.
*/
func CatchOnly(test func(error) bool, fun func()) (err error) {
defer RecOnly(&err, test)
if fun != nil {
fun()
}
return
}
/*
Shortcut for `Catch() != nil`. Useful when you want to handle all errors while
ignoring their content.
*/
func Caught(fun func()) bool {
return Catch(fun) != nil
}
/*
Shortcut for `CatchOnly() != nil`. Useful when you want to handle a specific
error while ignoring its content.
*/
func CaughtOnly(test func(error) bool, fun func()) bool {
return CatchOnly(test, fun) != nil
}
/*
Must be deferred. Catches panics; ignores errors that satisfy the provided
test; re-panics on other non-nil errors. Idempotently adds a stack trace.
*/
func SkipOnly(test func(error) bool) {
err := AnyErrTracedAt(recover(), 1)
if err != nil && test != nil && test(err) {
return
}
Try(err)
}
// Runs a function, catching and ignoring ALL panics.
func Skipping(fun func()) {
defer Skip()
if fun != nil {
fun()
}
}
/*
Runs a function, catching and ignoring only the panics that satisfy the provided
test. Idempotently adds a stack trace.
*/
func SkippingOnly(test func(error) bool, fun func()) {
defer SkipOnly(test)
if fun != nil {
fun()
}
}
// Must be deferred. Catches and ignores ALL panics.
func Skip() { _ = recover() }
/*
Must be deferred. Tool for adding a stack trace to an arbitrary panic. Unlike
the "rec" functions, this does NOT prevent the panic from propagating. It
simply ensures that there's a stack trace, then re-panics.
Caution: due to idiosyncrasies of `recover()`, this works ONLY when deferred
directly. Anything other than `defer gg.Traced()` will NOT work.
*/
func Traced() { TryErr(AnyErrTracedAt(recover(), 1)) }
/*
Must be deferred. Version of `Traced` that skips the given number of stack
frames when generating a stack trace.
*/
func TracedAt(skip int) { Try(AnyErrTracedAt(recover(), skip+1)) }
/*
Must be deferred. Runs the function only if there's no panic. Idempotently adds
a stack trace.
*/
func Ok(fun func()) {
Try(AnyErrTracedAt(recover(), 1))
if fun != nil {
fun()
}
}
/*
Must be deferred. Runs the function ONLY if there's an ongoing panic, and then
re-panics. Idempotently adds a stack trace.
*/
func Fail(fun func(error)) {
err := AnyErrTracedAt(recover(), 1)
if err != nil && fun != nil {
fun(err)
}
Try(err)
}
/*
Must be deferred. Always runs the given function, passing either the current
panic or nil. If the error is non-nil, re-panics.
*/
func Finally(fun func(error)) {
err := AnyErrTracedAt(recover(), 1)
if fun != nil {
fun(err)
}
Try(err)
}
/*
Must be deferred. Short for "transmute" or "transform". Catches an ongoing
panic, transforms the error by calling the provided function, and then
re-panics via `Try`. Idempotently adds a stack trace.
*/
func Trans(fun func(error) error) {
err := AnyErrTracedAt(recover(), 1)
if err != nil && fun != nil {
err = fun(err)
}
Try(err)
}
/*
Runs a function, "transmuting" or "transforming" the resulting panic by calling
the provided transformer. See `Trans`.
*/
func Transing(trans func(error) error, fun func()) {
defer Trans(trans)
if fun != nil {
fun()
}
}
/*
Must be deferred. Similar to `Trans`, but transforms only non-nil errors that
satisfy the given predicate. Idempotently adds a stack trace.
*/
func TransOnly(test func(error) bool, trans func(error) error) {
err := AnyErrTracedAt(recover(), 1)
if err != nil && test != nil && trans != nil && test(err) {
err = trans(err)
}
Try(err)
}
/*
Must be deferred. Wraps non-nil panics, prepending the error message and
idempotently adding a stack trace. The message is converted to a string via
`Str(msg...)`. Usage:
defer gg.Detail(`unable to do X`)
defer gg.Detail(`unable to do A with B `, someEntity.Id)
*/
func Detail(msg ...any) {
Try(Wrap(AnyErr(recover()), msg...))
}
/*
Must be deferred. Wraps non-nil panics, prepending the error message and
idempotently adding a stack trace. Usage:
defer gg.Detailf(`unable to %v`, `do X`)
The first argument must be a hardcoded pattern string compatible with
`fmt.Sprintf` and other similar functions. If the first argument is an
expression rather than a hardcoded string, use `Detail` instead.
*/
func Detailf(pat string, arg ...any) {
Try(Wrapf(AnyErr(recover()), pat, arg...))
}
/*
Must be deferred. Wraps non-nil panics, prepending the error message, ONLY if
they satisfy the provided test. Idempotently adds a stack trace.
*/
func DetailOnlyf(test func(error) bool, pat string, arg ...any) {
err := AnyErrTracedAt(recover(), 1)
if err != nil && test != nil && test(err) {
err = Wrapf(err, pat, arg...)
}
Try(err)
}
/*
Must be deferred in your `main` function. If there is a panic, this prints a
stack trace and kills the process with exit code 1. This is very similar to the
default Go behavior, the only difference being how the resulting panic trace is
formatted. This prevents Go from printing the default, very informative but
difficult to read panic trace, replacing it with a less informative but much
easier to read trace. See our types `Err` and `Trace`. Usage example:
func main() {
defer gg.Fatal()
// Perform actions that may panic.
}
*/
func Fatal() {
val := recover()
if val != nil {
Nop2(os.Stderr.Write(AnyToErrTracedAt(val, 1).AppendStackTo(nil)))
os.Exit(1)
}
}