-
Notifications
You must be signed in to change notification settings - Fork 31
/
builder.go
165 lines (140 loc) · 4.95 KB
/
builder.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
package errorx
import (
"fmt"
"strconv"
)
// ErrorBuilder is a utility to compose an error from type.
// Typically, a direct usage is not required: either Type methods of helpers like Decorate are sufficient.
// Only use builder if no simpler alternative is available.
type ErrorBuilder struct {
errorType *Type
message string
cause error
mode callStackBuildMode
isTransparent bool
}
// NewErrorBuilder creates error builder from an existing error type.
func NewErrorBuilder(t *Type) ErrorBuilder {
getMode := func() callStackBuildMode {
if !t.modifiers.CollectStackTrace() {
return stackTraceOmit
}
return stackTraceCollect
}
return ErrorBuilder{
errorType: t,
mode: getMode(),
isTransparent: t.modifiers.Transparent(),
}
}
// WithCause provides an original cause for error.
// For non-errorx errors, a stack trace is collected unless Type tells otherwise.
// Otherwise, it is inherited by default, as error wrapping is typically performed 'en passe'.
// Note that even if an original error explicitly omitted the stack trace, it could be added on wrap.
func (eb ErrorBuilder) WithCause(err error) ErrorBuilder {
eb.cause = err
if Cast(err) != nil {
if eb.errorType.modifiers.CollectStackTrace() {
eb.mode = stackTraceBorrowOrCollect
} else {
eb.mode = stackTraceBorrowOnly
}
}
return eb
}
// Transparent makes a wrap transparent rather than opaque (default).
// Transparent wrap hides the current error type from the type checks and exposes the error type of the cause instead.
// The same holds true for traits, and the dynamic properties are visible from both cause and transparent wrapper.
// Note that if the cause error is non-errorx, transparency will still hold, type check against wrapper will still fail.
func (eb ErrorBuilder) Transparent() ErrorBuilder {
if eb.cause == nil {
panic("wrong builder usage: wrap modifier without non-nil cause")
}
eb.isTransparent = true
return eb
}
// EnhanceStackTrace is a signal to collect the current stack trace along with the original one, and use both in formatting.
// If the original error does not hold a stack trace for whatever reason, it will be collected it this point.
// This is typically a way to handle an error received from another goroutine - say, a worker pool.
// When stack traces overlap, formatting makes a conservative attempt not to repeat itself,
// preserving the *original* stack trace in its entirety.
func (eb ErrorBuilder) EnhanceStackTrace() ErrorBuilder {
if eb.cause == nil {
panic("wrong builder usage: wrap modifier without non-nil cause")
}
if Cast(eb.cause) != nil {
eb.mode = stackTraceEnhance
} else {
eb.mode = stackTraceCollect
}
return eb
}
// WithConditionallyFormattedMessage provides a message for an error in flexible format, to simplify its usages.
// Without args, leaves the original message intact, so a message may be generated or provided externally.
// With args, a formatting is performed, and it is therefore expected a format string to be constant.
func (eb ErrorBuilder) WithConditionallyFormattedMessage(message string, args ...interface{}) ErrorBuilder {
if len(args) == 0 {
eb.message = message
} else {
eb.message = fmt.Sprintf(message, args...)
}
return eb
}
// Create returns an error with specified params.
func (eb ErrorBuilder) Create() *Error {
err := &Error{
errorType: eb.errorType,
message: eb.message,
cause: eb.cause,
transparent: eb.isTransparent,
stackTrace: eb.assembleStackTrace(),
}
return err
}
type callStackBuildMode int
const (
stackTraceCollect callStackBuildMode = 1
stackTraceBorrowOrCollect callStackBuildMode = 2
stackTraceBorrowOnly callStackBuildMode = 3
stackTraceEnhance callStackBuildMode = 4
stackTraceOmit callStackBuildMode = 5
)
func (eb ErrorBuilder) assembleStackTrace() *stackTrace {
switch eb.mode {
case stackTraceCollect:
return eb.collectOriginalStackTrace()
case stackTraceBorrowOnly:
return eb.borrowStackTraceFromCause()
case stackTraceBorrowOrCollect:
if st := eb.borrowStackTraceFromCause(); st != nil {
return st
}
return eb.collectOriginalStackTrace()
case stackTraceEnhance:
return eb.combineStackTraceWithCause()
case stackTraceOmit:
return nil
default:
panic("unknown mode " + strconv.Itoa(int(eb.mode)))
}
}
func (eb ErrorBuilder) collectOriginalStackTrace() *stackTrace {
return collectStackTrace()
}
func (eb ErrorBuilder) borrowStackTraceFromCause() *stackTrace {
return eb.extractStackTraceFromCause(eb.cause)
}
func (eb ErrorBuilder) combineStackTraceWithCause() *stackTrace {
currentStackTrace := collectStackTrace()
originalStackTrace := eb.extractStackTraceFromCause(eb.cause)
if originalStackTrace != nil {
currentStackTrace.enhanceWithCause(originalStackTrace)
}
return currentStackTrace
}
func (eb ErrorBuilder) extractStackTraceFromCause(cause error) *stackTrace {
if typedCause := Cast(cause); typedCause != nil {
return typedCause.stackTrace
}
return nil
}