errors

package module
v1.0.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2025 License: MIT Imports: 6 Imported by: 1

README

go-errors

Structured error handling package wrapping cockroachdb/errors with enterprise-grade error types, automatic stack traces, and intelligent retry detection.

Why This Package?

Production debugging requires stack traces. Type-safe retry logic requires structured errors. This package provides both:

  • Automatic stack traces via cockroachdb/errors
  • Typed error sentinels for reliable error detection (no string parsing)
  • IsRetryable() interface for intelligent retry logic
  • Structured context (HTTP status codes, validation fields, timeouts)
  • Safe error details for production logging

Installation

go get github.com/JohnPlummer/go-errors

Quick Start

import "github.com/JohnPlummer/go-errors"

// Create errors with automatic stack traces
err := errors.New("database connection failed")

// Get formatted stack trace for debugging
trace := errors.GetStackTrace(err)
fmt.Printf("Stack trace:\n%s\n", trace)

// Check if error is retryable
if errors.IsRetryable(err) {
    // Retry with exponential backoff
    time.Sleep(backoff)
    continue
}

Error Types

HTTPError - HTTP API Failures
// 5xx and 429 are automatically retryable
err := errors.NewHTTPError(503, "Service Unavailable", cause)

if errors.IsRetryable(err) {
    // Will return true for 429, 500-599
}

statusCode := errors.GetHTTPStatusCode(err)  // 503
ValidationError - Input Validation
err := errors.NewValidationError(
    "Price must be positive",
    "price",
    errors.WithValue(-10),
)

// Validation errors are NEVER retryable
if errors.IsPermanentError(err) {
    return err  // Don't retry
}
TimeoutError - Operation Timeouts
err := errors.NewTimeoutError(
    "API call timed out",
    "GetUser",
    30*time.Second,
)

// Timeouts are retryable UNLESS context.DeadlineExceeded
if errors.IsRetryable(err) {
    // Safe to retry
}
RateLimitError - API Rate Limiting
err := errors.NewRateLimitError(
    "API rate limit exceeded",
    "CallAPI",
    60*time.Second,  // retry after
)

// Always retryable, includes suggested retry delay
ProcessingError - Data Processing
err := errors.NewProcessingError(
    "Failed to process activity",
    "ProcessActivity",
    errors.WithItemID("activity-123"),
    errors.WithRetryable(true),
)

// Or use the convenience constructor
err = errors.NewRetryableProcessingError(
    "Database deadlock",
    "SaveData",
    errors.WithCause(dbErr),
)
NetworkError - Network Failures
err := errors.NewNetworkError(
    "Connection refused",
    "Connect",
    // errors.WithTransient(false) for DNS failures
)

// Network errors are transient by default
if errors.IsTransientError(err) {
    // Retry allowed
}
CircuitBreakerError - Circuit Breaker Protection
err := errors.NewCircuitBreakerError(
    "Too many failures",
    "CallExternalAPI",
    "open",
)

// Circuit breaker manages its own retry timing
// These errors are NOT retryable

IsRetryable() Logic

The IsRetryable() function implements sophisticated retry detection:

func processWithRetry(ctx context.Context) error {
    for attempt := 1; attempt <= maxAttempts; attempt++ {
        err := doWork(ctx)
        if err == nil {
            return nil
        }

        // CRITICAL: context.DeadlineExceeded is NOT retryable
        // even if wrapped in TimeoutError
        if !errors.IsRetryable(err) {
            return err  // Permanent failure
        }

        // Wait with exponential backoff
        backoff := time.Duration(attempt) * time.Second
        select {
        case <-time.After(backoff):
            continue
        case <-ctx.Done():
            return ctx.Err()
        }
    }
    return errors.New("max retries exceeded")
}
What Is Retryable?
  • ErrRateLimited, ErrNetworkTimeout, ErrServerError
  • ErrConnectionError, ErrDeadlock, ErrCircuitOpen
  • ✅ HTTP 429, 500-599 status codes
  • TimeoutError, RateLimitError, NetworkError (transient)
  • ProcessingError with Retryable: true
  • context.DeadlineExceeded, context.Canceled
  • ValidationError
  • ❌ HTTP 400-499 (except 429)
  • CircuitBreakerError
