Files
opencloud/vendor/github.com/olekukonko/errors/errors.go
dependabot[bot] 5e6fc50e5e build(deps): bump github.com/olekukonko/tablewriter from 1.0.8 to 1.0.9
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>
2025-08-26 06:40:11 +00:00

1497 lines
39 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 errors category, if set.
// Example:
//
// if err.Category() == "network" {
// handleNetworkError(err)
// }
func (e *Error) Category() string {
return e.category
}
// Code returns the errors 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 errors 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 errors 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 errors 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, its 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 errors 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 errors 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 errors 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 errors 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))
}
}