-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
305 lines (272 loc) · 7.6 KB
/
Copy patherrors.go
File metadata and controls
305 lines (272 loc) · 7.6 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
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
294
295
296
297
298
299
300
301
302
303
304
305
/*
* Copyright (c) 2024 OrigAdmin. All rights reserved.
*/
// Package errors provides enhanced error handling utilities for Go applications.
// It offers:
// - Thread-safe multi-error collection
// - Error chain traversal and inspection
// - Type-safe error assertions
// - Contextual error wrapping
// - Standard error interface compatibility
//
// The package is designed to work seamlessly with standard library errors
// while providing additional functionality for complex error handling scenarios.
package errors
import (
"context"
"errors"
"fmt"
"reflect"
perr "github.com/pkg/errors"
)
//go:generate adptool .
//go:adapter:package github.com/pkg/errors perr
//go:adapter:package:func Is
//go:adapter:package:func:rename IsPkgError
//go:adapter:package:func Unwrap
//go:adapter:package:func:rename UnwrapPkgError
//go:adapter:package:func As
//go:adapter:package:func:rename AsPkgError
//go:adapter:package github.com/hashicorp/go-multierror merr
//go:adapter:package:type *
//go:adapter:package:type:prefix Multi
//go:adapter:package:func *
//go:adapter:package:func:prefix Multi
//go:adapter:package errors stderr
//go:adapter:package:func New
//go:adapter:package:func:rename NewStdError
// ErrorChain represents a chain of errors, where each error in the chain
// can be unwrapped to get the next error. This is compatible with Go 1.13+
// error wrapping.
type ErrorChain interface {
// Unwrap returns the next error in the chain or nil if there are no more errors.
Unwrap() error
// Error returns the string representation of the error.
Error() string
}
// ErrorWithCode is an error that includes an error code.
type ErrorWithCode interface {
error
// Code returns the error code.
Code() int
}
// ErrorWithStack is an error that includes a stack trace.
type ErrorWithStack interface {
error
// StackTrace returns the stack trace associated with the error.
StackTrace() perr.StackTrace
}
// contextualError is an error that carries a context.Context
type contextualError struct {
err error
context context.Context
}
// Error implements the error interface
func (e *contextualError) Error() string { return e.err.Error() }
// Unwrap implements the error unwrapping interface
func (e *contextualError) Unwrap() error { return e.err }
// Context returns the context associated with the error
func (e *contextualError) Context() context.Context { return e.context }
// WalkFunc is the type of the function called for each error in the chain.
// If the function returns a non-nil error, Walk will stop and return that error.
type WalkFunc func(error) error
// Walk traverses the error chain and calls fn for each error in the chain.
// If fn returns an error, Walk stops and returns that error.
//
// Example:
//
// err := fmt.Errorf("root error")
// err = fmt.Errorf("wrapper: %w", err)
//
// err = Walk(err, func(e error) error {
// fmt.Println(e)
// return nil // Continue walking
// })
func Walk(err error, fn WalkFunc) error {
for err != nil {
if err = fn(err); err != nil {
return err
}
// Check for standard unwrapping
if unwrapper, ok := err.(interface{ Unwrap() error }); ok {
err = unwrapper.Unwrap()
continue
}
// Check for multi-error unwrapping
if multi, ok := err.(interface{ Errors() []error }); ok {
for _, e := range multi.Errors() {
if walkErr := Walk(e, fn); walkErr != nil {
return walkErr
}
}
}
// No more errors to unwrap
break
}
return nil
}
// Find traverses the error chain and returns the first error for which the
// provided function returns true.
//
// Example:
//
// // Find the first error of a specific type
// var target *MyError
// if found := Find(err, func(e error) bool {
// return As(e, &target)
// }); found != nil {
// // Handle the found error
// }
func Find(err error, fn func(error) bool) error {
var result error
Walk(err, func(e error) error {
if fn(e) {
result = e
return fmt.Errorf("stop") // Signal to stop walking
}
return nil
})
return result
}
// Has checks if any error in the chain matches the target error using errors.Is.
// It's similar to errors.Is but works with error chains.
//
// Example:
//
// if Has(err, io.EOF) {
// // Handle EOF error
// }
func Has(err, target error) bool {
return Find(err, func(e error) bool {
return errors.Is(e, target)
}) != nil
}
// WithContext adds a context.Context to an error.
// If the error is nil or context is nil, the original error is returned unchanged.
//
// Example:
//
// ctx := context.WithValue(context.Background(), "request_id", "123")
// err := errors.New("operation failed")
// err = WithContext(ctx, err)
func WithContext(ctx context.Context, err error) error {
if err == nil || ctx == nil {
return err
}
return &contextualError{
err: err,
context: ctx,
}
}
// ContextFrom retrieves the context.Context from an error if it exists.
// Returns the context and true if found, nil and false otherwise.
//
// Example:
//
// if ctx, ok := ContextFrom(err); ok {
// // Use ctx
// }
func ContextFrom(err error) (context.Context, bool) {
if e, ok := err.(*contextualError); ok {
return e.context, true
}
return nil, false
}
// Value retrieves a value from the error's context.
// It traverses the error chain to find a context that contains the key.
//
// Example:
//
// if val := Value(err, "request_id"); val != nil {
// // Use val
// }
func Value(err error, key interface{}) interface{} {
var result interface{}
Walk(err, func(e error) error {
if e, ok := e.(*contextualError); ok {
if val := e.context.Value(key); val != nil {
result = val
return fmt.Errorf("stop") // Stop walking after first match
}
}
return nil
})
return result
}
// AssertType checks if the error is of the specified type and returns it.
// It works with both value and pointer types.
//
// Example:
//
// // For non-pointer types
// if e, ok := AssertType[MyError](err); ok {
// // Use e which is of type MyError
// }
//
// // For pointer types
// if e, ok := AssertType[*MyError](err); ok {
// // Use e which is of type *MyError
// }
func AssertType[T error](err error) (T, bool) {
var zero T
// Handle nil error
if err == nil {
return zero, false
}
// Check if the error is directly of type T
if e, ok := err.(T); ok {
return e, true
}
// Handle pointer vs value type comparison
if reflect.TypeOf(zero) != nil && reflect.TypeOf(err).AssignableTo(reflect.TypeOf(zero)) {
return reflect.ValueOf(err).Convert(reflect.TypeOf(zero)).Interface().(T), true
}
// Check if the error is in the chain
var target T
if errors.As(err, &target) {
return target, true
}
return zero, false
}
// MustAssertType is like AssertType but panics if the error is not of the specified type.
// Use with caution, only when you're certain about the error type.
//
// Example:
//
// // Will panic if err is not of type *MyError
// e := MustAssertType[*MyError](err)
func MustAssertType[T error](err error) T {
e, ok := AssertType[T](err)
if !ok {
panic(fmt.Sprintf("expected error of type %T, got %T", *new(T), err))
}
return e
}
// IsType checks if the error is of the specified type.
// It's a type-safe alternative to errors.As.
//
// Example:
//
// if IsType[MyError](err) {
// // Handle MyError
// }
func IsType[T error](err error) bool {
_, ok := AssertType[T](err)
return ok
}
// Helper function to create a new error with context
//
// Example:
//
// err := NewWithContext(context.Background(), "operation failed")
// // Add more context
// err = fmt.Errorf("additional context: %w", err)
func NewWithContext(ctx context.Context, text string) error {
if ctx == nil {
return errors.New(text)
}
return &contextualError{
err: errors.New(text),
context: ctx,
}
}