Why context.DeadlineExceeded Is NOT Retryable

When context.DeadlineExceeded occurs, the parent context has expired. Retrying with the same context will fail immediately. These errors indicate the operation should be abandoned, not retried.

Error Wrapping

Preserve error chains while adding context:

dbErr := db.Query(...)
if dbErr != nil {
    // Wrap with additional context
    return errors.Wrap(dbErr, "failed to load user")
}

// Check wrapped errors
if errors.Is(err, sql.ErrNoRows) {
    // Handle specific error
}

// Extract typed errors from chain
var httpErr *errors.HTTPError
if errors.As(err, &httpErr) {
    log.Printf("HTTP %d error", httpErr.StatusCode)
}

Stack Traces

err := errors.New("something failed")

// Get formatted stack trace
trace := errors.GetStackTrace(err)
// Output:
// github.com/myorg/myapp.processItem
//     /path/to/code/processor.go:42
// github.com/myorg/myapp.worker
//     /path/to/code/worker.go:28

// Check if error has stack trace
if errors.HasStackTrace(err) {
    // Stack trace available
}

// Get safe details for logging (redacts sensitive info)
safe := errors.GetSafeDetails(err)

Structured Error Information

err := errors.NewHTTPError(503, "Service Unavailable", nil)

// Extract structured information
info := errors.ExtractErrorInfo(err)
// map[string]any{
//     "type": "HTTPError",
//     "retryable": true,
//     "status_code": 503,
//     "message": "HTTP 503: Service Unavailable",
// }

// Format for logging
formatted := errors.FormatError(err)
// "HTTPError(503): HTTP 503: Service Unavailable"

Functional Options

All error constructors support optional configuration:

err := errors.NewProcessingError(
    "Failed to process",
    "ProcessItem",
    errors.WithCause(dbErr),
    errors.WithItemID("item-123"),
    errors.WithRetryable(true),
)

err = errors.NewValidationError(
    "Invalid email format",
    "email",
    errors.WithValue("not-an-email"),
)

err = errors.NewHTTPError(
    500,
    "Internal Server Error",
    cause,
)

Migration from String-Based Detection

Before:

// Brittle string parsing
if strings.Contains(err.Error(), "rate limit") {
    // Retry...
}

After:

// Type-safe detection
if errors.IsRetryable(err) {
    // Retry...
}

// Or check specific types
if errors.Is(err, errors.ErrRateLimited) {
    // Handle rate limit
}

Best Practices

1. Wrap External Errors at Boundaries
// Wrap third-party library errors with typed errors
resp, err := http.Get(url)
if err != nil {
    return errors.NewNetworkError("HTTP GET failed", "FetchData",
        errors.WithCause(err))
}

if resp.StatusCode >= 500 {
    return errors.NewHTTPError(resp.StatusCode, "Server error", nil)
}
2. Use Typed Errors, Not Strings
// Bad
if strings.Contains(err.Error(), "timeout") {
    // Fragile
}

// Good
if errors.IsTimeout(err) {
    // Type-safe
}
3. Check context.DeadlineExceeded Early
func process(ctx context.Context) error {
    err := doWork(ctx)
    if err != nil {
        // Context errors are not retryable
        if errors.IsContextError(err) {
            return err
        }

        if errors.IsRetryable(err) {
            // Safe to retry
        }
    }
}
4. Preserve Stack Traces
// Use Wrap/Wrapf to add context while preserving stack trace
if err := validateInput(data); err != nil {
    return errors.Wrap(err, "validation failed")
}

License

MIT License - see LICENSE for details

Documentation

