-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrecovery.go
256 lines (224 loc) · 6.01 KB
/
recovery.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
package recovery
import (
"errors"
"fmt"
"io"
"log"
"github.com/gregwebs/stackfmt"
)
// A Panic that was converted to an error.
type PanicError struct {
Panic interface{}
Stack stackfmt.Stack
}
func newPanicError(r interface{}, skip int) error {
return PanicError{Panic: r, Stack: stackfmt.NewStackSkip(skip)}
}
func (p PanicError) Unwrap() error {
if err, ok := p.Panic.(error); ok {
return err
}
return nil
}
func (p PanicError) Error() string {
if err := p.Unwrap(); err != nil {
return "panic: " + err.Error()
} else {
return "panic: " + fmt.Sprintf("%v", p.Panic)
}
}
func (p PanicError) HasStack() bool {
return true
}
func (p PanicError) StackTrace() stackfmt.StackTrace {
return p.Stack.StackTrace()
}
func (p PanicError) StackTraceFormat(s fmt.State, v rune) {
p.Stack.FormatStackTrace(s, v)
}
// This works with the extended syntax "%+v" for printing stack traces
func (p PanicError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
if _, errWrite := fmt.Fprint(s, "panic: "); errWrite != nil {
handleWriteError(errWrite)
}
if f, ok := p.Panic.(fmt.Formatter); ok {
f.Format(s, verb)
} else {
if _, errWrite := fmt.Fprintf(s, "%+v", p.Panic); errWrite != nil {
handleWriteError(errWrite)
}
}
if p.Stack != nil {
if _, errWrite := io.WriteString(s, "\n"); errWrite != nil {
handleWriteError(errWrite)
}
p.Stack.FormatStackTrace(s, verb)
}
return
}
fallthrough
case 's', 'q':
if _, errWrite := io.WriteString(s, p.Error()); errWrite != nil {
handleWriteError(errWrite)
}
}
}
// An error that was intentionally thrown via panic
// Pass it through without wrapping it as a PanicError
type ThrownError struct {
Err error
Stack stackfmt.Stack
}
func (e ThrownError) Unwrap() error {
return e.Err
}
func (e ThrownError) Error() string {
return e.Unwrap().Error()
}
func (e ThrownError) HasStack() bool {
return true
}
func (e ThrownError) StackTrace() stackfmt.StackTrace {
return e.Stack.StackTrace()
}
func (e ThrownError) StackTraceFormat(s fmt.State, v rune) {
e.Stack.FormatStackTrace(s, v)
}
// Call is a helper function which allows you to easily recover from panics in the given function parameter "fn".
// If fn returns an error, that will be returned.
// If a panic occurs, Call will convert it to a PanicError and return it.
func Call(fn func() error) (err error) {
// the returned variable distinguishes the case of panic(nil)
returned := false
defer func() {
r := recover()
if !returned && err == nil {
// the case of panic(nil)
if r == nil {
r = newPanicError(r, 2)
}
err = ToError(r)
}
}()
result := fn()
returned = true
return result
}
// Same as Call but support returning 1 result in addition to the error.
func Call1[T any](fn func() (T, error)) (T, error) {
var t T
return t, Call(func() error {
var err error
t, err = fn()
return err
})
}
// Same as Call but support returning 2 results in addition to the error.
func Call2[T any, U any](fn func() (T, U, error)) (T, U, error) {
var t T
var u U
return t, u, Call(func() error {
var err error
t, u, err = fn()
return err
})
}
// Same as Call but support returning 3 results in addition to the error.
func Call3[T any, U any, V any](fn func() (T, U, V, error)) (T, U, V, error) {
var t T
var u U
var v V
return t, u, v, Call(func() error {
var err error
t, u, v, err = fn()
return err
})
}
// The default ErrorHandler is DefaultErrorHandler
var ErrorHandler func(error) = DefaultErrorHandler
// The DefaultErrorHandler prints the error with log.Printf
func DefaultErrorHandler(err error) {
log.Printf("%+v", err)
}
// Go is designed to use as an entry point to a go routine.
//
// go recovery.Go(func() error { ... })
//
// Instead of your program crashing, the panic is converted to a PanicError.
// The panic or a returned error is given to the global ErrorHandler function.
// Change the behavior globally by setting the ErrorHandler package variable.
// Or use GoHandler to set the error handler on a local basis.
func Go(fn func() error) {
GoHandler(ErrorHandler, fn)
}
// GoHandler is designed to be used when creating go routines to handle panics and errors.
// Instead of your program crashing, a panic is converted to a PanicError.
// The panic or a returned error is given to the errorHandler function.
//
// go GoHandler(handler, func() error { ... })
func GoHandler(errorHandler func(err error), fn func() error) {
e := Call(func() error {
return fn()
})
if e != nil {
errorHandler(e)
}
}
// Wrap panic values in a PanicError.
// nil is returned as nil so this function can be called direclty with the result of recover()
// A PanicError is returned as is and a ThrownError is returned unwrapped.
func ToError(r interface{}) error {
switch r := r.(type) {
case nil:
return nil
// return a ThrownError or PanicError as is
case ThrownError:
return r
case PanicError:
return r
case *ThrownError:
if r == nil {
return nil
}
return *r
case *PanicError:
if r == nil {
return nil
}
return *r
case error:
pe := PanicError{}
if errors.As(r, &pe) {
return r
}
}
// Convert a panic value to an error
return newPanicError(r, 3)
}
// Throw will panic an error as a ThrownError.
// The error will be returned by Call* functions as a normal error rather than a PanicError.
// Useful with CallX functions to avoid writing out zero values when prototyping.
//
// func (x int) (int, error) {
// recovery.Call1(func() (int, error) {
// recovery.Throw(err)
// }
// }
func Throw(err error) {
panic(ThrownError{Err: err, Stack: stackfmt.NewStackSkip(1)})
}
// A convenience function for calling Throw(fmt.Errorf(...))
func Throwf(format string, args ...interface{}) {
panic(ThrownError{Err: fmt.Errorf(format, args...), Stack: stackfmt.NewStackSkip(1)})
}
// HandleFmtWriteError handles (rare) errors when writing to fmt.State.
// It defaults to printing the errors.
func HandleFmtWriteError(handler func(err error)) {
handleWriteError = handler
}
var handleWriteError = func(err error) {
log.Println(err)
}