-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patherror.go
224 lines (193 loc) · 5.62 KB
/
error.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
// Package e is an error-handling package designed to be simple, safe, and
// compatible.
package e
import (
"errors"
"fmt"
"runtime"
"runtime/debug"
"strings"
)
// Error represents a standard application error.
// Implements ClientFacing and HasStacktrace so it can be introspected
// with functions like ErrorCode, ErrorMessage, and ErrorStacktrace.
type Error interface {
error
ClientFacing
HasStacktrace
Unwrap() error
// SetCode adds an error type to a non-nil Error such as "unexpected_error",
// "database_error", "not_exists", etc.
//
// Will panic when used with a nil Error receiver.
SetCode(code string) Error
// SetMessage adds a user-friendly message to a non-nil Error.
// Message will not be printed with Error() and should be retrieved with ErrorMessage().
//
// Will panic when used with a nil Error receiver.
SetMessage(message string) Error
}
// NewError constructs a new Error. code should be a short, single string
// describing the type of error (typically a pre-defined const). cause is used
// to create the nested error which will act as the root of the error stack.
//
// Usage:
// func Foo(bar *Bar) error {
// if bar == nil {
// return e.New("unexpected_error", "bar is nil")
// }
// return doFoo(bar)
// }
//
func NewError(code, cause string) Error {
return errorImpl{
op: getCallingFunc(2),
code: code,
err: errors.New(cause),
stacktrace: string(debug.Stack()),
}
}
// NewErrorf constructs a new Error with formatted string. code should be a short,
// single string describing the type of error (typically a pre-defined const).
// cause is used to create the nested error which will act as the root of the error stack.
//
// Usage:
// func Foo(bar Bar) error {
// done := doFoo(bar)
// if !done {
// return e.NewErrorf("unexpected_error", "cannot process bar: %v", bar)
// }
// return nil
// }
//
func NewErrorf(code, fmtCause string, args ...interface{}) Error {
return errorImpl{
op: getCallingFunc(2),
code: code,
err: fmt.Errorf(fmtCause, args...),
stacktrace: string(debug.Stack()),
}
}
// Wrap adds the name of the calling function to the wrapped error.
// OptionalInfo can be passed to insert more context at the wrap site.
// Only the first OptionalInfo string will be used.
//
// Basic usage:
// err := Foo()
// if err != nil {
// return e.Wrap(err)
// }
//
// Adding additional info:
// err := Foo()
// if err != nil {
// return e.Wrap(err, fmt.Sprintf("cannot find id: %v", id))
// }
//
func Wrap(err error, optionalInfo ...string) Error {
if err == nil {
return nil
}
innerErr := err
if len(optionalInfo) > 0 {
innerErr = fmt.Errorf("(%v): %w", optionalInfo[0], err) // localizer.Ignore
}
wrapped := errorImpl{
op: getCallingFunc(2),
err: innerErr,
stacktrace: ErrorStacktrace(err),
}
if wrapped.stacktrace == "" {
wrapped.stacktrace = string(debug.Stack())
}
return wrapped
}
// Wrapf adds the name of the calling function and a formatted message
// to the wrapped error.
//
// Basic usage:
// err := Foo(bar)
// if err != nil {
// return e.Wrapf(err, "bar not found: %v", bar.Id)
// }
//
func Wrapf(err error, fmtInfo string, args ...interface{}) Error {
if err == nil {
return nil
}
wrapped := errorImpl{
op: getCallingFunc(2),
err: fmt.Errorf("(%v): %w", fmt.Sprintf(fmtInfo, args...), err), // localizer.Ignore
stacktrace: ErrorStacktrace(err),
}
if wrapped.stacktrace == "" {
wrapped.stacktrace = string(debug.Stack())
}
return wrapped
}
// errorImpl should always have a non-nil nested err and therefore this type
// cannot by itself be the true root of an error stack.
type errorImpl struct {
// Operation being performed--populated at runtime automagically
op string
// Represents the error type to be used by client or application.
// e.g. "unexpected_error", "database_error", "not_exists" etc.
// Use ErrorCode(err) to retrieve the outermost code.
code string
// A user-friendly error message. Does not get printed with Error().
// Use ErrorMessage(err) to retrieve the outermost message.
message string
// Nested error for building an error stacktrace. Should not be nil.
err error
// Internal stacktrace for logging. Does not get printed with Error().
// Use ErrorStacktrace(err) to retrieve the innermost stacktrace.
stacktrace string
}
func (e errorImpl) Error() string {
var sb strings.Builder
if e.op != "" {
sb.WriteString(fmt.Sprintf("%s: ", e.op))
}
if e.code != "" {
sb.WriteString(fmt.Sprintf("[%s] ", e.code)) // localizer.Ignore
}
sb.WriteString(e.err.Error())
return sb.String()
}
func (e errorImpl) Unwrap() error {
return e.err
}
func (e errorImpl) ClientCode() string {
return e.code
}
func (e errorImpl) ClientMessage() string {
return e.message
}
func (e errorImpl) SetCode(code string) Error {
e.code = code
return e
}
func (e errorImpl) SetMessage(message string) Error {
e.message = message
return e
}
func (e errorImpl) Stacktrace() string {
return e.stacktrace
}
// getCallingFunc returns the name of the calling function N levels
// above getCallingFunc (e.g. 0 for `getCallingFunc` itself)
func getCallingFunc(frameOffset int) string {
// only need len = 1 to contain the calling function
programCounters := make([]uintptr, 1)
// base offset is 1 to skip `runtime.Callers` itself
n := runtime.Callers(1+frameOffset, programCounters)
if n == 0 {
return "unknown"
}
frames := runtime.CallersFrames(programCounters)
frame, _ := frames.Next()
// Remove package name (too verbose)
ss := strings.Split(frame.Function, "/")
funcname := ss[len(ss)-1]
return strings.SplitAfterN(funcname, ".", 2)[1]
}