Overview

Package errors provides structured error types with automatic stack traces. It wraps cockroachdb/errors to provide enterprise-grade error handling with typed sentinels, IsRetryable() interface, and structured error context.

Key features:

  • Automatic stack trace capture for all errors
  • Typed error sentinels for type-safe error detection
  • IsRetryable() interface for intelligent retry logic
  • Structured error context (HTTP status codes, validation fields, etc.)
  • Safe error details for production logging

Index

Constants

This section is empty.

Variables

View Source
var (
	// New creates a new error with a stack trace.
	New = errors.New

	// Errorf creates a new error with formatted message and stack trace.
	Errorf = errors.Errorf

	// Wrap annotates an error with a message and stack trace.
	Wrap = errors.Wrap

	// Wrapf annotates an error with a formatted message and stack trace.
	Wrapf = errors.Wrapf

	// WithStack adds a stack trace to an error if it doesn't have one.
	WithStack = errors.WithStack

	// Is checks if an error matches a target using error chain traversal.
	Is = errors.Is

	// As finds the first error in the chain that matches target type.
	As = errors.As

	// Unwrap returns the result of calling Unwrap on err, if err's type contains
	// an Unwrap method returning error. Otherwise, Unwrap returns nil.
	Unwrap = errors.Unwrap

	// Cause returns the underlying cause of the error, if possible.
	Cause = errors.Cause
)

Re-export commonly used cockroachdb/errors functions. These automatically include stack traces when creating or wrapping errors.

View Source
var (
	// ErrRateLimited indicates API rate limiting.
	ErrRateLimited = errors.New("rate limited")

	// ErrNetworkTimeout indicates network timeout.
	ErrNetworkTimeout = errors.New("network timeout")

	// ErrServerError indicates 5xx HTTP server error.
	ErrServerError = errors.New("server error")

	// ErrConnectionError indicates network connection failure.
	ErrConnectionError = errors.New("connection error")

	// ErrDeadlock indicates database deadlock.
	ErrDeadlock = errors.New("database deadlock")

	// ErrCircuitOpen indicates circuit breaker is open.
	ErrCircuitOpen = errors.New("circuit breaker open")

	// ErrInvalidResponse indicates malformed or unexpected response.
	ErrInvalidResponse = errors.New("invalid response")
)

Sentinel errors for common retryable conditions. Use these when wrapping errors to enable type-safe error detection.

View Source
var (
	// ErrActivityNotFound indicates a requested activity was not found.
	ErrActivityNotFound = errors.New("activity not found")

	// ErrLocationNotFound indicates a requested location was not found.
	ErrLocationNotFound = errors.New("location not found")
)

Sentinel errors for common API/backend error conditions.

Functions

func ExtractErrorInfo

func ExtractErrorInfo(err error) map[string]any

ExtractErrorInfo returns structured information about the error. Returns a map with error type, retryability, and extracted fields.

Example:

info := ExtractErrorInfo(err)
// info = map[string]any{
//     "type": "HTTPError",
//     "retryable": true,
//     "status_code": 503,
//     "message": "Service Unavailable",
// }

func FormatError

func FormatError(err error) string

FormatError returns a formatted error string with type information. Useful for structured logging and debugging.

Example output:

HTTPError(500): Internal Server Error: database connection failed

func GetHTTPStatusCode

func GetHTTPStatusCode(err error) int

GetHTTPStatusCode extracts HTTP status code from error, or 0 if not found.

func GetSafeDetails

func GetSafeDetails(err error) string

GetSafeDetails returns error details safe for production logging. Redacts sensitive information while preserving debugging context.

Example:

err := NewHTTPError(500, "Internal Server Error", fmt.Errorf("db password: secret123"))
safe := GetSafeDetails(err)
// safe contains error type and structure, but sensitive data is redacted

func GetStackTrace

func GetStackTrace(err error) string

GetStackTrace returns a formatted stack trace for the error. Returns empty string if the error has no stack trace.

