Files
opencloud/vendor/github.com/olekukonko/errors/errors.go
T
dependabot[bot] 9dbde7952d build(deps): bump github.com/olekukonko/errors from 1.2.0 to 1.3.0
Bumps [github.com/olekukonko/errors](https://github.com/olekukonko/errors) from 1.2.0 to 1.3.0.
- [Commits](https://github.com/olekukonko/errors/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/olekukonko/errors
  dependency-version: 1.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-21 10:28:36 +02:00

1404 lines
38 KiB
Go
Raw 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"
"log/slog"
"runtime"
"strings"
"sync"
"sync/atomic"
)
// 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 {
// Fields used in atomic operations. Place them at the beginning of the
// struct to ensure proper alignment across all architectures.
count uint64 // Occurrence count for tracking frequency.
// 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").
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.
}
// 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 newError()
}
// 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(f any, args ...interface{}) *Error {
var format string
switch v := f.(type) {
case string:
format = v
case fmt.Stringer:
format = v.String()
default:
panic("Newf: format must be a string or fmt.Stringer")
}
err := newError()
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
}
// 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...)
}
// 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 (i.e. caller passed &myErrPtr where myErrPtr is *Error).
// Traverse the chain and return the first *Error that has a name; if none has a
// name, return the first *Error in the chain. This satisfies both:
// - TestErrorAs: wraps Named("target") -> finds it by name
// - TestErrorFullChain: finds Named("AuthError") deep in the chain
if targetPtr, ok := target.(**Error); ok {
var first *Error
current := e
for current != nil {
if first == nil {
first = current
}
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 {
break
}
}
if first != nil {
*targetPtr = first
return true
}
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
newErr.callback = e.callback // was silently dropped by Copy
newErr.formatWrapped = e.formatWrapped // was silently dropped by Copy
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 {
// Same len-vs-nil reasoning as Stack().
if len(e.stack) == 0 {
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 return the error to the pool and prevent memory leaks.
// Use defer err.Free() at the call site that created the error.
// Example:
//
// defer err.Free()
func (e *Error) Free() {
if currentConfig.disablePooling {
return
}
// Disarm any pending auto-cleanup (finalizer or runtime.AddCleanup) before
// manually returning to the pool. Without this, GC could return the same
// *Error a second time after Free() has already done so — double-put.
errorPool.clearCleanup(e)
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
}
}
// String-equality fallback: matches any error whose message equals this
// error's message. This is intentional — it allows matching errors created
// by fmt.Errorf or errors.New with the same text — but it deviates from
// stdlib errors.Is which uses pointer/sentinel identity.
// IMPORTANT: two distinct errors with identical messages will match each other.
// For strict identity matching use errors.Const() to create named sentinels.
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
}
// 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. Do NOT defer-return it — we must copy the result
// out of buf's backing array and return the buf to the pool BEFORE we return
// the copied slice. If we defer the Put, another goroutine can Get the same
// buf and overwrite its backing array while the caller is still reading our
// returned slice (the race the detector flags).
buf := jsonBufferPool.Get().(*bytes.Buffer)
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
}
// Copy bytes out of buf before returning buf to the pool.
// buf.Bytes() is a slice into buf's internal array — if we put buf back first
// and another goroutine resets it, they share the same backing memory.
raw := buf.Bytes()
if len(raw) > 0 && raw[len(raw)-1] == '\n' {
raw = raw[:len(raw)-1]
}
result := make([]byte, len(raw))
copy(result, raw)
jsonBufferPool.Put(buf)
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 {
// Use len check not nil: a recycled error has stack reset to stack[:0]
// (non-nil, zero length). Calling CallersFrames on an empty slice returns
// no frames, making Stack() silently return [] instead of nil.
if len(e.stack) == 0 {
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 {
// Check len rather than nil for the same reason as WithStack.
if len(e.stack) == 0 {
// skip=1: trimmed = skip+1 = 2, removes captureStack + Trace() itself.
e.stack = captureStack(1)
}
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
}
// Return the original nodes directly. Each *Error in the chain already owns
// its own context map and message — there is no bleeding between nodes.
// Returning originals (rather than copies) ensures:
// e.Error() returns only that node's own msg/name (cause is on the
// NEXT node, not duplicated here — Error() appends e.cause.Error() which
// is exactly the next node's contribution).
//
// Wait — Error() DOES append cause. So chain[0].Error() includes the full
// chain. The test wants chain[0].Error() == "outer" (msg only).
// return snapshot wrappers that expose only the node's own message.
var chain []error
current := error(e)
for current != nil {
if err, ok := current.(*Error); ok {
// Wrap in a msgOnlyError so Error() returns only this node's own
// message without appending the cause chain. Unwrap() still returns
// the original *Error so standard chain traversal continues to work.
chain = append(chain, &msgOnlyError{err: err})
} 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)")
}
// Acquire the lock once up-front. The previous "optimistic read then lock"
// pattern read e.smallCount and e.context without holding the lock, which
// the race detector correctly flagged as a data race when two goroutines
// call With() on the same *Error concurrently.
e.mu.Lock()
defer e.mu.Unlock()
// Fast path: all pairs fit in the fixed-size smallContext array.
if e.smallCount < contextSize && e.context == nil {
remainingSlots := contextSize - int(e.smallCount)
if len(keyValues)/2 <= remainingSlots {
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++
}
return e
}
}
// Slow path: too many pairs or already using map context.
// 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 {
// Check len rather than nil: a pooled error has stack reset to stack[:0]
// (non-nil but empty). The nil check would skip capture for recycled errors.
if len(e.stack) == 0 {
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
}
// LogValue implements slog.LogValuer so *Error can be passed directly to
// any slog logging call and will be rendered as a structured group containing
// message, name, code, category, and context fields.
//
// Example:
//
// slog.Error("request failed", "err", err)
// // => err.message="...", err.name="AuthError", err.code=401, ...
func (e *Error) LogValue() slog.Value {
if e == nil {
return slog.StringValue("<nil>")
}
attrs := make([]slog.Attr, 0, 6)
if e.msg != "" {
attrs = append(attrs, slog.String("message", e.msg))
}
if e.name != "" {
attrs = append(attrs, slog.String("name", e.name))
}
if e.code != 0 {
attrs = append(attrs, slog.Int("code", int(e.code)))
}
if e.category != "" {
attrs = append(attrs, slog.String("category", e.category))
}
if ctx := e.contextAtThisLevel(); len(ctx) > 0 {
ctxAttrs := make([]slog.Attr, 0, len(ctx))
for k, v := range ctx {
ctxAttrs = append(ctxAttrs, slog.Any(k, v))
}
attrs = append(attrs, slog.Attr{Key: "context", Value: slog.GroupValue(ctxAttrs...)})
}
if e.cause != nil {
attrs = append(attrs, slog.String("cause", e.cause.Error()))
}
return slog.GroupValue(attrs...)
}
// msgOnlyError wraps a single *Error and returns only its own message from
// Error(), without appending the cause chain. Used by UnwrapAll so each
// element in the returned slice represents exactly one chain node.
type msgOnlyError struct {
err *Error
}
func (m *msgOnlyError) Error() string {
if m.err.msg != "" {
return m.err.msg
}
if m.err.name != "" {
return m.err.name
}
if m.err.template != "" {
return m.err.template
}
return ""
}
// Unwrap returns the underlying *Error so errors.Is/As and chain traversal work.
func (m *msgOnlyError) Unwrap() error { return m.err.cause }
// Convenience accessors so callers can still reach *Error fields after UnwrapAll.
func (m *msgOnlyError) Name() string { return m.err.Name() }
func (m *msgOnlyError) Code() int { return m.err.Code() }
func (m *msgOnlyError) Context() map[string]interface{} { return m.err.Context() }
func (m *msgOnlyError) Stack() []string { return m.err.Stack() }