-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patherrwrap.go
214 lines (192 loc) · 4.93 KB
/
errwrap.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
package errwrap
import (
"errors"
"fmt"
"io"
"runtime"
"strconv"
)
// baseError represents pure error, so it does not prorivde wrapping.
// Other functions are same as WrappedError, e.g. StackTrace, Generic assertion.
type baseError[T any] struct {
Message string
pc uintptr
}
func (e *baseError[T]) counter() uintptr {
return e.pc
}
func (e *baseError[T]) Error() string {
return e.Message
}
func (e *baseError[T]) Stack() []uintptr {
return []uintptr{e.pc}
}
func (e *baseError[T]) Format(s fmt.State, verb rune) {
switch verb {
case 'v', 's':
switch {
case s.Flag('+'):
fn := runtime.FuncForPC(e.pc)
file, line := fn.FileLine(e.pc)
name := fn.Name()
io.WriteString(s, e.Error())
io.WriteString(s, "\n")
io.WriteString(s, name)
io.WriteString(s, "\n\t")
io.WriteString(s, file)
io.WriteString(s, ":")
io.WriteString(s, strconv.Itoa(line))
default:
io.WriteString(s, e.Error())
}
default:
io.WriteString(s, "{%!")
io.WriteString(s, string(verb))
io.WriteString(s, "(error=")
io.WriteString(s, e.Error())
io.WriteString(s, ")}")
}
}
// wrappedError wraps an error and stores its stacks.
// It also has a generic to be used in assertion.
// Generic is usually set as a component where it's called.
type wrappedError[T any] struct {
Inner error
Message string
pc uintptr
}
func (e *wrappedError[T]) counter() uintptr {
return e.pc
}
func (e *wrappedError[T]) Error() string {
if e.Inner == nil {
return e.Message
}
return e.Message + ": " + e.Inner.Error()
}
func (e *wrappedError[T]) Unwrap() error { return e.Inner }
// Overrides Format to format itself.
//
// %+v and %+s print stack in detail.
func (e *wrappedError[T]) Format(s fmt.State, verb rune) {
switch verb {
case 'v', 's':
switch {
case s.Flag('+'):
io.WriteString(s, e.Error())
err := error(e)
for {
// Format detail if it's an instance of wrappedError.
// Printed shape is like below:
//
// [package].[function] ([error])
// /[path]/[file].go:[line]
//
// If it's not an instance of wrappedError, then it's like:
//
// [untraceable]
// [error]
if counterable, ok := err.(counterer); ok {
pc := counterable.counter()
fn := runtime.FuncForPC(pc)
file, line := fn.FileLine(pc)
name := fn.Name()
io.WriteString(s, "\n")
io.WriteString(s, name)
io.WriteString(s, " (")
io.WriteString(s, err.Error())
io.WriteString(s, ")\n\t")
io.WriteString(s, file)
io.WriteString(s, ":")
io.WriteString(s, strconv.Itoa(line))
} else {
io.WriteString(s, "\n")
io.WriteString(s, untraceable)
io.WriteString(s, "\n\t")
io.WriteString(s, err.Error())
}
// It can keep formatting after untraceable error if it's unwrappable.
// On the other hand, it only can work when the latest error is an instance
// of wrappedError.
if unwrappable, ok := err.(unwrapper); ok {
err = unwrappable.Unwrap()
} else {
break
}
}
default:
io.WriteString(s, e.Error())
}
default:
io.WriteString(s, "{%!")
io.WriteString(s, string(verb))
io.WriteString(s, "(error=")
io.WriteString(s, e.Error())
io.WriteString(s, ")}")
}
}
// updatePC updates the last stack to caller with the given skip.
func (e *wrappedError[T]) updatePC(skip int) {
e.pc = caller(skip)
}
// factory returns two functions which are a wrapper and an assertor.
// wrapper can wrap an error and append the error stack.
// assertor does assertion if it is possible.
func factory[T any](message string) (w Wrapper, a Assertor[T]) {
w = func(err error) error {
if err == nil {
return nil
}
return &wrappedError[T]{
Message: message,
Inner: err,
pc: caller(2),
}
}
a = func(err error) (*wrappedError[T], bool) {
var (
target *wrappedError[T]
ok = errors.As(err, &target)
)
return target, ok
}
return
}
// Factory must be typed with a generic and gets a message.
// This returns a wrapper and an assertor that can be used for wrapping and an assertion.
func Factory[T any](message string) (Wrapper, Assertor[T]) {
return factory[T](message)
}
// Factoryf is same as Factory and the only difference is that it has a format specifier for the message.
func Factoryf[T any](message string, args ...any) (Wrapper, Assertor[T]) {
return factory[T](fmt.Sprintf(message, args...))
}
// WithChecker attaches a checker to the given wrapper.
// Checker is able to check the given error is the type of the wrapper.
func WithChecker(wrapper *Wrapper, size ...int) Checker {
var sz int
if len(size) > 0 {
sz = size[0]
}
var (
errs = make(map[error]bool, sz)
_wrapper = *wrapper // store previous
)
*wrapper = func(err error) error {
if err == nil {
return nil
}
werr := _wrapper(err)
werr.(updater).updatePC(3)
errs[Innermost(err)] = true
return werr
}
return func(err error) bool {
for k := range errs {
if errors.Is(err, k) {
return true
}
}
return false
}
}