Example output:

main.processItem
    /path/to/main.go:42
main.worker
    /path/to/main.go:28
main.main
    /path/to/main.go:15

func GetStackTraceLines

func GetStackTraceLines(err error) []string

GetStackTraceLines returns the stack trace as individual lines. Returns empty slice if the error has no stack trace.

func HasStackTrace

func HasStackTrace(err error) bool

HasStackTrace checks if the error has a stack trace.

func IsContextError

func IsContextError(err error) bool

IsContextError checks if err is a context error (DeadlineExceeded or Canceled).

func IsNetworkError

func IsNetworkError(err error) bool

IsNetworkError checks if err is a network error (NetworkError or net.Error).

func IsNotFound added in v1.0.7

func IsNotFound(err error) bool

IsNotFound checks if an error represents a "not found" condition. Returns true for: - ErrActivityNotFound or ErrLocationNotFound sentinels - Any error wrapping these sentinels - HTTPError with status code 404

func IsPermanentError

func IsPermanentError(err error) bool

IsPermanentError checks if err represents a permanent failure. Permanent failures should not be retried. Examples: validation errors, authentication errors, not found errors.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable checks if an error should trigger a retry. It checks in priority order: 1. Context errors (DeadlineExceeded, Canceled) - NOT retryable 2. Any error implementing Retryable interface (generic check) 3. Typed sentinel errors (ErrRateLimited, ErrNetworkTimeout, etc.) 4. HTTPError with retryable status codes (429, 5xx) 5. Defensive fallback for untyped rate limit messages

CRITICAL: Context errors are checked FIRST because some error types implement IsRetryable() but may wrap context errors. If context.DeadlineExceeded is wrapped, retrying with the same context will fail immediately - these operations should be abandoned, not retried.

The generic Retryable interface check (step 2) works with error types from any package, not just go-errors. External packages can define their own error types with IsRetryable() methods, and they will be properly detected.

Example usage:

if err != nil {
    if IsRetryable(err) {
        // Use exponential backoff
        time.Sleep(backoff)
        continue
    }
    return err // Permanent failure
}

func IsRetryableTimeout

func IsRetryableTimeout(err error) bool

IsRetryableTimeout checks if err is a retryable timeout. Returns false for context.DeadlineExceeded (parent context expired). Returns true for other timeout errors (network timeouts, API timeouts, etc.).

func IsTimeout

func IsTimeout(err error) bool

IsTimeout checks if err is a timeout error (TimeoutError or net.Error with Timeout()).

func IsTransientError

func IsTransientError(err error) bool

IsTransientError checks if err represents a transient failure. Transient failures are temporary and should be retried. Examples: network errors, rate limits, server errors.

func IsValidation

func IsValidation(err error) bool

IsValidation checks if err is a ValidationError.

func NewCircuitBreakerError

func NewCircuitBreakerError(message, operation, state string, opts ...Option) error

NewCircuitBreakerError creates a CircuitBreakerError with automatic stack trace.

func NewHTTPError

func NewHTTPError(statusCode int, message string, cause error) error

NewHTTPError creates an HTTPError with automatic stack trace.

func NewInternalError added in v1.0.6

func NewInternalError(message string, cause error) error

NewInternalError creates an HTTPError with status 500 (Internal Server Error). This is a convenience wrapper for API/backend services.

func NewNetworkError

func NewNetworkError(message, operation string, opts ...Option) error

NewNetworkError creates a NetworkError with automatic stack trace.

func NewNotFoundError added in v1.0.8

func NewNotFoundError(message string, cause error) error

NewNotFoundError creates an HTTPError with status 404 (Not Found). This is a convenience wrapper for API/backend services.

func NewProcessingError

func NewProcessingError(message, operation string, opts ...Option) error

NewProcessingError creates a ProcessingError with automatic stack trace.

func NewRateLimitError

