-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy patherrors.go
More file actions
241 lines (210 loc) · 5.7 KB
/
errors.go
File metadata and controls
241 lines (210 loc) · 5.7 KB
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
// Error recorder with As
//
// recoding one code, and recording the As caller static with caller, argument information for every As func is called.
//
// the data static format like this:
// ["error code", ["runtime stack of New"], ["runtime stack of As", "args of As"...], ["runtime statick of As"]...]
// the first one is error code, the second is New, the others are func As been called.
//
// # Example
//
// package main
//
// import "github.com/gwaylib/errors"
//
// func fn1(a int) error {
// if a == 1 {
// return errors.ErrNoData.As(a)
// }
// err := errors.New("not implements") // make a error code and record the first stack of caller runtime.
// return err.As(a) // make the second stack of caller runtime
// }
//
// func fn2(b int) error {
// return errors.As(fn1(b)) // make the third stack of caller runtime
// }
//
// func main() {
// err := fn2(2)
// if err != nil {
// // errors.ErrNoData == err not necessarily true, so use Equal instead.
// if !errors.ErrNoData.Equal(err) {
// panic(err)
// }
//
// fmt.Println(err)
// }
// }
package errors
import (
"encoding/json"
"errors"
"fmt"
"runtime"
"strings"
)
var (
ErrNoData = New("data not found")
)
type Error interface {
// Return the code of make.
Code() string
// Implement the error interface of go package
Error() string
// Impelment the json marshal interface of go package.
MarshalJSON() ([]byte, error)
// Record the stack when call, and return a new error with new stack.
As(arg ...interface{}) Error
// Copy the as stack data for output
Stack() []interface{}
// Compare to another error
// It should be established with err1.Code() == err2.Code().
Equal(err error) bool
}
// Compare two error are same instances or code are matched.
func Equal(err1 error, err2 error) bool {
return equal(err1, err2)
}
// Alias name of Equal func, compatible with official errors.Is
func Is(err1 error, err2 error) bool {
return equal(err1, err2)
}
func equal(err1 error, err2 error) bool {
// Memory compare
if err1 == err2 {
return true
}
if err1 == nil || err2 == nil {
return false
}
// checking the standard package errors
if errors.Is(err1, err2) {
return true
}
// parse the error and compare the code, net transfer the error would be serial by Error() function.
eImpl1, eImpl2 := ParseError(err1), ParseError(err2)
return eImpl1.Code() == eImpl2.Code()
}
// ["error code", ["where stack of first caller ", "As args"...], ["where stack of second caller ", "As args"...]...]
type ErrData []interface{}
type errImpl struct {
data ErrData // not export the data to keep it read only.
}
// Make a new error with Error type.
func New(code string, args ...interface{}) Error {
stack := make([]interface{}, len(args)+1)
stack[0] = caller(2)
copy(stack[1:], args)
return &errImpl{[]interface{}{code, stack}}
}
// Parse error from serial string, if it's ErrData format, create an Error of this package defined.
// if src is empty, return a nil Error
func Parse(src string) Error {
if len(src) == 0 {
return nil
}
return parse(src)
}
// Parse Error from a error instance.
// If the error is the type of interface Error, directly convert to the Error interface of this package.
// Call Parse(err.Error()) in others.
func ParseError(err error) Error {
if err == nil {
return nil
}
if e, ok := err.(*errImpl); ok {
return e
}
return parse(err.Error())
}
func as(depth int, err error, args ...interface{}) Error {
if err == nil {
return nil
}
e := ParseError(err).(*errImpl)
stack := make([]interface{}, len(args)+1)
stack[0] = caller(depth)
copy(stack[1:], args)
data := make([]interface{}, len(e.data)+1)
copy(data, e.data)
data[len(data)-1] = stack
return &errImpl{data: data}
}
// Record a stack of runtime caller and the reason with as.
// return a new error pointer after called.
// return nil if err is nil
func As(err error, args ...interface{}) Error {
return as(3, err, args...)
}
// Alias name of 'As'
func Wrap(err error, args ...interface{}) Error {
return as(3, err, args...)
}
func parse(src string) *errImpl {
if len(src) == 0 {
return nil
}
if src[0] != '[' {
return New(src).(*errImpl)
}
data := ErrData{}
if err := json.Unmarshal([]byte(src), &data); err != nil {
return New(src).(*errImpl)
}
return &errImpl{data: data}
}
// call for domain
func caller(depth int) string {
at := ""
pc, file, line, ok := runtime.Caller(depth)
if !ok {
at = "caller is false"
}
me := runtime.FuncForPC(pc)
if me == nil {
at = "pc of caller is not set"
}
fileFields := strings.Split(file, "/")
if len(fileFields) < 1 {
at = "file of caller is not named"
return at
}
funcFields := strings.Split(me.Name(), "/")
if len(funcFields) < 1 {
at = "func of caller is not named"
return at
}
fileName := strings.Join(fileFields[len(fileFields)-1:], "/")
funcName := strings.Join(funcFields[len(funcFields)-1:], "/")
return fmt.Sprintf("%s:%d#%s", fileName, line, funcName)
}
// Return the code of New or Parse.
func (e *errImpl) Code() string {
return e.data[0].(string)
}
// Copy and return the stack array
func (e *errImpl) Stack() []interface{} {
stack := make([]interface{}, len(e.data)-1)
copy(stack, e.data[1:])
return stack
}
// Implement the error interface of go package
func (e *errImpl) Error() string {
data, err := json.Marshal(e.data)
if err != nil {
return fmt.Sprintf("%+v", e.data)
}
return string(data)
}
// Impelment the json marshal interface of go package.
func (e *errImpl) MarshalJSON() ([]byte, error) {
return json.Marshal(e.data)
}
// Record caller stack and return a new error interface.
func (e *errImpl) As(args ...interface{}) Error {
return as(3, e, args...)
}
// Compare to another error
func (e *errImpl) Equal(l error) bool {
return equal(e, l)
}