mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-30 09:38:26 -05:00
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.8 to 1.0.9. - [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.8...v1.0.9) --- updated-dependencies: - dependency-name: github.com/olekukonko/tablewriter dependency-version: 1.0.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
1497 lines
39 KiB
Go
1497 lines
39 KiB
Go
// Package errors provides a robust error handling library with support for
|
||
// error wrapping, stack traces, context storage, and retry mechanisms. It extends
|
||
// the standard library's error interface with features like HTTP-like status codes,
|
||
// error categorization, and JSON serialization, while maintaining compatibility
|
||
// with `errors.Is`, `errors.As`, and `errors.Unwrap`. The package is thread-safe
|
||
// and optimized with object pooling for performance.
|
||
package errors
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"regexp"
|
||
"runtime"
|
||
"strings"
|
||
"sync"
|
||
"sync/atomic"
|
||
)
|
||
|
||
// Constants defining default configuration and context keys.
|
||
const (
|
||
ctxTimeout = "[error] timeout" // Context key marking timeout errors.
|
||
ctxRetry = "[error] retry" // Context key marking retryable errors.
|
||
|
||
contextSize = 4 // Initial size of fixed-size context array for small contexts.
|
||
bufferSize = 256 // Initial buffer size for JSON marshaling.
|
||
warmUpSize = 100 // Number of errors to pre-warm the pool for efficiency.
|
||
stackDepth = 32 // Maximum stack trace depth to prevent excessive memory use.
|
||
|
||
DefaultCode = 500 // Default HTTP status code for errors if not specified.
|
||
)
|
||
|
||
// spaceRe is a precompiled regex for normalizing whitespace in error messages.
|
||
var spaceRe = regexp.MustCompile(`\s+`)
|
||
|
||
// ErrorCategory is a string type for categorizing errors (e.g., "network", "validation").
|
||
type ErrorCategory string
|
||
|
||
// ErrorOpts provides options for customizing error creation.
|
||
type ErrorOpts struct {
|
||
SkipStack int // Number of stack frames to skip when capturing the stack trace.
|
||
}
|
||
|
||
// Config defines the global configuration for the errors package, controlling
|
||
// stack depth, context size, pooling, and frame filtering.
|
||
type Config struct {
|
||
StackDepth int // Maximum stack trace depth; 0 uses default (32).
|
||
ContextSize int // Initial context map size; 0 uses default (4).
|
||
DisablePooling bool // If true, disables object pooling for errors.
|
||
FilterInternal bool // If true, filters internal package frames from stack traces.
|
||
AutoFree bool // If true, automatically frees errors to pool after use.
|
||
}
|
||
|
||
// cachedConfig holds the current configuration, updated only by Configure().
|
||
// Protected by configMu for thread-safety.
|
||
type cachedConfig struct {
|
||
stackDepth int
|
||
contextSize int
|
||
disablePooling bool
|
||
filterInternal bool
|
||
autoFree bool
|
||
}
|
||
|
||
var (
|
||
// currentConfig stores the active configuration, read frequently and updated rarely.
|
||
currentConfig cachedConfig
|
||
// configMu protects updates to currentConfig for thread-safety.
|
||
configMu sync.RWMutex
|
||
// errorPool manages reusable Error instances to reduce allocations.
|
||
errorPool = NewErrorPool()
|
||
// stackPool manages reusable stack trace slices for efficiency.
|
||
stackPool = sync.Pool{
|
||
New: func() interface{} {
|
||
return make([]uintptr, currentConfig.stackDepth)
|
||
},
|
||
}
|
||
// emptyError is a pre-allocated empty error for lightweight reuse.
|
||
emptyError = &Error{
|
||
smallContext: [contextSize]contextItem{},
|
||
msg: "",
|
||
name: "",
|
||
template: "",
|
||
cause: nil,
|
||
}
|
||
)
|
||
|
||
// contextItem holds a single key-value pair in the smallContext array.
|
||
type contextItem struct {
|
||
key string
|
||
value interface{}
|
||
}
|
||
|
||
// Error is a custom error type with enhanced features: message, name, stack trace,
|
||
// context, cause, and metadata like code and category. It is thread-safe and
|
||
// supports pooling for performance.
|
||
type Error struct {
|
||
// Primary fields (frequently accessed).
|
||
msg string // The error message displayed by Error().
|
||
name string // The error name or type (e.g., "AuthError").
|
||
stack []uintptr // Stack trace as program counters.
|
||
|
||
// Secondary metadata.
|
||
template string // Fallback message template if msg is empty.
|
||
category string // Error category (e.g., "network").
|
||
count uint64 // Occurrence count for tracking frequency.
|
||
code int32 // HTTP-like status code (e.g., 400, 500).
|
||
smallCount int32 // Number of items in smallContext.
|
||
|
||
// Context and chaining.
|
||
context map[string]interface{} // Key-value pairs for additional context.
|
||
cause error // Wrapped underlying error for chaining.
|
||
callback func() // Optional callback invoked by Error().
|
||
smallContext [contextSize]contextItem // Fixed-size array for small contexts.
|
||
|
||
// Synchronization.
|
||
mu sync.RWMutex // Protects mutable fields (context, smallContext).
|
||
|
||
// Internal flags.
|
||
formatWrapped bool // True if created by Newf with %w verb.
|
||
}
|
||
|
||
// init sets up the package with default configuration and pre-warms the error pool.
|
||
func init() {
|
||
currentConfig = cachedConfig{
|
||
stackDepth: stackDepth,
|
||
contextSize: contextSize,
|
||
disablePooling: false,
|
||
filterInternal: true,
|
||
autoFree: true,
|
||
}
|
||
WarmPool(warmUpSize) // Pre-allocate errors for performance.
|
||
}
|
||
|
||
// Configure updates the global configuration for the errors package.
|
||
// It is thread-safe and should be called early to avoid race conditions.
|
||
// Changes apply to all subsequent error operations.
|
||
// Example:
|
||
//
|
||
// errors.Configure(errors.Config{StackDepth: 16, DisablePooling: true})
|
||
func Configure(cfg Config) {
|
||
configMu.Lock()
|
||
defer configMu.Unlock()
|
||
|
||
if cfg.StackDepth != 0 {
|
||
currentConfig.stackDepth = cfg.StackDepth
|
||
}
|
||
if cfg.ContextSize != 0 {
|
||
currentConfig.contextSize = cfg.ContextSize
|
||
}
|
||
currentConfig.disablePooling = cfg.DisablePooling
|
||
currentConfig.filterInternal = cfg.FilterInternal
|
||
currentConfig.autoFree = cfg.AutoFree
|
||
}
|
||
|
||
// newError creates a new Error instance, reusing from the pool if enabled.
|
||
// Initializes smallContext and sets stack to nil.
|
||
// Internal use; prefer New, Named, or Trace for public API.
|
||
func newError() *Error {
|
||
if currentConfig.disablePooling {
|
||
return &Error{
|
||
smallContext: [contextSize]contextItem{},
|
||
stack: nil,
|
||
}
|
||
}
|
||
return errorPool.Get()
|
||
}
|
||
|
||
// Empty returns a new empty error with no message, name, or stack trace.
|
||
// Useful for incrementally building errors or as a neutral base.
|
||
// Example:
|
||
//
|
||
// err := errors.Empty().With("key", "value").WithCode(400)
|
||
func Empty() *Error {
|
||
return emptyError
|
||
}
|
||
|
||
// Named creates an error with the specified name and captures a stack trace.
|
||
// The name doubles as the error message if no message is set.
|
||
// Use for errors where type identification and stack context are important.
|
||
// Example:
|
||
//
|
||
// err := errors.Named("AuthError").WithCode(401)
|
||
func Named(name string) *Error {
|
||
e := newError()
|
||
e.name = name
|
||
return e.WithStack()
|
||
}
|
||
|
||
// New creates a lightweight error with the given message and no stack trace.
|
||
// Optimized for performance; use Trace() for stack traces.
|
||
// Returns a shared empty error for empty messages to reduce allocations.
|
||
// Example:
|
||
//
|
||
// err := errors.New("invalid input")
|
||
func New(text string) *Error {
|
||
if text == "" {
|
||
return emptyError.Copy() // Avoid modifying shared instance.
|
||
}
|
||
err := newError()
|
||
err.msg = text
|
||
return err
|
||
}
|
||
|
||
// Newf creates a formatted error, supporting the %w verb for wrapping errors.
|
||
// If the format contains exactly one %w verb with a non-nil error argument,
|
||
// the error is wrapped as the cause. The final error message string generated
|
||
// by Error() will be compatible with the output of fmt.Errorf for the same inputs.
|
||
// Does not capture a stack trace by default.
|
||
// Example:
|
||
//
|
||
// cause := errors.New("db error")
|
||
// err := errors.Newf("query failed: %w", cause)
|
||
// // err.Error() will match fmt.Errorf("query failed: %w", cause).Error()
|
||
// // errors.Unwrap(err) == cause
|
||
func Newf(format string, args ...interface{}) *Error {
|
||
err := newError()
|
||
|
||
// --- Start: Parsing and Validation (mostly unchanged) ---
|
||
var wCount int
|
||
var wArgPos = -1
|
||
var wArg error
|
||
var validationErrorMsg string
|
||
argPos := 0
|
||
runes := []rune(format)
|
||
i := 0
|
||
parsingOk := true
|
||
var fmtVerbs []struct {
|
||
isW bool
|
||
spec string // The full verb specifier or literal segment
|
||
argIdx int // Index in the original 'args' slice, -1 for literals/%%
|
||
}
|
||
|
||
// Parse format string to identify verbs and literals.
|
||
for i < len(runes) && parsingOk {
|
||
segmentStart := i
|
||
if runes[i] == '%' {
|
||
if i+1 >= len(runes) {
|
||
parsingOk = false
|
||
validationErrorMsg = "ends with %"
|
||
break
|
||
}
|
||
if runes[i+1] == '%' {
|
||
fmtVerbs = append(fmtVerbs, struct {
|
||
isW bool
|
||
spec string
|
||
argIdx int
|
||
}{isW: false, spec: "%%", argIdx: -1})
|
||
i += 2
|
||
continue
|
||
}
|
||
i++ // Move past '%'
|
||
// Parse flags, width, precision (simplified loop)
|
||
for i < len(runes) && strings.ContainsRune("+- #0", runes[i]) {
|
||
i++
|
||
}
|
||
for i < len(runes) && ((runes[i] >= '0' && runes[i] <= '9') || runes[i] == '.') {
|
||
i++
|
||
}
|
||
if i >= len(runes) {
|
||
parsingOk = false
|
||
validationErrorMsg = "ends mid-specifier"
|
||
break
|
||
}
|
||
verb := runes[i]
|
||
specifierEndIndex := i + 1
|
||
fullSpec := string(runes[segmentStart:specifierEndIndex])
|
||
// Check if the verb consumes an argument
|
||
currentVerbConsumesArg := strings.ContainsRune("vTtbcdoqxXUeEfFgGspw", verb)
|
||
currentArgIdx := -1
|
||
isWVerb := false
|
||
|
||
if verb == 'w' {
|
||
isWVerb = true
|
||
wCount++
|
||
if wCount == 1 {
|
||
wArgPos = argPos // Record position of the error argument
|
||
} else {
|
||
parsingOk = false
|
||
validationErrorMsg = "multiple %w"
|
||
break
|
||
}
|
||
}
|
||
|
||
if currentVerbConsumesArg {
|
||
if argPos >= len(args) {
|
||
parsingOk = false
|
||
if isWVerb { // More specific message for missing %w arg
|
||
validationErrorMsg = "missing %w argument"
|
||
} else {
|
||
validationErrorMsg = fmt.Sprintf("missing argument for %s", string(verb))
|
||
}
|
||
break
|
||
}
|
||
currentArgIdx = argPos
|
||
if isWVerb {
|
||
cause, ok := args[argPos].(error)
|
||
if !ok || cause == nil {
|
||
parsingOk = false
|
||
validationErrorMsg = "bad %w argument type"
|
||
break
|
||
}
|
||
wArg = cause // Store the actual error argument
|
||
}
|
||
argPos++ // Consume the argument position
|
||
}
|
||
fmtVerbs = append(fmtVerbs, struct {
|
||
isW bool
|
||
spec string
|
||
argIdx int
|
||
}{isW: isWVerb, spec: fullSpec, argIdx: currentArgIdx})
|
||
i = specifierEndIndex // Move past the verb character
|
||
} else {
|
||
// Handle literal segment
|
||
literalStart := i
|
||
for i < len(runes) && runes[i] != '%' {
|
||
i++
|
||
}
|
||
fmtVerbs = append(fmtVerbs, struct {
|
||
isW bool
|
||
spec string
|
||
argIdx int
|
||
}{isW: false, spec: string(runes[literalStart:i]), argIdx: -1})
|
||
}
|
||
}
|
||
|
||
// Check for too many arguments after parsing
|
||
if parsingOk && argPos < len(args) {
|
||
parsingOk = false
|
||
validationErrorMsg = fmt.Sprintf("too many arguments for format %q", format)
|
||
}
|
||
|
||
// Handle format validation errors.
|
||
if !parsingOk {
|
||
switch validationErrorMsg {
|
||
case "multiple %w":
|
||
err.msg = fmt.Sprintf("errors.Newf: format %q has multiple %%w verbs", format)
|
||
case "missing %w argument":
|
||
err.msg = fmt.Sprintf("errors.Newf: format %q has %%w but not enough arguments", format)
|
||
case "bad %w argument type":
|
||
argValStr := "(<nil>)"
|
||
if wArgPos >= 0 && wArgPos < len(args) && args[wArgPos] != nil {
|
||
argValStr = fmt.Sprintf("(%T)", args[wArgPos])
|
||
} else if wArgPos >= len(args) {
|
||
argValStr = "(missing)" // Should be caught by "missing %w argument" case
|
||
}
|
||
err.msg = fmt.Sprintf("errors.Newf: argument %d for %%w is not a non-nil error %s", wArgPos, argValStr)
|
||
case "ends with %":
|
||
err.msg = fmt.Sprintf("errors.Newf: format %q ends with %%", format)
|
||
case "ends mid-specifier":
|
||
err.msg = fmt.Sprintf("errors.Newf: format %q ends during verb specifier", format)
|
||
default: // Includes "too many arguments" and other potential fmt issues
|
||
err.msg = fmt.Sprintf("errors.Newf: error in format %q: %s", format, validationErrorMsg)
|
||
}
|
||
err.cause = nil // Ensure no cause is set on format error
|
||
err.formatWrapped = false
|
||
return err
|
||
}
|
||
// --- End: Parsing and Validation ---
|
||
|
||
// --- Start: Processing Valid Format String ---
|
||
if wCount == 1 && wArg != nil {
|
||
// --- Handle %w: Simulate for Sprintf and pre-format ---
|
||
err.cause = wArg // Set the cause for unwrapping
|
||
err.formatWrapped = true // Signal that msg is the final formatted string
|
||
|
||
var finalFormat strings.Builder
|
||
var finalArgs []interface{}
|
||
causeStr := wArg.Error() // Get the string representation of the cause
|
||
|
||
// Rebuild format string and argument list for Sprintf
|
||
for _, verb := range fmtVerbs {
|
||
if verb.isW {
|
||
// Replace the %w verb specifier (e.g., "%w", "%+w") with "%s"
|
||
finalFormat.WriteString("%s")
|
||
// Add the cause's *string* to the arguments list for the new %s
|
||
finalArgs = append(finalArgs, causeStr)
|
||
} else {
|
||
// Keep the original literal segment or non-%w verb specifier
|
||
finalFormat.WriteString(verb.spec)
|
||
if verb.argIdx != -1 {
|
||
// Add the original argument for this non-%w verb/literal
|
||
finalArgs = append(finalArgs, args[verb.argIdx])
|
||
}
|
||
}
|
||
}
|
||
|
||
// Format using the *modified* format string and arguments list
|
||
result, fmtErr := FmtErrorCheck(finalFormat.String(), finalArgs...)
|
||
if fmtErr != nil {
|
||
// Handle potential errors during the final formatting step
|
||
// This is unlikely if parsing passed, but possible with complex verbs/args
|
||
err.msg = fmt.Sprintf("errors.Newf: formatting error during %%w simulation for format %q: %v", format, fmtErr)
|
||
err.cause = nil // Don't keep the cause if final formatting failed
|
||
err.formatWrapped = false
|
||
} else {
|
||
// Store the final, fully formatted string, matching fmt.Errorf output
|
||
err.msg = result
|
||
}
|
||
// --- End %w Simulation ---
|
||
|
||
} else {
|
||
// --- No %w or wArg is nil: Format directly (original logic) ---
|
||
result, fmtErr := FmtErrorCheck(format, args...)
|
||
if fmtErr != nil {
|
||
err.msg = fmt.Sprintf("errors.Newf: formatting error for format %q: %v", format, fmtErr)
|
||
err.cause = nil
|
||
err.formatWrapped = false
|
||
} else {
|
||
err.msg = result
|
||
err.formatWrapped = false // Ensure false if no %w was involved
|
||
}
|
||
}
|
||
// --- End: Processing Valid Format String ---
|
||
|
||
return err
|
||
}
|
||
|
||
// Errorf is an alias for Newf, providing a familiar interface compatible with
|
||
// fmt.Errorf. It creates a formatted error without capturing a stack trace.
|
||
// See Newf for full details on formatting, including %w support for error wrapping.
|
||
//
|
||
// Example:
|
||
//
|
||
// err := errors.Errorf("failed: %w", errors.New("cause"))
|
||
// // err.Error() == "failed: cause"
|
||
func Errorf(format string, args ...interface{}) *Error {
|
||
return Newf(format, args...)
|
||
}
|
||
|
||
// FmtErrorCheck safely formats a string using fmt.Sprintf, catching panics.
|
||
// Returns the formatted string and any error encountered.
|
||
// Internal use by Newf to validate format strings.
|
||
// Example:
|
||
//
|
||
// result, err := FmtErrorCheck("value: %s", "test")
|
||
func FmtErrorCheck(format string, args ...interface{}) (result string, err error) {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
if e, ok := r.(error); ok {
|
||
err = e
|
||
} else {
|
||
err = fmt.Errorf("panic during formatting: %v", r)
|
||
}
|
||
}
|
||
}()
|
||
result = fmt.Sprintf(format, args...)
|
||
return result, nil
|
||
}
|
||
|
||
// countFmtArgs counts format specifiers that consume arguments in a format string.
|
||
// Ignores %% and non-consuming verbs like %n.
|
||
// Internal use by Newf for argument validation.
|
||
func countFmtArgs(format string) int {
|
||
count := 0
|
||
runes := []rune(format)
|
||
i := 0
|
||
for i < len(runes) {
|
||
if runes[i] == '%' {
|
||
if i+1 < len(runes) && runes[i+1] == '%' {
|
||
i += 2 // Skip %%
|
||
continue
|
||
}
|
||
i++ // Move past %
|
||
for i < len(runes) && (runes[i] == '+' || runes[i] == '-' || runes[i] == '#' ||
|
||
runes[i] == ' ' || runes[i] == '0' ||
|
||
(runes[i] >= '1' && runes[i] <= '9') || runes[i] == '.') {
|
||
i++
|
||
}
|
||
if i < len(runes) {
|
||
if strings.ContainsRune("vTtbcdoqxXUeEfFgGsp", runes[i]) {
|
||
count++
|
||
}
|
||
i++ // Move past verb
|
||
}
|
||
} else {
|
||
i++
|
||
}
|
||
}
|
||
return count
|
||
}
|
||
|
||
// Std creates a standard error using errors.New for compatibility.
|
||
// Does not capture stack traces or add context.
|
||
// Example:
|
||
//
|
||
// err := errors.Std("simple error")
|
||
func Std(text string) error {
|
||
return errors.New(text)
|
||
}
|
||
|
||
// Stdf creates a formatted standard error using fmt.Errorf for compatibility.
|
||
// Supports %w for wrapping; does not capture stack traces.
|
||
// Example:
|
||
//
|
||
// err := errors.Stdf("failed: %w", cause)
|
||
func Stdf(format string, a ...interface{}) error {
|
||
return fmt.Errorf(format, a...)
|
||
}
|
||
|
||
// Trace creates an error with the given message and captures a stack trace.
|
||
// Use when debugging context is needed; for performance, prefer New().
|
||
// Example:
|
||
//
|
||
// err := errors.Trace("operation failed")
|
||
func Trace(text string) *Error {
|
||
e := New(text)
|
||
return e.WithStack()
|
||
}
|
||
|
||
// Tracef creates a formatted error with a stack trace.
|
||
// Supports %w for wrapping errors.
|
||
// Example:
|
||
//
|
||
// err := errors.Tracef("query %s failed: %w", query, cause)
|
||
func Tracef(format string, args ...interface{}) *Error {
|
||
e := Newf(format, args...)
|
||
return e.WithStack()
|
||
}
|
||
|
||
// As attempts to assign the error or one in its chain to the target interface.
|
||
// Supports *Error and standard error types, traversing the cause chain.
|
||
// Returns true if successful.
|
||
// Example:
|
||
//
|
||
// var target *Error
|
||
// if errors.As(err, &target) {
|
||
// fmt.Println(target.Name())
|
||
// }
|
||
func (e *Error) As(target interface{}) bool {
|
||
if e == nil {
|
||
return false
|
||
}
|
||
// Handle *Error target.
|
||
if targetPtr, ok := target.(*Error); ok {
|
||
current := e
|
||
for current != nil {
|
||
if current.name != "" {
|
||
*targetPtr = *current
|
||
return true
|
||
}
|
||
if next, ok := current.cause.(*Error); ok {
|
||
current = next
|
||
} else if current.cause != nil {
|
||
return errors.As(current.cause, target)
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
// Handle *error target.
|
||
if targetErr, ok := target.(*error); ok {
|
||
innermost := error(e)
|
||
current := error(e)
|
||
for current != nil {
|
||
if err, ok := current.(*Error); ok && err.cause != nil {
|
||
current = err.cause
|
||
innermost = current
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
*targetErr = innermost
|
||
return true
|
||
}
|
||
// Delegate to cause for other types.
|
||
if e.cause != nil {
|
||
return errors.As(e.cause, target)
|
||
}
|
||
return false
|
||
}
|
||
|
||
// Callback sets a function to be called when Error() is invoked.
|
||
// Useful for logging or side effects on error access.
|
||
// Example:
|
||
//
|
||
// err := errors.New("test").Callback(func() { log.Println("error accessed") })
|
||
func (e *Error) Callback(fn func()) *Error {
|
||
e.callback = fn
|
||
return e
|
||
}
|
||
|
||
// Category returns the error’s category, if set.
|
||
// Example:
|
||
//
|
||
// if err.Category() == "network" {
|
||
// handleNetworkError(err)
|
||
// }
|
||
func (e *Error) Category() string {
|
||
return e.category
|
||
}
|
||
|
||
// Code returns the error’s HTTP-like status code, if set.
|
||
// Returns 0 if no code is set.
|
||
// Example:
|
||
//
|
||
// if err.Code() == 404 {
|
||
// renderNotFound()
|
||
// }
|
||
func (e *Error) Code() int {
|
||
return int(e.code)
|
||
}
|
||
|
||
// Context returns the error’s context as a map, merging smallContext and map-based context.
|
||
// Thread-safe; lazily initializes the map if needed.
|
||
// Example:
|
||
//
|
||
// ctx := err.Context()
|
||
// if userID, ok := ctx["user_id"]; ok {
|
||
// fmt.Println(userID)
|
||
// }
|
||
func (e *Error) Context() map[string]interface{} {
|
||
e.mu.RLock()
|
||
defer e.mu.RUnlock()
|
||
|
||
if e.smallCount > 0 && e.context == nil {
|
||
e.context = make(map[string]interface{}, e.smallCount)
|
||
for i := int32(0); i < e.smallCount; i++ {
|
||
e.context[e.smallContext[i].key] = e.smallContext[i].value
|
||
}
|
||
}
|
||
return e.context
|
||
}
|
||
|
||
// Copy creates a deep copy of the error, preserving all fields except stack freshness.
|
||
// The new error can be modified independently.
|
||
// Example:
|
||
//
|
||
// newErr := err.Copy().With("new_key", "value")
|
||
func (e *Error) Copy() *Error {
|
||
if e == emptyError {
|
||
return &Error{
|
||
smallContext: [contextSize]contextItem{},
|
||
}
|
||
}
|
||
|
||
newErr := newError()
|
||
|
||
newErr.msg = e.msg
|
||
newErr.name = e.name
|
||
newErr.template = e.template
|
||
newErr.cause = e.cause
|
||
newErr.code = e.code
|
||
newErr.category = e.category
|
||
newErr.count = e.count
|
||
|
||
if e.smallCount > 0 {
|
||
newErr.smallCount = e.smallCount
|
||
for i := int32(0); i < e.smallCount; i++ {
|
||
newErr.smallContext[i] = e.smallContext[i]
|
||
}
|
||
} else if e.context != nil {
|
||
newErr.context = make(map[string]interface{}, len(e.context))
|
||
for k, v := range e.context {
|
||
newErr.context[k] = v
|
||
}
|
||
}
|
||
|
||
if e.stack != nil && len(e.stack) > 0 {
|
||
if newErr.stack == nil {
|
||
newErr.stack = stackPool.Get().([]uintptr)
|
||
}
|
||
newErr.stack = append(newErr.stack[:0], e.stack...)
|
||
}
|
||
|
||
return newErr
|
||
}
|
||
|
||
// Count returns the number of times the error has been incremented.
|
||
// Useful for tracking error frequency.
|
||
// Example:
|
||
//
|
||
// fmt.Printf("Error occurred %d times", err.Count())
|
||
func (e *Error) Count() uint64 {
|
||
return e.count
|
||
}
|
||
|
||
// Err returns the error as an error interface.
|
||
// Useful for type assertions or interface compatibility.
|
||
// Example:
|
||
//
|
||
// var stdErr error = err.Err()
|
||
func (e *Error) Err() error {
|
||
return e
|
||
}
|
||
|
||
// Error returns the string representation of the error.
|
||
// If the error was created using Newf/Errorf with the %w verb, it returns the
|
||
// pre-formatted string compatible with fmt.Errorf.
|
||
// Otherwise, it combines the message, template, or name with the cause's error
|
||
// string, separated by ": ". Invokes any set callback.
|
||
func (e *Error) Error() string {
|
||
if e.callback != nil {
|
||
e.callback()
|
||
}
|
||
|
||
// If created by Newf/Errorf with %w, msg already contains the final string.
|
||
if e.formatWrapped {
|
||
return e.msg // Return the pre-formatted fmt.Errorf-compatible string
|
||
}
|
||
|
||
// --- Original logic for errors not created via Newf("%w", ...) ---
|
||
// --- or errors created via New/Named and then Wrap() called. ---
|
||
var buf strings.Builder
|
||
|
||
// Append primary message part (msg, template, or name)
|
||
if e.msg != "" {
|
||
buf.WriteString(e.msg)
|
||
} else if e.template != "" {
|
||
buf.WriteString(e.template)
|
||
} else if e.name != "" {
|
||
buf.WriteString(e.name)
|
||
}
|
||
|
||
// Append cause if it exists (only relevant if not formatWrapped)
|
||
if e.cause != nil {
|
||
if buf.Len() > 0 {
|
||
// Add separator only if there was a prefix message/name/template
|
||
buf.WriteString(": ")
|
||
}
|
||
buf.WriteString(e.cause.Error())
|
||
} else if buf.Len() == 0 {
|
||
// Handle case where msg/template/name are empty AND cause is nil
|
||
// Could return a specific string like "[empty error]" or just ""
|
||
return "" // Return empty string for a truly empty error
|
||
}
|
||
|
||
return buf.String()
|
||
}
|
||
|
||
// FastStack returns a lightweight stack trace with file and line numbers only.
|
||
// Omits function names for performance; skips internal frames if configured.
|
||
// Returns nil if no stack trace exists.
|
||
// Example:
|
||
//
|
||
// for _, frame := range err.FastStack() {
|
||
// fmt.Println(frame) // e.g., "main.go:42"
|
||
// }
|
||
func (e *Error) FastStack() []string {
|
||
if e.stack == nil {
|
||
return nil
|
||
}
|
||
configMu.RLock()
|
||
filter := currentConfig.filterInternal
|
||
configMu.RUnlock()
|
||
|
||
pcs := e.stack
|
||
frames := make([]string, 0, len(pcs))
|
||
for _, pc := range pcs {
|
||
fn := runtime.FuncForPC(pc)
|
||
if fn == nil {
|
||
frames = append(frames, "unknown")
|
||
continue
|
||
}
|
||
file, line := fn.FileLine(pc)
|
||
if filter && isInternalFrame(runtime.Frame{File: file, Function: fn.Name()}) {
|
||
continue
|
||
}
|
||
frames = append(frames, fmt.Sprintf("%s:%d", file, line))
|
||
}
|
||
return frames
|
||
}
|
||
|
||
// Find searches the error chain for the first error where pred returns true.
|
||
// Returns nil if no match is found or if pred is nil.
|
||
// Example:
|
||
//
|
||
// err := err.Find(func(e error) bool { return strings.Contains(e.Error(), "timeout") })
|
||
func (e *Error) Find(pred func(error) bool) error {
|
||
if e == nil || pred == nil {
|
||
return nil
|
||
}
|
||
return Find(e, pred)
|
||
}
|
||
|
||
// Format returns a detailed, human-readable string representation of the error,
|
||
// including message, code, context, stack, and cause.
|
||
// Recursive for causes that are also *Error.
|
||
// Example:
|
||
//
|
||
// fmt.Println(err.Format())
|
||
// // Output:
|
||
// // Error: failed: cause
|
||
// // Code: 500
|
||
// // Context:
|
||
// // key: value
|
||
// // Stack:
|
||
// // 1. main.main main.go:42
|
||
func (e *Error) Format() string {
|
||
var sb strings.Builder
|
||
|
||
// Error message.
|
||
sb.WriteString("Error: " + e.Error() + "\n")
|
||
|
||
// Metadata.
|
||
if e.code != 0 {
|
||
sb.WriteString(fmt.Sprintf("Code: %d\n", e.code))
|
||
}
|
||
|
||
// Context.
|
||
if ctx := e.contextAtThisLevel(); len(ctx) > 0 {
|
||
sb.WriteString("Context:\n")
|
||
for k, v := range ctx {
|
||
sb.WriteString(fmt.Sprintf("\t%s: %v\n", k, v))
|
||
}
|
||
}
|
||
|
||
// Stack trace.
|
||
if e.stack != nil {
|
||
sb.WriteString("Stack:\n")
|
||
for i, frame := range e.Stack() {
|
||
sb.WriteString(fmt.Sprintf("\t%d. %s\n", i+1, frame))
|
||
}
|
||
}
|
||
|
||
// Cause.
|
||
if e.cause != nil {
|
||
sb.WriteString("Caused by: ")
|
||
if causeErr, ok := e.cause.(*Error); ok {
|
||
sb.WriteString(causeErr.Format())
|
||
} else {
|
||
sb.WriteString("Error: " + e.cause.Error() + "\n")
|
||
}
|
||
sb.WriteString("\n")
|
||
}
|
||
|
||
return sb.String()
|
||
}
|
||
|
||
// contextAtThisLevel returns context specific to this error, excluding inherited context.
|
||
// Internal use by Format to isolate context per error level.
|
||
func (e *Error) contextAtThisLevel() map[string]interface{} {
|
||
if e.context == nil && e.smallCount == 0 {
|
||
return nil
|
||
}
|
||
|
||
ctx := make(map[string]interface{})
|
||
// Add smallContext items.
|
||
for i := 0; i < int(e.smallCount); i++ {
|
||
ctx[e.smallContext[i].key] = e.smallContext[i].value
|
||
}
|
||
// Add map context items.
|
||
if e.context != nil {
|
||
for k, v := range e.context {
|
||
ctx[k] = v
|
||
}
|
||
}
|
||
return ctx
|
||
}
|
||
|
||
// Free resets the error and returns it to the pool if pooling is enabled.
|
||
// Safe to call multiple times; no-op if pooling is disabled.
|
||
// Call after use to prevent memory leaks when autoFree is false.
|
||
// Example:
|
||
//
|
||
// defer err.Free()
|
||
func (e *Error) Free() {
|
||
if currentConfig.disablePooling {
|
||
return
|
||
}
|
||
|
||
e.Reset()
|
||
|
||
if e.stack != nil {
|
||
stackPool.Put(e.stack[:cap(e.stack)])
|
||
e.stack = nil
|
||
}
|
||
errorPool.Put(e)
|
||
}
|
||
|
||
// Has checks if the error contains meaningful content (message, template, name, or cause).
|
||
// Returns false for nil or empty errors.
|
||
// Example:
|
||
//
|
||
// if !err.Has() {
|
||
// return nil
|
||
// }
|
||
func (e *Error) Has() bool {
|
||
return e != nil && (e.msg != "" || e.template != "" || e.name != "" || e.cause != nil)
|
||
}
|
||
|
||
// HasContextKey checks if the specified key exists in the error’s context.
|
||
// Thread-safe; checks both smallContext and map-based context.
|
||
// Example:
|
||
//
|
||
// if err.HasContextKey("user_id") {
|
||
// fmt.Println(err.Context()["user_id"])
|
||
// }
|
||
func (e *Error) HasContextKey(key string) bool {
|
||
e.mu.RLock()
|
||
defer e.mu.RUnlock()
|
||
|
||
if e.smallCount > 0 {
|
||
for i := int32(0); i < e.smallCount; i++ {
|
||
if e.smallContext[i].key == key {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
if e.context != nil {
|
||
_, exists := e.context[key]
|
||
return exists
|
||
}
|
||
return false
|
||
}
|
||
|
||
// Increment atomically increases the error’s count by 1 and returns the error.
|
||
// Useful for tracking repeated occurrences.
|
||
// Example:
|
||
//
|
||
// err := err.Increment()
|
||
func (e *Error) Increment() *Error {
|
||
atomic.AddUint64(&e.count, 1)
|
||
return e
|
||
}
|
||
|
||
// Is checks if the error matches the target by pointer, name, or cause chain.
|
||
// Compatible with errors.Is; also matches by string for standard errors.
|
||
// Returns true if the error or its cause matches the target.
|
||
// Example:
|
||
//
|
||
// if errors.Is(err, errors.New("target")) {
|
||
// handleTargetError()
|
||
// }
|
||
func (e *Error) Is(target error) bool {
|
||
if e == nil || target == nil {
|
||
return e == target
|
||
}
|
||
if e == target {
|
||
return true
|
||
}
|
||
if e.name != "" {
|
||
if te, ok := target.(*Error); ok && te.name != "" && e.name == te.name {
|
||
return true
|
||
}
|
||
}
|
||
// Match standard errors by string.
|
||
if stdErr, ok := target.(error); ok && e.Error() == stdErr.Error() {
|
||
return true
|
||
}
|
||
if e.cause != nil {
|
||
return errors.Is(e.cause, target)
|
||
}
|
||
return false
|
||
}
|
||
|
||
// IsEmpty checks if the error lacks meaningful content (no message, name, template, or cause).
|
||
// Returns true for nil or fully empty errors.
|
||
// Example:
|
||
//
|
||
// if err.IsEmpty() {
|
||
// return nil
|
||
// }
|
||
func (e *Error) IsEmpty() bool {
|
||
if e == nil {
|
||
return true
|
||
}
|
||
return e.msg == "" && e.template == "" && e.name == "" && e.cause == nil
|
||
}
|
||
|
||
// IsNull checks if the error is nil, empty, or contains only SQL NULL values in its context or cause.
|
||
// Useful for handling database-related errors.
|
||
// Example:
|
||
//
|
||
// if err.IsNull() {
|
||
// return nil
|
||
// }
|
||
func (e *Error) IsNull() bool {
|
||
if e == nil || e == emptyError {
|
||
return true
|
||
}
|
||
// If no context or cause, and no content, it’s not null.
|
||
if e.smallCount == 0 && e.context == nil && e.cause == nil {
|
||
return false
|
||
}
|
||
|
||
// Check cause first.
|
||
if e.cause != nil {
|
||
var isNull bool
|
||
if ce, ok := e.cause.(*Error); ok {
|
||
isNull = ce.IsNull()
|
||
} else {
|
||
isNull = sqlNull(e.cause)
|
||
}
|
||
if isNull {
|
||
return true
|
||
}
|
||
}
|
||
|
||
// Check small context.
|
||
if e.smallCount > 0 {
|
||
allNull := true
|
||
for i := 0; i < int(e.smallCount); i++ {
|
||
isNull := sqlNull(e.smallContext[i].value)
|
||
if !isNull {
|
||
allNull = false
|
||
break
|
||
}
|
||
}
|
||
if !allNull {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// Check regular context.
|
||
if e.context != nil {
|
||
allNull := true
|
||
for _, v := range e.context {
|
||
isNull := sqlNull(v)
|
||
if !isNull {
|
||
allNull = false
|
||
break
|
||
}
|
||
}
|
||
if !allNull {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// Null if context exists and is all null.
|
||
return e.smallCount > 0 || e.context != nil
|
||
}
|
||
|
||
// jsonBufferPool manages reusable buffers for JSON marshaling to reduce allocations.
|
||
var (
|
||
jsonBufferPool = sync.Pool{
|
||
New: func() interface{} {
|
||
return bytes.NewBuffer(make([]byte, 0, bufferSize))
|
||
},
|
||
}
|
||
)
|
||
|
||
// MarshalJSON serializes the error to JSON, including name, message, context, cause, stack, and code.
|
||
// Causes are recursively serialized if they implement json.Marshaler or are *Error.
|
||
// Example:
|
||
//
|
||
// data, _ := json.Marshal(err)
|
||
// fmt.Println(string(data))
|
||
func (e *Error) MarshalJSON() ([]byte, error) {
|
||
// Get buffer from pool.
|
||
buf := jsonBufferPool.Get().(*bytes.Buffer)
|
||
defer jsonBufferPool.Put(buf)
|
||
buf.Reset()
|
||
|
||
// Create new encoder.
|
||
enc := json.NewEncoder(buf)
|
||
enc.SetEscapeHTML(false)
|
||
|
||
// Prepare JSON structure.
|
||
je := struct {
|
||
Name string `json:"name,omitempty"`
|
||
Message string `json:"message,omitempty"`
|
||
Context map[string]interface{} `json:"context,omitempty"`
|
||
Cause interface{} `json:"cause,omitempty"`
|
||
Stack []string `json:"stack,omitempty"`
|
||
Code int `json:"code,omitempty"`
|
||
}{
|
||
Name: e.name,
|
||
Message: e.msg,
|
||
Code: e.Code(),
|
||
}
|
||
|
||
// Add context.
|
||
if ctx := e.Context(); len(ctx) > 0 {
|
||
je.Context = ctx
|
||
}
|
||
|
||
// Add stack.
|
||
if e.stack != nil {
|
||
je.Stack = e.Stack()
|
||
}
|
||
|
||
// Add cause.
|
||
if e.cause != nil {
|
||
switch c := e.cause.(type) {
|
||
case *Error:
|
||
je.Cause = c
|
||
case json.Marshaler:
|
||
je.Cause = c
|
||
default:
|
||
je.Cause = c.Error()
|
||
}
|
||
}
|
||
|
||
// Encode JSON.
|
||
if err := enc.Encode(je); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Remove trailing newline.
|
||
result := buf.Bytes()
|
||
if len(result) > 0 && result[len(result)-1] == '\n' {
|
||
result = result[:len(result)-1]
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// Msgf sets the error’s message using a formatted string and returns the error.
|
||
// Overwrites any existing message.
|
||
// Example:
|
||
//
|
||
// err := err.Msgf("user %s not found", username)
|
||
func (e *Error) Msgf(format string, args ...interface{}) *Error {
|
||
e.msg = fmt.Sprintf(format, args...)
|
||
return e
|
||
}
|
||
|
||
// Name returns the error’s name, if set.
|
||
// Example:
|
||
//
|
||
// if err.Name() == "AuthError" {
|
||
// handleAuthError()
|
||
// }
|
||
func (e *Error) Name() string {
|
||
return e.name
|
||
}
|
||
|
||
// Reset clears all fields of the error, preparing it for reuse in the pool.
|
||
// Internal use by Free; does not release stack to stackPool.
|
||
// Example:
|
||
//
|
||
// err.Reset() // Clear all fields.
|
||
func (e *Error) Reset() {
|
||
e.msg = ""
|
||
e.name = ""
|
||
e.template = ""
|
||
e.category = ""
|
||
e.code = 0
|
||
e.count = 0
|
||
e.cause = nil
|
||
e.callback = nil
|
||
e.formatWrapped = false
|
||
|
||
if e.context != nil {
|
||
for k := range e.context {
|
||
delete(e.context, k)
|
||
}
|
||
}
|
||
e.smallCount = 0
|
||
|
||
if e.stack != nil {
|
||
e.stack = e.stack[:0]
|
||
}
|
||
}
|
||
|
||
// Stack returns a detailed stack trace with function names, files, and line numbers.
|
||
// Filters internal frames if configured; returns nil if no stack exists.
|
||
// Example:
|
||
//
|
||
// for _, frame := range err.Stack() {
|
||
// fmt.Println(frame) // e.g., "main.main main.go:42"
|
||
// }
|
||
func (e *Error) Stack() []string {
|
||
if e.stack == nil {
|
||
return nil
|
||
}
|
||
|
||
frames := runtime.CallersFrames(e.stack)
|
||
var trace []string
|
||
for {
|
||
frame, more := frames.Next()
|
||
if frame == (runtime.Frame{}) {
|
||
break
|
||
}
|
||
|
||
if currentConfig.filterInternal && isInternalFrame(frame) {
|
||
continue
|
||
}
|
||
|
||
trace = append(trace, fmt.Sprintf("%s %s:%d",
|
||
frame.Function,
|
||
frame.File,
|
||
frame.Line))
|
||
|
||
if !more {
|
||
break
|
||
}
|
||
}
|
||
return trace
|
||
}
|
||
|
||
// Trace ensures the error has a stack trace, capturing it if absent.
|
||
// Returns the error for chaining.
|
||
// Example:
|
||
//
|
||
// err := errors.New("failed").Trace()
|
||
func (e *Error) Trace() *Error {
|
||
if e.stack == nil {
|
||
e.stack = captureStack(2)
|
||
}
|
||
return e
|
||
}
|
||
|
||
// Transform applies transformations to a copy of the error and returns the new error.
|
||
// The original error is unchanged; nil-safe.
|
||
// Example:
|
||
//
|
||
// newErr := err.Transform(func(e *Error) { e.With("key", "value") })
|
||
func (e *Error) Transform(fn func(*Error)) *Error {
|
||
if e == nil || fn == nil {
|
||
return e
|
||
}
|
||
newErr := e.Copy()
|
||
fn(newErr)
|
||
return newErr
|
||
}
|
||
|
||
// Unwrap returns the underlying cause of the error, if any.
|
||
// Compatible with errors.Unwrap for chain traversal.
|
||
// Example:
|
||
//
|
||
// cause := errors.Unwrap(err)
|
||
func (e *Error) Unwrap() error {
|
||
return e.cause
|
||
}
|
||
|
||
// UnwrapAll returns a slice of all errors in the chain, starting with this error.
|
||
// Each error is isolated to prevent modifications affecting others.
|
||
// Example:
|
||
//
|
||
// chain := err.UnwrapAll()
|
||
// for _, e := range chain {
|
||
// fmt.Println(e.Error())
|
||
// }
|
||
func (e *Error) UnwrapAll() []error {
|
||
if e == nil {
|
||
return nil
|
||
}
|
||
var chain []error
|
||
current := error(e)
|
||
for current != nil {
|
||
if err, ok := current.(*Error); ok {
|
||
isolated := newError()
|
||
isolated.msg = err.msg
|
||
isolated.name = err.name
|
||
isolated.template = err.template
|
||
isolated.code = err.code
|
||
isolated.category = err.category
|
||
if err.smallCount > 0 {
|
||
isolated.smallCount = err.smallCount
|
||
for i := int32(0); i < err.smallCount; i++ {
|
||
isolated.smallContext[i] = err.smallContext[i]
|
||
}
|
||
}
|
||
if err.context != nil {
|
||
isolated.context = make(map[string]interface{}, len(err.context))
|
||
for k, v := range err.context {
|
||
isolated.context[k] = v
|
||
}
|
||
}
|
||
if err.stack != nil {
|
||
isolated.stack = append([]uintptr(nil), err.stack...)
|
||
}
|
||
chain = append(chain, isolated)
|
||
} else {
|
||
chain = append(chain, current)
|
||
}
|
||
if unwrapper, ok := current.(interface{ Unwrap() error }); ok {
|
||
current = unwrapper.Unwrap()
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
return chain
|
||
}
|
||
|
||
// Walk traverses the error chain, applying fn to each error.
|
||
// Stops if fn is nil or the chain ends.
|
||
// Example:
|
||
//
|
||
// err.Walk(func(e error) { fmt.Println(e.Error()) })
|
||
func (e *Error) Walk(fn func(error)) {
|
||
if e == nil || fn == nil {
|
||
return
|
||
}
|
||
current := error(e)
|
||
for current != nil {
|
||
fn(current)
|
||
if unwrappable, ok := current.(interface{ Unwrap() error }); ok {
|
||
current = unwrappable.Unwrap()
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
// With adds key-value pairs to the error's context and returns the error.
|
||
// Uses a fixed-size array (smallContext) for up to contextSize items, then switches
|
||
// to a map. Thread-safe. Accepts variadic key-value pairs.
|
||
// Example:
|
||
//
|
||
// err := err.With("key1", value1, "key2", value2)
|
||
func (e *Error) With(keyValues ...interface{}) *Error {
|
||
if len(keyValues) == 0 {
|
||
return e
|
||
}
|
||
|
||
// Validate that we have an even number of arguments
|
||
if len(keyValues)%2 != 0 {
|
||
keyValues = append(keyValues, "(MISSING)")
|
||
}
|
||
|
||
// Fast path for small context when we can add all pairs to smallContext
|
||
if e.smallCount < contextSize && e.context == nil {
|
||
remainingSlots := contextSize - int(e.smallCount)
|
||
if len(keyValues)/2 <= remainingSlots {
|
||
e.mu.Lock()
|
||
// Recheck conditions after acquiring lock
|
||
if e.smallCount < contextSize && e.context == nil {
|
||
for i := 0; i < len(keyValues); i += 2 {
|
||
key, ok := keyValues[i].(string)
|
||
if !ok {
|
||
key = fmt.Sprintf("%v", keyValues[i])
|
||
}
|
||
e.smallContext[e.smallCount] = contextItem{key, keyValues[i+1]}
|
||
e.smallCount++
|
||
}
|
||
e.mu.Unlock()
|
||
return e
|
||
}
|
||
e.mu.Unlock()
|
||
}
|
||
}
|
||
|
||
// Slow path - either we have too many pairs or already using map context
|
||
e.mu.Lock()
|
||
defer e.mu.Unlock()
|
||
|
||
// Initialize map context if needed
|
||
if e.context == nil {
|
||
e.context = make(map[string]interface{}, max(currentConfig.contextSize, len(keyValues)/2+int(e.smallCount)))
|
||
// Migrate existing smallContext items
|
||
for i := int32(0); i < e.smallCount; i++ {
|
||
e.context[e.smallContext[i].key] = e.smallContext[i].value
|
||
}
|
||
// Reset smallCount since we've moved to map context
|
||
e.smallCount = 0
|
||
}
|
||
|
||
// Add all pairs to map context
|
||
for i := 0; i < len(keyValues); i += 2 {
|
||
key, ok := keyValues[i].(string)
|
||
if !ok {
|
||
key = fmt.Sprintf("%v", keyValues[i])
|
||
}
|
||
e.context[key] = keyValues[i+1]
|
||
}
|
||
|
||
return e
|
||
}
|
||
|
||
// Helper function to get maximum of two integers
|
||
func max(a, b int) int {
|
||
if a > b {
|
||
return a
|
||
}
|
||
return b
|
||
}
|
||
|
||
// WithCategory sets the error’s category and returns the error.
|
||
// Example:
|
||
//
|
||
// err := err.WithCategory("validation")
|
||
func (e *Error) WithCategory(category ErrorCategory) *Error {
|
||
e.category = string(category)
|
||
return e
|
||
}
|
||
|
||
// WithCode sets an HTTP-like status code and returns the error.
|
||
// Example:
|
||
//
|
||
// err := err.WithCode(400)
|
||
func (e *Error) WithCode(code int) *Error {
|
||
e.code = int32(code)
|
||
return e
|
||
}
|
||
|
||
// WithName sets the error’s name and returns the error.
|
||
// Example:
|
||
//
|
||
// err := err.WithName("AuthError")
|
||
func (e *Error) WithName(name string) *Error {
|
||
e.name = name
|
||
return e
|
||
}
|
||
|
||
// WithRetryable marks the error as retryable in its context and returns the error.
|
||
// Example:
|
||
//
|
||
// err := err.WithRetryable()
|
||
func (e *Error) WithRetryable() *Error {
|
||
return e.With(ctxRetry, true)
|
||
}
|
||
|
||
// WithStack captures a stack trace if none exists and returns the error.
|
||
// Skips one frame (caller of WithStack).
|
||
// Example:
|
||
//
|
||
// err := errors.New("failed").WithStack()
|
||
func (e *Error) WithStack() *Error {
|
||
if e.stack == nil {
|
||
e.stack = captureStack(1)
|
||
}
|
||
return e
|
||
}
|
||
|
||
// WithTemplate sets a message template and returns the error.
|
||
// Used as a fallback if the message is empty.
|
||
// Example:
|
||
//
|
||
// err := err.WithTemplate("operation failed")
|
||
func (e *Error) WithTemplate(template string) *Error {
|
||
e.template = template
|
||
return e
|
||
}
|
||
|
||
// WithTimeout marks the error as a timeout error in its context and returns the error.
|
||
// Example:
|
||
//
|
||
// err := err.WithTimeout()
|
||
func (e *Error) WithTimeout() *Error {
|
||
return e.With(ctxTimeout, true)
|
||
}
|
||
|
||
// Wrap associates a cause error with this error, creating a chain.
|
||
// Returns the error unchanged if cause is nil.
|
||
// Example:
|
||
//
|
||
// err := errors.New("failed").Wrap(errors.New("cause"))
|
||
func (e *Error) Wrap(cause error) *Error {
|
||
if cause == nil {
|
||
return e
|
||
}
|
||
e.cause = cause
|
||
return e
|
||
}
|
||
|
||
// Wrapf wraps a cause error with formatted message and returns the error.
|
||
// If cause is nil, returns the error unchanged.
|
||
// Example:
|
||
//
|
||
// err := errors.New("base").Wrapf(io.EOF, "read failed: %s", "file.txt")
|
||
func (e *Error) Wrapf(cause error, format string, args ...interface{}) *Error {
|
||
e.msg = fmt.Sprintf(format, args...)
|
||
if cause != nil {
|
||
e.cause = cause
|
||
}
|
||
return e
|
||
}
|
||
|
||
// WrapNotNil wraps a cause error only if it is non-nil and returns the error.
|
||
// Example:
|
||
//
|
||
// err := err.WrapNotNil(maybeError)
|
||
func (e *Error) WrapNotNil(cause error) *Error {
|
||
if cause != nil {
|
||
e.cause = cause
|
||
}
|
||
return e
|
||
}
|
||
|
||
// WarmPool pre-populates the error pool with count instances.
|
||
// Improves performance by reducing initial allocations.
|
||
// No-op if pooling is disabled.
|
||
// Example:
|
||
//
|
||
// errors.WarmPool(1000)
|
||
func WarmPool(count int) {
|
||
if currentConfig.disablePooling {
|
||
return
|
||
}
|
||
for i := 0; i < count; i++ {
|
||
e := &Error{
|
||
smallContext: [contextSize]contextItem{},
|
||
stack: nil,
|
||
}
|
||
errorPool.Put(e)
|
||
stackPool.Put(make([]uintptr, 0, currentConfig.stackDepth))
|
||
}
|
||
}
|
||
|
||
// WarmStackPool pre-populates the stack pool with count slices.
|
||
// Improves performance for stack-intensive operations.
|
||
// No-op if pooling is disabled.
|
||
// Example:
|
||
//
|
||
// errors.WarmStackPool(500)
|
||
func WarmStackPool(count int) {
|
||
if currentConfig.disablePooling {
|
||
return
|
||
}
|
||
for i := 0; i < count; i++ {
|
||
stackPool.Put(make([]uintptr, 0, currentConfig.stackDepth))
|
||
}
|
||
}
|