/
errors.go
189 lines (172 loc) · 4.98 KB
/
errors.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
package errors
import (
"errors"
"fmt"
"strings"
)
// New returns an error that formats as the given text.
func New(text string) error {
return errors.New(text)
}
// wrapperError satisfies the error interface.
type wrapperError struct {
msg string
detail []string
data map[string]interface{}
stack []StackFrame
root error
}
// It satisfies the error interface.
func (e wrapperError) Error() string {
return e.msg
}
// Root returns the original error that was wrapped by one or more
// calls to Wrap. If e does not wrap other errors, it will be returned
// as-is.
func Root(e error) error {
if wErr, ok := e.(wrapperError); ok {
return wErr.root
}
return e
}
// wrap adds a context message and stack trace to err and returns a new error
// containing the new context. This function is meant to be composed within
// other exported functions, such as Wrap and WithDetail.
// The argument stackSkip is the number of stack frames to ascend when
// generating stack straces, where 0 is the caller of wrap.
func wrap(err error, msg string, stackSkip int) error {
if err == nil {
return nil
}
werr, ok := err.(wrapperError)
if !ok {
werr.root = err
werr.msg = err.Error()
werr.stack = getStack(stackSkip+2, stackTraceSize)
}
if msg != "" {
werr.msg = msg + ": " + werr.msg
}
return werr
}
// Wrap adds a context message and stack trace to err and returns a new error
// with the new context. Arguments are handled as in fmt.Print.
// Use Root to recover the original error wrapped by one or more calls to Wrap.
// Use Stack to recover the stack trace.
// Wrap returns nil if err is nil.
func Wrap(err error, a ...interface{}) error {
if err == nil {
return nil
}
return wrap(err, fmt.Sprint(a...), 1)
}
// Wrapf is like Wrap, but arguments are handled as in fmt.Printf.
func Wrapf(err error, format string, a ...interface{}) error {
if err == nil {
return nil
}
return wrap(err, fmt.Sprintf(format, a...), 1)
}
// WithDetail returns a new error that wraps
// err as a chain error messsage containing text
// as its additional context.
// Function Detail will return the given text
// when called on the new error value.
func WithDetail(err error, text string) error {
if err == nil {
return nil
}
if text == "" {
return err
}
e1 := wrap(err, text, 1).(wrapperError)
e1.detail = append(e1.detail, text)
return e1
}
// WithDetailf is like WithDetail, except it formats
// the detail message as in fmt.Printf.
// Function Detail will return the formatted text
// when called on the new error value.
func WithDetailf(err error, format string, v ...interface{}) error {
if err == nil {
return nil
}
text := fmt.Sprintf(format, v...)
e1 := wrap(err, text, 1).(wrapperError)
e1.detail = append(e1.detail, text)
return e1
}
// Detail returns the detail message contained in err, if any.
// An error has a detail message if it was made by WithDetail
// or WithDetailf.
func Detail(err error) string {
wrapper, ok := err.(wrapperError)
if !ok {
return err.Error()
}
return strings.Join(wrapper.detail, "; ")
}
// withData returns a new error that wraps err
// as a chain error message containing v as
// an extra data item.
// Calling Data on the returned error yields v.
// Note that if err already has a data item,
// it will not be accessible via the returned error value.
func withData(err error, v map[string]interface{}) error {
if err == nil {
return nil
}
e1 := wrap(err, "", 1).(wrapperError)
e1.data = v
return e1
}
// WithData returns a new error that wraps err
// as a chain error message containing a value of type
// map[string]interface{} as an extra data item.
// The map contains the values in the map in err,
// if any, plus the items in keyval.
// Keyval takes the form
// k1, v1, k2, v2, ...
// Values kN must be strings.
// Calling Data on the returned error yields the map.
// Note that if err already has a data item of any other type,
// it will not be accessible via the returned error value.
func WithData(err error, keyval ...interface{}) error {
if err == nil {
return nil
}
// TODO(kr): add vet check for odd-length keyval and non-string keys
newkv := make(map[string]interface{})
for k, v := range Data(err) {
newkv[k] = v
}
for i := 0; i < len(keyval); i += 2 {
newkv[keyval[i].(string)] = keyval[i+1]
}
return withData(err, newkv)
}
// Data returns the data item in err, if any.
func Data(err error) map[string]interface{} {
wrapper, _ := err.(wrapperError)
return wrapper.data
}
// Sub returns an error containing root as its root and
// taking all other metadata (stack trace, detail, message,
// and data items) from err.
//
// Sub returns nil when either root or err is nil.
//
// Use this when you need to substitute a new root error in place
// of an existing error that may already hold a stack trace
// or other metadata.
func Sub(root, err error) error {
if wrapper, ok := err.(wrapperError); ok && root != nil {
wrapper.root = Root(root)
wrapper.msg = root.Error()
root = wrapper
}
if err == nil {
return nil
}
return Wrap(root, err.Error())
}