-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelpers.go
More file actions
115 lines (104 loc) · 3.09 KB
/
helpers.go
File metadata and controls
115 lines (104 loc) · 3.09 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
// helpers.go: Helpers functions for the go-errors AGILira library
//
// Copyright (c) 2025 AGILira - A. Giordano
// Series: an AGLIra library
// SPDX-License-Identifier: MPL-2.0
package errors
import (
"errors"
"fmt"
"github.com/agilira/go-timecache"
)
// Wrap wraps an existing error with a new code and message, capturing the current stack trace.
// This is useful for adding context to errors that occur deeper in the call stack.
// If code is empty or whitespace-only, DefaultErrorCode will be used instead.
//
// Example:
//
// if err := someOperation(); err != nil {
// return Wrap(err, "OPERATION_FAILED", "Failed to process user data")
// }
func Wrap(err error, code ErrorCode, message string) *Error {
if !validateErrorCode(code) {
code = DefaultErrorCode
}
return &Error{
Code: code,
Message: message,
Timestamp: timecache.CachedTime(),
Severity: SeverityError,
Cause: err,
Context: make(map[string]interface{}),
Stack: CaptureStacktrace(1),
}
}
// Error implements the error interface for *Error.
// It returns a formatted string containing the error code and message.
func (e *Error) Error() string {
return fmt.Sprintf("[%s]: %s", e.Code, e.Message)
}
// Unwrap returns the underlying cause error, implementing the error wrapping interface.
func (e *Error) Unwrap() error {
return e.Cause
}
// RootCause returns the original error in the error chain by unwrapping all nested errors.
// This is useful for finding the root cause of an error that has been wrapped multiple times.
func RootCause(err error) error {
for {
unwrapped := errors.Unwrap(err)
if unwrapped == nil {
return err
}
err = unwrapped
}
}
// HasCode checks if any error in the error chain has the given error code.
// This is useful for checking if a specific type of error occurred anywhere in the chain.
//
// Example:
//
// if HasCode(err, "VALIDATION_ERROR") {
// // Handle validation-specific error
// log.Warning("Validation failed", "error", err)
// }
func HasCode(err error, code ErrorCode) bool {
for err != nil {
if ec, ok := err.(*Error); ok && ec.Code == code {
return true
}
err = errors.Unwrap(err)
}
return false
}
// Is implements errors.Is compatibility for error comparison.
// It returns true if the target error has the same error code.
func (e *Error) Is(target error) bool {
if target == nil {
return false
}
if te, ok := target.(*Error); ok {
return e.Code == te.Code
}
return false
}
// As implements errors.As compatibility for error type assertion.
// It delegates the check to the underlying Cause error.
// Note: It will not match the *Error instance itself, only errors in its cause chain.
func (e *Error) As(target interface{}) bool {
return errors.As(e.Cause, target)
}
// validateErrorCode checks if an ErrorCode is valid (non-empty).
// Returns true if the code is valid, false if empty or whitespace-only.
func validateErrorCode(code ErrorCode) bool {
s := string(code)
if len(s) == 0 {
return false
}
// Check if it's only whitespace
for _, r := range s {
if r != ' ' && r != '\t' && r != '\n' && r != '\r' {
return true
}
}
return false
}