func NewRateLimitError(message, operation string, retryAfter time.Duration, opts ...Option) error

NewRateLimitError creates a RateLimitError with automatic stack trace.

func NewRetryableError added in v1.0.3

func NewRetryableError(message, operation string, retryAfter time.Duration, opts ...Option) error

NewRetryableError creates a RetryableError with automatic stack trace.

func NewRetryableProcessingError

func NewRetryableProcessingError(message, operation string, opts ...Option) error

NewRetryableProcessingError creates a retryable ProcessingError with automatic stack trace.

func NewTimeoutError

func NewTimeoutError(message, operation string, duration time.Duration, opts ...Option) error

NewTimeoutError creates a TimeoutError with automatic stack trace.

func NewValidationError

func NewValidationError(message, field string, opts ...Option) error

NewValidationError creates a ValidationError with automatic stack trace.

Types

type CircuitBreakerError

type CircuitBreakerError struct {
	Message   string
	Operation string
	Component string
	State     string
	Err       error
}

CircuitBreakerError represents circuit breaker protection. Automatically includes stack trace from creation point.

func (*CircuitBreakerError) Error

func (e *CircuitBreakerError) Error() string

func (*CircuitBreakerError) IsRetryable

func (e *CircuitBreakerError) IsRetryable() bool

func (*CircuitBreakerError) Unwrap

func (e *CircuitBreakerError) Unwrap() error

type HTTPError

type HTTPError struct {
	StatusCode int
	Message    string
	Component  string
	Err        error
}

HTTPError wraps HTTP-related errors with status code information. Automatically includes stack trace from creation point.

func IsHTTPError

func IsHTTPError(err error) (*HTTPError, bool)

IsHTTPError checks if err is an HTTPError and returns it.

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) IsRetryable

func (e *HTTPError) IsRetryable() bool

IsRetryable returns true for 5xx errors and 429 (rate limit).

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

type NetworkError

type NetworkError struct {
	Message     string
	Operation   string
	Component   string
	IsTransient bool
	Err         error
}

NetworkError represents a network connectivity failure. Automatically includes stack trace from creation point.

func (*NetworkError) Error

func (e *NetworkError) Error() string

func (*NetworkError) IsRetryable

func (e *NetworkError) IsRetryable() bool

func (*NetworkError) Unwrap

func (e *NetworkError) Unwrap() error

type Option

type Option func(any)

Option is a functional option for configuring error creation. Use with error constructor functions to specify optional fields.

Example:

err := NewProcessingError("Failed to process item", "ProcessItem",
    WithCause(dbErr),
    WithItemID("item-123"),
    WithRetryable(true))

func WithCause

func WithCause(cause error) Option

WithCause sets the underlying cause for an error. Use this to wrap lower-level errors while maintaining the error chain.

Example:

dbErr := db.Query(...)
err := NewProcessingError("Failed to load user", "LoadUser",
    WithCause(dbErr))

func WithComponent added in v1.0.1

func WithComponent(component string) Option

WithComponent sets the component name for an error. Component identifies where the error occurred (e.g., "enricher", "curator", "llm_matcher"). Applies to all error types that have a Component field.

Example:

err := NewProcessingError("Failed to enrich activity", "EnrichActivity",
    WithComponent("enricher"),
    WithItemID(activity.ID))

func WithField

func WithField(field string) Option

WithField sets the field name for validation errors. Only applies to ValidationError types, ignored for others.

Example:

err := NewValidationError("Invalid format", "",
    WithField("email"))

func WithItemID

func WithItemID(itemID string) Option

WithItemID sets the item ID for processing errors. Only applies to ProcessingError types, ignored for others.

Example:

err := NewProcessingError("Failed to process activity", "ProcessActivity",
    WithItemID("activity-123"))

func WithMessage

func WithMessage(message string) Option

WithMessage sets the message for errors that support it. Applies to most error types.

Example:

