Files
opencloud/vendor/github.com/olekukonko/errors/errors.go
dependabot[bot] 70fc6eb40b build(deps): bump github.com/olekukonko/tablewriter from 0.0.5 to 1.0.6
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 0.0.5 to 1.0.6.
- [Commits](https://github.com/olekukonko/tablewriter/compare/v0.0.5...v1.0.6)

---
updated-dependencies:
- dependency-name: github.com/olekukonko/tablewriter
  dependency-version: 1.0.6
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 15:04:23 +00:00

958 lines
25 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
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"runtime"
"strings"
"sync"
"sync/atomic"
)
const (
ctxTimeout = "[error] timeout" // Context key for marking timeout errors
ctxRetry = "[error] retry" // Context key for marking retryable errors
contextSize = 4 // Default size of fixed-size context array
bufferSize = 256 // Initial buffer size for JSON marshaling
warmUpSize = 100 // Number of errors to pre-warm the pool
stackDepth = 32 // Default maximum stack trace depth
)
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 configuration for the errors package.
type Config struct {
StackDepth int // Maximum depth of the stack trace; 0 uses default
ContextSize int // Initial size of the context map; 0 uses default
DisablePooling bool // Disables object pooling for errors if true
FilterInternal bool // Filters internal package frames from stack traces if true
AutoFree bool // Automatically frees errors to pool if true
}
// cachedConfig holds the current configuration, updated only on Configure().
type cachedConfig struct {
stackDepth int
contextSize int
disablePooling bool
filterInternal bool
autoFree bool
}
var (
currentConfig cachedConfig
configMu sync.RWMutex
errorPool = NewErrorPool() // Custom pool for Error instances
stackPool = sync.Pool{ // Pool for stack trace slices
New: func() interface{} {
return make([]uintptr, currentConfig.stackDepth)
},
}
emptyError = &Error{
smallContext: [contextSize]contextItem{},
msg: "",
name: "",
template: "",
cause: nil,
}
)
//var bufferPool = sync.Pool{
// New: func() interface{} {
// return bytes.NewBuffer(make([]byte, 0, bufferSize))
// },
//}
// contextItem represents a single key-value pair in the smallContext array.
type contextItem struct {
key string
value interface{}
}
// Error represents a custom error with enhanced features like context, stack traces, and wrapping.
type Error struct {
// Primary error information (most frequently accessed)
msg string // Error message
name string // Error name/type
stack []uintptr // Stack trace
// Secondary error metadata
template string // Message template used if msg is empty
category string // Error category (e.g., "network", "validation")
count uint64 // Occurrence count for tracking frequency
code int32 // HTTP-like error code
smallCount int32 // Number of items in smallContext
// Context and chaining
context map[string]interface{} // Additional context as key-value pairs
cause error // Wrapped underlying error
callback func() // Optional callback executed on Error()
smallContext [contextSize]contextItem // Fixed-size context storage for efficiency
// Synchronization
mu sync.RWMutex // Protects concurrent access to mutable fields
}
// init initializes 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-warm pool with initial errors
}
// Configure updates the global configuration for the errors package.
// Thread-safe; should be called before heavy usage for optimal performance.
// Changes apply immediately to all subsequent error operations.
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, using the pool if enabled.
// Initializes smallContext and stack appropriately.
func newError() *Error {
if currentConfig.disablePooling {
return &Error{
smallContext: [contextSize]contextItem{},
stack: nil,
}
}
return errorPool.Get()
}
// Empty creates a new empty error with no stack trace.
// Useful as a base for building errors incrementally.
func Empty() *Error {
return emptyError
}
// Named creates a new error with a specific name and stack trace.
// The name is used as the error message if no other message is set.
func Named(name string) *Error {
e := newError()
e.name = name
return e.WithStack()
}
// New creates a fast, lightweight error without stack tracing.
// Use instead of Trace() when stack traces aren't needed for better performance.
func New(text string) *Error {
if text == "" {
return emptyError.Copy() // Global pre-allocated empty error
}
err := newError()
err.msg = text
return err
}
// Newf is an alias to Errorf for fmt.Errorf compatibility.
// Creates a formatted error without stack traces.
func Newf(format string, args ...interface{}) *Error {
err := newError()
err.msg = fmt.Sprintf(format, args...)
return err
}
// Std creates a standard error using errors.New, provided for backward compatibility.
// This function serves as a lightweight wrapper around the standard library's error creation,
// allowing users to opt into basic error handling without adopting the full features of this package.
func Std(text string) error {
return errors.New(text)
}
// Stdf creates a formatted standard error using fmt.Errorf, provided for backward compatibility.
// This function wraps the standard library's formatted error creation, offering a simple alternative
// to the package's enhanced error handling while maintaining compatibility with existing codebases.
func Stdf(format string, a ...interface{}) error {
return fmt.Errorf(format, a...)
}
// Trace creates an error with stack trace capture enabled.
// Use when call stacks are needed for debugging; has performance overhead.
func Trace(text string) *Error {
e := New(text)
return e.WithStack()
}
// Tracef creates a formatted error with stack trace.
// Combines Errorf and WithStack for convenience.
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.
// It only assigns when the target is a **Error and the current error node has a non-empty name.
// If the current node has an empty name, it delegates to its wrapped cause.
func (e *Error) As(target interface{}) bool {
if e == nil {
return false
}
// Handle *Error target (for stderrors.As compatibility)
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 - unwrap to innermost error
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; returns the error for chaining.
func (e *Error) Callback(fn func()) *Error {
e.callback = fn
return e
}
// Category returns the error's category, if set.
// Returns an empty string if no category is defined.
func (e *Error) Category() string {
return e.category
}
// Code returns the error's status code, if set.
// Returns 0 if no code is defined.
func (e *Error) Code() int {
return int(e.code)
}
// Context returns the error's context as a map.
// Converts smallContext to a map if needed; returns nil if empty.
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.
// The new error does not capture a new stack trace unless explicitly added.
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 occurrence frequency.
func (e *Error) Count() uint64 {
return e.count
}
// Err returns the error as an error interface.
// Provided for compatibility; simply returns the error itself.
func (e *Error) Err() error {
return e
}
// Error returns the string representation of the error.
// Prioritizes msg, then template, then name, falling back to "unknown error".
// Executes callback if set before returning the message.
func (e *Error) Error() string {
if e.callback != nil {
e.callback()
}
var msg string
switch {
case e.msg != "":
msg = e.msg
case e.template != "":
msg = e.template
case e.name != "":
msg = e.name
default:
msg = "unknown error"
}
if e.cause != nil {
causeMsg := e.cause.Error()
if msg != "" && causeMsg != "" {
msg = msg + ": " + causeMsg
} else if causeMsg != "" {
msg = causeMsg
}
}
return msg
}
// Errorf creates a formatted error without stack traces.
// Compatible with fmt.Errorf; does not capture stack trace for performance.
func Errorf(format string, args ...interface{}) *Error {
err := newError()
err.msg = fmt.Sprintf(format, args...)
return err
}
// FastStack returns a lightweight stack trace without function names.
// Filters internal frames if FilterInternal is enabled; returns nil if no stack.
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 matching pred.
// Starts with the current error and follows Unwrap() and Cause() chains.
func (e *Error) Find(pred func(error) bool) error {
if e == nil || pred == nil {
return nil
}
return Find(e, pred)
}
// Format returns a formatted string representation of the error.
// Includes message, code, context, and stack trace if present.
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 (only show context added at this level)
if ctx := e.contextAtThisLevel(); len(ctx) > 0 {
sb.WriteString("Context:\n")
for k, v := range ctx {
sb.WriteString(fmt.Sprintf(" %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(" %d. %s\n", i+1, frame))
}
}
return sb.String()
}
// contextAtThisLevel returns context specific to this error level, excluding inherited context.
// Combines smallContext and context map into a single map; returns nil if empty.
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.
// Does nothing beyond reset if pooling is disabled.
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.
// Returns true if msg, template, name, or cause is non-empty/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.
// Searches both smallContext and context map; thread-safe.
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 increases the error's count by 1 and returns the error.
// Uses atomic operation for thread safety.
func (e *Error) Increment() *Error {
atomic.AddUint64(&e.count, 1)
return e
}
// Is checks if the error matches a target error by pointer equality, name, or wrapped cause.
// Ensures compatibility with stderrors.Is by prioritizing chain traversal.
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
}
}
// Add string comparison for standard errors
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 has no meaningful content (empty message, no name/template/cause).
// Returns true for nil errors or errors with no data.
func (e *Error) IsEmpty() bool {
if e == nil {
return true
}
return e.msg == "" && e.template == "" && e.name == "" && e.cause == nil
}
// IsNull checks if an error is nil or represents a SQL NULL value.
// Considers both the error itself and any context values; returns true if all context is null.
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 its null, the whole error is null
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
}
// If cause isnt null, continue checking this errors context
}
// 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 we have context and its all null
return e.smallCount > 0 || e.context != nil
}
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, and stack.
// Handles nested *Error causes and custom marshalers efficiently.
func (e *Error) MarshalJSON() ([]byte, error) {
// Get buffer from pool
buf := jsonBufferPool.Get().(*bytes.Buffer)
defer jsonBufferPool.Put(buf)
buf.Reset()
// Create new encoder each time (no Reset available)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
// Prepare error data
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(),
}
// Handle context
if ctx := e.Context(); len(ctx) > 0 {
je.Context = ctx
}
// Handle stack
if e.stack != nil {
je.Stack = e.Stack()
}
// Handle 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
if err := enc.Encode(je); err != nil {
return nil, err
}
// Return bytes without 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 message using a formatted string.
// Overwrites any existing message; returns the error for chaining.
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.
// Returns an empty string if no name is defined.
func (e *Error) Name() string {
return e.name
}
// Reset clears all fields of the error, preparing it for reuse.
// Does not free the stack; use Free() to return to pool.
func (e *Error) Reset() {
e.msg = ""
e.name = ""
e.template = ""
e.category = ""
e.code = 0
e.count = 0
e.cause = nil
e.callback = nil
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 as a slice of strings.
// Filters internal frames if FilterInternal is enabled; returns nil if no stack.
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 missing.
// Skips capture if stack already exists; returns the error for chaining.
func (e *Error) Trace() *Error {
if e.stack == nil {
e.stack = captureStack(2)
}
return e
}
// Transform applies transformations to a copy of the error.
// Returns the transformed copy or the original if no changes are needed.
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.
// Implements the errors.Unwrap interface for unwrapping chains.
func (e *Error) Unwrap() error {
return e.cause
}
// UnwrapAll returns a slice of all errors in the chain, starting with this error.
// Traverses the cause chain, creating isolated copies of each *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.
// Starts with the current error and follows the cause chain.
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 a key-value pair to the error's context.
// Uses smallContext for efficiency until full, then switches to map; thread-safe.
func (e *Error) With(key string, value interface{}) *Error {
// Fast path for small context (no map needed)
if e.smallCount < contextSize && e.context == nil {
e.mu.Lock()
// Double-check after acquiring lock
if e.smallCount < contextSize && e.context == nil {
e.smallContext[e.smallCount] = contextItem{key, value}
e.smallCount++
e.mu.Unlock()
return e
}
e.mu.Unlock()
}
// Slow path - requires map
e.mu.Lock()
defer e.mu.Unlock()
if e.context == nil {
e.context = make(map[string]interface{}, currentConfig.contextSize)
// Migrate existing items if any
for i := int32(0); i < e.smallCount; i++ {
e.context[e.smallContext[i].key] = e.smallContext[i].value
}
}
e.context[key] = value
return e
}
// WithCategory sets a category for the error and returns the error.
// Useful for classifying errors (e.g., "network", "validation").
func (e *Error) WithCategory(category ErrorCategory) *Error {
e.category = string(category)
return e
}
// WithCode sets an HTTP-like status code for the error and returns the error.
// Overwrites any existing code.
func (e *Error) WithCode(code int) *Error {
e.code = int32(code)
return e
}
// WithName sets the error's name and returns the error.
// Overwrites any existing name.
func (e *Error) WithName(name string) *Error {
e.name = name
return e
}
// WithRetryable marks the error as retryable in its context.
// Adds a "retry" key with value true; returns the error.
func (e *Error) WithRetryable() *Error {
return e.With(ctxRetry, true)
}
// WithStack captures the stack trace at call time and returns the error.
// Skips capturing if stack already exists or depth is 0.
func (e *Error) WithStack() *Error {
if e.stack == nil {
e.stack = captureStack(1) // Skip WithStack
}
return e
}
// WithTemplate sets a template string for the error and returns the error.
// Used as the error message if no explicit message is set.
func (e *Error) WithTemplate(template string) *Error {
e.template = template
return e
}
// WithTimeout marks the error as a timeout error in its context.
// Adds a "timeout" key with value true; returns the error.
func (e *Error) WithTimeout() *Error {
return e.With(ctxTimeout, true)
}
// Wrap associates a cause error with this error, creating an error chain.
// Returns the error for method chaining.
func (e *Error) Wrap(cause error) *Error {
if cause == nil {
return e
}
e.cause = cause
return e
}
// WrapNotNil wraps a cause error only if it is non-nil.
// Returns the error for method chaining; no-op if cause is nil.
func (e *Error) WrapNotNil(cause error) *Error {
if cause != nil {
e.cause = cause
}
return e
}
// WarmPool pre-populates the error pool with a specified number of instances.
// Reduces allocation overhead during initial usage; no effect if pooling is disabled.
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 a specified number of slices.
// Reduces allocation overhead for stack traces; no effect if pooling is disabled.
func WarmStackPool(count int) {
if currentConfig.disablePooling {
return
}
for i := 0; i < count; i++ {
stackPool.Put(make([]uintptr, 0, currentConfig.stackDepth))
}
}