-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
236 lines (203 loc) · 5.46 KB
/
errors.go
File metadata and controls
236 lines (203 loc) · 5.46 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
package errors
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"runtime"
)
type internalError struct {
Kind string `json:"kind"`
Message string `json:"message"`
Annotations map[string]any `json:"annotations,omitempty"`
Stacktrace string `json:"stacktrace,omitempty"`
Parent error `json:"parent,omitempty"`
}
// Error implements the error interface for internalError.
func (e *internalError) Error() string {
if e.Kind == "" {
return e.Message
}
return "[" + e.Kind + "] " + e.Message
}
// Unwrap implements the Unwrap method for internalError.
func (e *internalError) Unwrap() error {
return e.Parent
}
func (e *internalError) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
b, err := json.Marshal(e)
if err != nil {
panic(err)
}
fmt.Fprint(s, string(b))
return
}
fallthrough
case 's':
io.WriteString(s, e.Error())
case 'q':
fmt.Fprintf(s, "%q", e.Error())
}
}
func (e *internalError) LogValue() slog.Value {
attrs := []slog.Attr{}
if e.Kind != "" {
attrs = append(attrs, slog.String("kind", e.Kind))
}
if e.Message != "" {
attrs = append(attrs, slog.String("message", e.Message))
}
if len(e.Annotations) > 0 {
annotations := []slog.Attr{}
for k, v := range e.Annotations {
annotations = append(annotations, slog.Any(k, v))
}
attrs = append(attrs, slog.Any("annotations", slog.GroupValue(annotations...)))
}
if e.Stacktrace != "" {
attrs = append(attrs, slog.String("stacktrace", e.Stacktrace))
}
if e.Parent != nil {
attrs = append(attrs, slog.String("parent", e.Parent.Error()))
}
return slog.GroupValue(attrs...)
}
func internalNew(msg string, opts ...func(*internalError)) *internalError {
err := &internalError{
Kind: "-",
Message: msg,
Stacktrace: stackFromCallers(),
}
for _, opt := range opts {
opt(err)
}
return err
}
func stackFromCallers() string {
// get callers, skipping the first 4 frames, as those frames contain the errors-internals
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(4, pcs[:])
// get frames for the callers and build stacktrace
frames := runtime.CallersFrames(pcs[0:n])
var stacktrace string
for {
frame, more := frames.Next()
if !more {
break
}
stacktrace += fmt.Sprintf("%s\n\t%s:%d\n", frame.Function, frame.File, frame.Line)
}
return stacktrace
}
// New creates a new error with the given kind and message.
// The stacktrace is captured at the time of creation.
// The kind is used to categorize the error, and the message is used to
// provide additional context about the error.
func New(msg string, opts ...func(*internalError)) error {
return internalNew(msg, opts...)
}
func WithKind(kind string) func(*internalError) {
return func(ie *internalError) {
ie.Kind = kind
}
}
func WithAnnotation(key string, value any) func(*internalError) {
return func(ie *internalError) {
if ie.Annotations == nil {
ie.Annotations = make(map[string]any)
}
ie.Annotations[key] = value
}
}
func WithMessage(msg string) func(*internalError) {
return func(ie *internalError) {
ie.Message = msg
}
}
// Wrap creates a new error that wraps the given parent error.
// The stacktrace is captured at the time of creation.
// The kind is used to categorize the error.
// If the error is already wrapped we return the existing error
func Wrap(parent error, opts ...func(*internalError)) error {
internalErr, ok := parent.(*internalError)
if ok {
return internalErr
}
err := internalNew(parent.Error())
err.Parent = parent
for _, opt := range opts {
opt(err)
}
return err
}
// Annotate adds a key-value pair to the error's annotations.
// Annotations are used to provide additional context about the error.
// If the error is nil, it returns nil.
// If the error is not of type *internalError, it first Wraps the error.
func Annotate(err error, key string, value any) error {
if err == nil {
return nil
}
internalErr, ok := err.(*internalError)
if !ok {
internalErr = internalNew(err.Error())
internalErr.Parent = err
}
if internalErr.Annotations == nil {
internalErr.Annotations = make(map[string]any)
}
internalErr.Annotations[key] = value
return internalErr
}
// GetAnnotations returns all annotations of the specified error and a boolean indicating if they exist
func GetAnnotations(err error) (map[string]any, bool) {
internalErr, ok := err.(*internalError)
if !ok {
return nil, false
}
if len(internalErr.Annotations) == 0 {
return nil, false
}
return internalErr.Annotations, true
}
// HasAnnotation checks if the error has a specific annotation attached
func HasAnnotation(err error, key string) bool {
internalErr, ok := err.(*internalError)
if !ok {
return false
}
_, exists := internalErr.Annotations[key]
return exists
}
// GetAnnotation returns an annotation by key and a boolean indicating if the annotation exists
func GetAnnotation(err error, key string) (any, bool) {
internalErr, ok := err.(*internalError)
if !ok {
return nil, false
}
value, exists := internalErr.Annotations[key]
if !exists {
return nil, false
}
return value, true
}
// HasKind checks if the error has a kind set
func HasKind(err error) bool {
internalErr, ok := err.(*internalError)
if !ok {
return false
}
return internalErr.Kind != ""
}
// GetKind returns the kind of the error and a boolean indicating if it is set
func GetKind(err error) (string, bool) {
internalErr, ok := err.(*internalError)
if !ok {
return "", false
}
return internalErr.Kind, true
}