err := NewHTTPError(500, "Internal Server Error", nil,
    WithMessage("Database connection failed"))

func WithOperation

func WithOperation(operation string) Option

WithOperation sets the operation name for errors that support it. Applies to TimeoutError, RateLimitError, ProcessingError, NetworkError, and CircuitBreakerError.

Example:

err := NewTimeoutError("API call timed out", "GetUser", 30*time.Second)

func WithRetryable

func WithRetryable(retryable bool) Option

WithRetryable sets whether a processing error is retryable. Only applies to ProcessingError types, ignored for others.

Example:

err := NewProcessingError("Database connection lost", "SaveData",
    WithRetryable(true))

func WithState

func WithState(state string) Option

WithState sets the circuit breaker state. Only applies to CircuitBreakerError types, ignored for others.

Example:

err := NewCircuitBreakerError("Too many failures", "CallAPI", "",
    WithState("open"))

func WithStatusCode

func WithStatusCode(statusCode int) Option

WithStatusCode sets the HTTP status code. Only applies to HTTPError types, ignored for others.

Example:

err := NewHTTPError(0, "Server error", cause,
    WithStatusCode(503))

func WithTransient

func WithTransient(transient bool) Option

WithTransient sets whether a network error is transient. Only applies to NetworkError types, ignored for others.

Example:

err := NewNetworkError("DNS lookup failed", "Connect",
    WithTransient(false))

func WithValue

func WithValue(value any) Option

WithValue sets the value field for validation errors. Only applies to ValidationError types, ignored for others.

Example:

err := NewValidationError("Price must be positive", "price",
    WithValue(-10))

type ProcessingError

type ProcessingError struct {
	Message   string
	Operation string
	ItemID    string
	Component string
	Retryable bool
	Err       error
}

ProcessingError represents an error during data processing. Automatically includes stack trace from creation point.

func (*ProcessingError) Error

func (e *ProcessingError) Error() string

func (*ProcessingError) IsRetryable

func (e *ProcessingError) IsRetryable() bool

func (*ProcessingError) Unwrap

func (e *ProcessingError) Unwrap() error

type RateLimitError

type RateLimitError struct {
	Message    string
	Operation  string
	Component  string
	RetryAfter time.Duration
	Err        error
}

RateLimitError represents rate limiting with retry-after duration. Automatically includes stack trace from creation point.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

func (*RateLimitError) IsRetryable

func (e *RateLimitError) IsRetryable() bool

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

type Retryable

type Retryable interface {
	IsRetryable() bool
}

Retryable interface defines errors that can be retried. Implement this interface on custom error types to enable intelligent retry logic without string parsing.

type RetryableError added in v1.0.3

type RetryableError struct {
	Message    string
	Operation  string
	Component  string
	RetryAfter time.Duration
	Err        error
}

RetryableError represents a generic retryable error with retry-after duration. More general than RateLimitError - can be used for any temporary failure. Automatically includes stack trace from creation point.

func (*RetryableError) Error added in v1.0.3

func (e *RetryableError) Error() string

func (*RetryableError) IsRetryable added in v1.0.3

func (e *RetryableError) IsRetryable() bool

func (*RetryableError) Unwrap added in v1.0.3

func (e *RetryableError) Unwrap() error

type TimeoutError

type TimeoutError struct {
	Message   string
	Operation string
	Component string
	Duration  time.Duration
	Err       error
}

TimeoutError represents an operation that exceeded its deadline. Automatically includes stack trace from creation point.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

func (*TimeoutError) IsRetryable

func (e *TimeoutError) IsRetryable() bool

func (*TimeoutError) Unwrap

func (e *TimeoutError) Unwrap() error

type ValidationError

type ValidationError struct {
	Message   string
	Field     string
	Component string
	Value     any
	Err       error
}

ValidationError represents a data validation failure. Automatically includes stack trace from creation point.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) IsRetryable

func (e *ValidationError) IsRetryable() bool

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL