vendor/github.com/olekukonko/errors/errors.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))
}
}