forked from dropbox/godropbox
-
Notifications
You must be signed in to change notification settings - Fork 0
/
errors.go
293 lines (262 loc) · 7.18 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
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
// This module implements functions which manipulate errors and provide stack
// trace information.
//
// NOTE: This package intentionally mirrors the standard "errors" module.
// All dropbox code should use this.
package errors
import (
"bytes"
"fmt"
"reflect"
"runtime"
"strings"
)
// This interface exposes additional information about the error.
type DropboxError interface {
// This returns the error message without the stack trace.
GetMessage() string
// This returns the stack trace without the error message.
GetStack() string
// This returns the stack trace's context.
GetContext() string
// This returns the wrapped error. This returns nil if this does not wrap
// another error.
GetInner() error
// Implements the built-in error interface.
Error() string
}
// Standard struct for general types of errors.
//
// For an example of custom error type, look at databaseError/newDatabaseError
// in errors_test.go.
type DropboxBaseError struct {
Msg string
Stack string
Context string
inner error
}
// This returns the error string without stack trace information.
func GetMessage(err interface{}) string {
switch e := err.(type) {
case DropboxError:
dberr := DropboxError(e)
ret := []string{}
for dberr != nil {
ret = append(ret, dberr.GetMessage())
d := dberr.GetInner()
if d == nil {
break
}
var ok bool
dberr, ok = d.(DropboxError)
if !ok {
ret = append(ret, d.Error())
break
}
}
return strings.Join(ret, " ")
case runtime.Error:
return runtime.Error(e).Error()
default:
return "Passed a non-error to GetMessage"
}
}
// This returns a string with all available error information, including inner
// errors that are wrapped by this errors.
func (e *DropboxBaseError) Error() string {
return DefaultError(e)
}
// This returns the error message without the stack trace.
func (e *DropboxBaseError) GetMessage() string {
return e.Msg
}
// This returns the stack trace without the error message.
func (e *DropboxBaseError) GetStack() string {
return e.Stack
}
// This returns the stack trace's context.
func (e *DropboxBaseError) GetContext() string {
return e.Context
}
// This returns the wrapped error, if there is one.
func (e *DropboxBaseError) GetInner() error {
return e.inner
}
// This returns a new DropboxBaseError initialized with the given message and
// the current stack trace.
func New(msg string) DropboxError {
stack, context := StackTrace()
return &DropboxBaseError{
Msg: msg,
Stack: stack,
Context: context,
}
}
// Same as New, but with fmt.Printf-style parameters.
func Newf(format string, args ...interface{}) DropboxError {
stack, context := StackTrace()
return &DropboxBaseError{
Msg: fmt.Sprintf(format, args...),
Stack: stack,
Context: context,
}
}
// Wraps another error in a new DropboxBaseError.
func Wrap(err error, msg string) DropboxError {
stack, context := StackTrace()
return &DropboxBaseError{
Msg: msg,
Stack: stack,
Context: context,
inner: err,
}
}
// Same as Wrap, but with fmt.Printf-style parameters.
func Wrapf(err error, format string, args ...interface{}) DropboxError {
stack, context := StackTrace()
return &DropboxBaseError{
Msg: fmt.Sprintf(format, args...),
Stack: stack,
Context: context,
inner: err,
}
}
// A default implementation of the Error method of the error interface.
func DefaultError(e DropboxError) string {
// Find the "original" stack trace, which is probably the most helpful for
// debugging.
errLines := make([]string, 1)
var origStack string
errLines[0] = "ERROR:"
fillErrorInfo(e, &errLines, &origStack)
errLines = append(errLines, "")
errLines = append(errLines, "ORIGINAL STACK TRACE:")
errLines = append(errLines, origStack)
return strings.Join(errLines, "\n")
}
// Fills errLines with all error messages, and origStack with the inner-most
// stack.
func fillErrorInfo(err error, errLines *[]string, origStack *string) {
if err == nil {
return
}
derr, ok := err.(DropboxError)
if ok {
*errLines = append(*errLines, derr.GetMessage())
*origStack = derr.GetStack()
fillErrorInfo(derr.GetInner(), errLines, origStack)
} else {
*errLines = append(*errLines, err.Error())
}
}
// Returns a copy of the error with the stack trace field populated and any
// other shared initialization; skips 'skip' levels of the stack trace.
//
// NOTE: This panics on any error.
func stackTrace(skip int) (current, context string) {
// grow buf until it's large enough to store entire stack trace
buf := make([]byte, 128)
for {
n := runtime.Stack(buf, false)
if n < len(buf) {
buf = buf[:n]
break
}
buf = make([]byte, len(buf)*2)
}
// Returns the index of the first occurrence of '\n' in the buffer 'b'
// starting with index 'start'.
//
// In case no occurrence of '\n' is found, it returns len(b). This
// simplifies the logic on the calling sites.
indexNewline := func(b []byte, start int) int {
if start >= len(b) {
return len(b)
}
searchBuf := b[start:]
index := bytes.IndexByte(searchBuf, '\n')
if index == -1 {
return len(b)
}
return (start + index)
}
// Strip initial levels of stack trace, but keep header line that
// identifies the current goroutine.
var strippedBuf bytes.Buffer
index := indexNewline(buf, 0)
if index != -1 {
strippedBuf.Write(buf[:index])
}
// Skip lines.
for i := 0; i < skip; i++ {
index = indexNewline(buf, index+1)
index = indexNewline(buf, index+1)
}
isDone := false
startIndex := index
lastIndex := index
for !isDone {
index = indexNewline(buf, index+1)
if (index - lastIndex) <= 1 {
isDone = true
} else {
lastIndex = index
}
}
strippedBuf.Write(buf[startIndex:index])
return strippedBuf.String(), string(buf[index:])
}
// This returns the current stack trace string. NOTE: the stack creation code
// is excluded from the stack trace.
func StackTrace() (current, context string) {
return stackTrace(3)
}
// Return a wrapped error or nil if there is none.
func unwrapError(ierr error) (nerr error) {
// Internal errors have a well defined bit of context.
if dbxErr, ok := ierr.(DropboxError); ok {
return dbxErr.GetInner()
}
// At this point, if anything goes wrong, just return nil.
defer func() {
if x := recover(); x != nil {
nerr = nil
}
}()
// Go system errors have a convention but paradoxically no
// interface. All of these panic on error.
errV := reflect.ValueOf(ierr).Elem()
errV = errV.FieldByName("Err")
return errV.Interface().(error)
}
// Keep peeling away layers or context until a primitive error is revealed.
func RootError(ierr error) (nerr error) {
nerr = ierr
for i := 0; i < 20; i++ {
terr := unwrapError(nerr)
if terr == nil {
return nerr
}
nerr = terr
}
return fmt.Errorf("too many iterations: %T", nerr)
}
// Perform a deep check, unwrapping errors as much as possilbe and
// comparing the string version of the error.
func IsError(err, errConst error) bool {
if err == errConst {
return true
}
// Must rely on string equivalence, otherwise a value is not equal
// to its pointer value.
rootErrStr := ""
rootErr := RootError(err)
if rootErr != nil {
rootErrStr = rootErr.Error()
}
errConstStr := ""
if errConst != nil {
errConstStr = errConst.Error()
}
return rootErrStr == errConstStr
}