File: panic.go

package info (click to toggle)
golang-github-joomcode-errorx 1.2.0-1~exp1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 280 kB
  • sloc: makefile: 2
file content (71 lines) | stat: -rw-r--r-- 2,616 bytes parent folder | download
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
package errorx

import "fmt"

// Panic is an alternative to the built-in panic call.
// When calling panic as a reaction to error, prefer this function over vanilla panic().
// If err happens to be an errorx error, it may hold the original stack trace of the issue.
// With panic(err), this information may be lost if panic is handled by the default handler.
// With errorx.Panic(err), all data is preserved regardless of the handle mechanism.
// It can be recovered either from default panic message, recover() result or ErrorFromPanic() function.
//
// Even if err stack trace is exactly the same as default panic trace, this can be tolerated,
// as panics must not be a way to report conventional errors and are therefore rare.
// With this in mind, it is better to err on the side of completeness rather than brevity.
//
// This function never returns, but the signature may be used for convenience:
//
// 		return nil, errorx.Panic(err)
// 		panic(errorx.Panic(err))
//
func Panic(err error) error {
	panic(newPanicErrorWrapper(err))
}

// ErrorFromPanic recovers the original error from panic, best employed along with Panic() function from the same package.
// The original error, if present, typically holds more relevant data
// than a combination of panic message and the stack trace which can be collected after recover().
//
// More importantly, it allows for greater composability,
// if ever there is a need to recover from panic and pass the error information forwards in its proper form.
//
// Note that panic is not a proper means to report errors,
// so this mechanism should never be used where a error based control flow is at all possible.
func ErrorFromPanic(recoverResult interface{}) (error, bool) {
	err, ok := recoverResult.(error)
	if !ok {
		return nil, false
	}

	if wrapper, ok := err.(*panicErrorWrapper); ok {
		return wrapper.inner, true
	}

	return err, true
}

func newPanicErrorWrapper(err error) *panicErrorWrapper {
	return &panicErrorWrapper{
		inner: NewErrorBuilder(panicPayloadWrap).
			WithConditionallyFormattedMessage("panic").
			WithCause(err).
			EnhanceStackTrace().
			Create(),
	}
}

// panicErrorWrapper is designed for the original stack trace not to be lost in any way it may be handled
type panicErrorWrapper struct {
	inner error
}

func (w *panicErrorWrapper) Error() string {
	return fmt.Sprintf("%+v", w.inner)
}

func (w *panicErrorWrapper) String() string {
	return w.Error()
}

// Only required to transform panic into error while preserving the stack trace
var panicPayloadWrap = syntheticErrors.NewType("panic").ApplyModifiers(TypeModifierTransparent)