errorsx

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2025 License: MIT Imports: 5 Imported by: 0

README

go-errorsx

Go Reference Go Report Card CI

A comprehensive error handling library for Go that provides structured, chainable errors with stack traces, error types, and enhanced context.

Features

  • Structured Errors: Create errors with IDs, types, and custom messages
  • Stack Traces: Automatic stack trace capture with customizable cleaning
  • Error Chaining: Chain errors with cause relationships
  • Type Classification: Categorize errors with custom types
  • HTTP Integration: Built-in HTTP status code support
  • Validation Errors: Specialized support for form validation with field-level errors
  • JSON Marshaling: Seamless JSON serialization for API responses
  • Error Joining: Combine multiple errors into a single error
  • Message Extraction: Type-safe message data extraction
  • Retryable Errors: Mark errors as retryable for resilient operations

Installation

go get github.com/hacomono-lib/go-errorsx

Quick Start

package main

import (
    "fmt"
    "github.com/hacomono-lib/go-errorsx"
)

func main() {
    // Create a simple error
    err := errorsx.New("user.not.found").
        WithReason("User with ID %d not found", 123).
        WithType(errorsx.TypeValidation).
        WithHTTPStatus(404)
    
    fmt.Println(err.Error()) // Output: User with ID 123 not found
}

Core Concepts

Error Creation

Create errors with unique identifiers and optional configurations:

// Basic error
err := errorsx.New("database.connection.failed")

// Error with reason and type
err := errorsx.New("validation.failed").
    WithReason("Invalid email format").
    WithType(errorsx.TypeValidation)

// Error with cause (automatically captures stack trace)
baseErr := errors.New("connection refused")
err := errorsx.New("database.error").
    WithCause(baseErr)
Error Types

Classify errors using built-in or custom types:

const (
    TypeBusiness   errorsx.ErrorType = "business"
    TypeInfrastructure errorsx.ErrorType = "infrastructure"
)

err := errorsx.New("payment.failed").WithType(TypeBusiness)

// Check error type
if errorsx.HasType(err, TypeBusiness) {
    // Handle business logic error
}

// Filter errors by type
businessErrors := errorsx.FilterByType(err, TypeBusiness)
Design Philosophy: ID vs Type

The library follows a clear separation of concerns:

  • Error ID: Represents the unique identity of an error (e.g., "user.not.found", "payment.failed")
  • Error Type: Represents the category or classification for grouping errors (e.g., "validation", "business", "infrastructure")

This design allows:

// Same logical error in different contexts
userErr := errorsx.New("user.not.found").WithType(TypeBusiness)
adminErr := errorsx.New("user.not.found").WithType(TypeSecurity)

// They are considered the same error for handling purposes
if errors.Is(userErr, adminErr) {
    // This returns true - same ID means same logical error
    // regardless of classification
}

// But can be handled differently based on type
if errorsx.HasType(userErr, TypeBusiness) {
    // Handle as business logic error
}
if errorsx.HasType(adminErr, TypeSecurity) {
    // Handle as security-related error
}

Key Benefits:

  • Flexible Error Handling: The same logical error can be handled consistently across different contexts
  • Clear Separation: Identity (what happened) vs Classification (how to categorize it)
  • Reusable Error Definitions: Error IDs can be reused across different layers or modules
Validation Errors

Handle form validation with field-level error details:

validationErr := errorsx.NewValidationError("form.validation.failed").
    WithHTTPStatus(400).
    WithMessage("Form validation failed")

// Simple string messages
validationErr.AddFieldError("email", "required", "Email is required")

// Complex message data
validationErr.AddFieldError("password", "min_length", map[string]any{
    "min":     8,
    "current": 3,
})

// Translation data
validationErr.AddFieldError("age", "range", struct {
    Key    string `json:"key"`
    Params map[string]any `json:"params"`
}{
    Key:    "validation.age.range",
    Params: map[string]any{"min": 18, "max": 65},
})

// JSON output will include structured field errors
jsonData, _ := json.Marshal(validationErr)
Message Data

Attach structured data to errors for UI display:

type UserErrorData struct {
    UserID   int    `json:"user_id"`
    Username string `json:"username"`
}

err := errorsx.New("user.not.found").
    WithMessage(UserErrorData{
        UserID:   123,
        Username: "johndoe",
    })

// Extract message data with type safety
if data, ok := errorsx.Message[UserErrorData](err); ok {
    fmt.Printf("User %s (ID: %d) not found", data.Username, data.UserID)
}

// Or use with fallback
data := errorsx.MessageOr(err, UserErrorData{UserID: -1, Username: "unknown"})
Error Joining

Combine multiple errors:

err1 := errorsx.New("validation.email")
err2 := errorsx.New("validation.password")
err3 := errors.New("network.timeout")

combined := errorsx.Join(err1, err2, err3)
fmt.Println(combined.Error()) // All errors joined with "; "
Stack Traces

Capture and clean stack traces:

// Manually capture stack trace
err := errorsx.New("process.failed").WithCallerStack()

// Stack trace is automatically captured when using WithCause
baseErr := errors.New("underlying error")
err := errorsx.New("wrapper.error").WithCause(baseErr)

// Get full stack trace
stackTrace := errorsx.FullStackTrace(err)

// Use custom stack trace cleaner
err = errorsx.New("error").
    WithCallerStack().
    WithStackTraceCleaner(func(frames []string) []string {
        // Custom cleaning logic
        return frames
    })

JSON Logging

Errors can be easily serialized to JSON for structured logging:

// Basic error with JSON output
err := errorsx.New("user.not.found").
    WithType(errorsx.TypeValidation).
    WithHTTPStatus(404).
    WithMessage(map[string]interface{}{
        "user_id": 123,
        "action":  "fetch_profile",
    })

jsonData, _ := json.Marshal(err)
fmt.Println(string(jsonData))

Output:

{
  "id": "user.not.found",
  "type": "errorsx.validation",
  "message_data": {
    "user_id": 123,
    "action": "fetch_profile"
  }
}
Validation Error JSON
validationErr := errorsx.NewValidationError("form.validation.failed").
    WithHTTPStatus(400).
    WithMessage("Form validation failed")

validationErr.AddFieldError("email", "required", "Email is required")
validationErr.AddFieldError("password", "min_length", map[string]any{
    "min": 8, "current": 3,
})
validationErr.AddFieldError("age", "range", struct {
    Min int `json:"min"`
    Max int `json:"max"`
}{Min: 18, Max: 65})

jsonData, _ := json.Marshal(validationErr)
fmt.Println(string(jsonData))

Output:

{
  "id": "form.validation.failed",
  "type": "errorsx.validation",
  "message_data": "Form validation failed",
  "message": "Validation failed with 3 error(s)",
  "field_errors": [
    {
      "field": "email",
      "code": "required",
      "message": "Email is required",
      "translated_message": "Email is required"
    },
    {
      "field": "password",
      "code": "min_length",
      "message": {
        "min": 8,
        "current": 3
      },
      "translated_message": "Password must be at least 8 characters"
    },
    {
      "field": "age",
      "code": "range",
      "message": {
        "min": 18,
        "max": 65
      },
      "translated_message": "Age must be between 18 and 65"
    }
  ]
}
Logging Integration
import (
    "log/slog"
    "github.com/hacomono-lib/go-errorsx"
)

func logError(err error) {
    var xerr *errorsx.Error
    if errors.As(err, &xerr) {
        // slog automatically handles JSON marshaling for errorsx.Error
        slog.Error("Operation failed", "error", xerr)
        
        // Or with additional context
        slog.Error("Operation failed",
            "error", xerr,
            "user_id", 123,
            "operation", "fetch_user",
        )
    } else {
        slog.Error("Operation failed", "error", err)
    }
}

HTTP Integration

Seamlessly integrate with HTTP handlers:

func handler(w http.ResponseWriter, r *http.Request) {
    user, err := getUserByID(123)
    if err != nil {
        var xerr *errorsx.Error
        if errors.As(err, &xerr) {
            w.WriteHeader(xerr.HTTPStatus())
            json.NewEncoder(w).Encode(xerr)
            return
        }
        // Handle non-errorsx errors
        w.WriteHeader(500)
        return
    }
    
    json.NewEncoder(w).Encode(user)
}

Advanced Usage

Custom Error Types

Define domain-specific error types:

const (
    TypeAuthentication errorsx.ErrorType = "auth"
    TypeAuthorization  errorsx.ErrorType = "authz"
    TypeRateLimit      errorsx.ErrorType = "rate_limit"
)

err := errorsx.New("auth.invalid.token").WithType(TypeAuthentication)
Error Wrapping and Unwrapping
originalErr := errors.New("database connection failed")
wrappedErr := errorsx.New("user.fetch.failed").WithCause(originalErr)

// Unwrap to get the original error
if errors.Is(wrappedErr, originalErr) {
    // Handle database connection issues
}
Retryable Errors

Mark errors as retryable to implement resilient error handling:

// Create retryable error
err := errorsx.New("service.unavailable").WithRetryable()

// Or use the convenience constructor
err := errorsx.NewRetryable("connection.timeout")

// Or use the option
err := errorsx.New("rate.limit.exceeded", errorsx.WithRetryable())

// Check if error is retryable
if errorsx.IsRetryable(err) {
    // Implement retry logic
    for i := 0; i < maxRetries; i++ {
        if err := operation(); err == nil {
            break
        } else if !errorsx.IsRetryable(err) {
            return err // Don't retry non-retryable errors
        }
        time.Sleep(backoff)
    }
}

// Retryable status is preserved in JSON
jsonData, _ := json.Marshal(err)
// Output includes: "is_retryable": true
Validation with Translation Support

The library provides built-in translation support for both summary messages and individual field errors:

// Custom translators
summaryTranslator := func(fieldErrors []errorsx.FieldError, messageData any) string {
    count := len(fieldErrors)
    if count == 1 {
        return "There is 1 validation error"
    }
    return fmt.Sprintf("There are %d validation errors", count)
}

fieldTranslator := func(field, code string, message any) string {
    switch code {
    case "required":
        return fmt.Sprintf("%s is required", strings.Title(field))
    case "min_length":
        if data, ok := message.(map[string]any); ok {
            if min, ok := data["min"].(int); ok {
                return fmt.Sprintf("%s must be at least %d characters", strings.Title(field), min)
            }
        }
    }
    return fmt.Sprintf("%v", message)
}

// Apply translators
validationErr := errorsx.NewValidationError("form.invalid").
    WithSummaryTranslator(summaryTranslator).
    WithFieldTranslator(fieldTranslator).
    WithMessage("Form validation failed")

// Add field errors - translators will be applied automatically
validationErr.AddFieldError("email", "required", nil)
validationErr.AddFieldError("password", "min_length", map[string]any{"min": 8})

// Error() and JSON output will use translated messages
fmt.Println(validationErr.Error())
// Output: form.invalid: Email: Email is required; Password: Password must be at least 8 characters
Translation with i18n Libraries
// Example with go-i18n or similar
fieldTranslator := func(field, code string, message any) string {
    key := fmt.Sprintf("validation.%s.%s", field, code)
    
    // Use your i18n library
    return i18n.Translate(key, message)
}

validationErr.WithFieldTranslator(fieldTranslator)

API Reference

Core Types
  • Error: Main error type with ID, type, message, and stack trace
  • ErrorType: String-based error classification
  • ValidationError: Specialized error for form validation
  • FieldError: Individual field validation error
Key Functions
  • New(id string, opts ...Option) *Error: Create new error
  • NewRetryable(id string, opts ...Option) *Error: Create new retryable error
  • Join(errs ...error) error: Combine multiple errors
  • Message[T](err error) (T, bool): Extract typed message data
  • FilterByType(err error, typ ErrorType) []*Error: Filter errors by type
  • HasType(err error, typ ErrorType) bool: Check if error has specific type
  • IsRetryable(err error) bool: Check if error is retryable
Options
  • WithType(ErrorType): Set error type
  • WithHTTPStatus(int): Set HTTP status code
  • WithCallerStack(): Capture stack trace from caller
  • WithCause(error): Set underlying cause and automatically capture stack trace
  • WithMessage(any): Attach message data
  • WithRetryable(): Mark error as retryable

Note: WithCause and WithCallerStack are mutually exclusive. WithCause automatically captures the stack trace, so using both together is not necessary and the second one will be ignored.

Development

Local Development
# Install development tools
make install-tools

# Run tests
make test

# Run tests with coverage
make test-cover

# Run linter
make lint

# Run all checks
make ci
Container Development

For developers who prefer containerized development or want to avoid polluting their local environment:

Using Docker Compose
# Start development environment
make docker-dev

# Run tests in container
make docker-test

# Run linter in container
make docker-lint

# Run security scan in container
make docker-security

# Clean up containers
make docker-clean
Using VS Code Dev Containers
  1. Install the "Dev Containers" extension in VS Code
  2. Open the project in VS Code
  3. Press Ctrl+Shift+P (or Cmd+Shift+P on Mac)
  4. Select "Dev Containers: Reopen in Container"

The development container includes:

  • Go 1.23 with all development tools
  • golangci-lint for code quality
  • gosec for security scanning
  • Git and GitHub CLI
  • Optimized VS Code settings for Go development
Performance Optimizations

The CI/CD pipeline includes several performance optimizations:

  • Multi-level caching: Go modules, build cache, and tool binaries
  • Parallel execution: Tests run concurrently across multiple Go versions
  • Incremental builds: Only rebuild when necessary
  • Container layer caching: Optimized Docker builds

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package errorsx provides structured error handling with advanced features for Go applications, including stack traces, error chaining, type classification, and JSON serialization.

This package is designed for production web applications that need:

  • Structured error information with unique IDs
  • Stack trace capture and management
  • Error type classification for different handling strategies
  • HTTP status code mapping
  • Internationalization support through message data
  • Validation error aggregation
  • JSON serialization for API responses

Basic Usage:

err := errorsx.New("user.not_found",
	errorsx.WithType(errorsx.TypeNotFound),
	errorsx.WithHTTPStatus(404),
	errorsx.WithMessage(map[string]string{"en": "User not found"}),
)

Error Chaining:

err := errorsx.New("database.connection_failed").
	WithCause(originalErr).
	WithCallerStack()

Validation Errors:

verr := errorsx.NewValidationError("validation.failed")
verr.AddFieldError("email", "required", "Email is required")
verr.AddFieldError("age", "min_value", map[string]int{"min": 18})
Example

Example demonstrates basic error creation and configuration.

err := errorsx.New("user.not_found",
	errorsx.WithHTTPStatus(404),
	errorsx.WithMessage("User not found"),
).WithNotFound()

fmt.Println("Error ID:", err.ID())
fmt.Println("Error Type:", err.Type())
fmt.Println("Error Message:", err.Error())
Output:

Error ID: user.not_found
Error Type: errorsx.unknown
Error Message: user.not_found
Example (WebAPI)

Example_webAPI demonstrates a complete web API error handling pattern.

// Simulate a web API handler
err := handleUserCreation("", "weak")
if err != nil {
	// Handle different error types
	switch {
	case errorsx.HasType(err, errorsx.TypeValidation):
		log.Printf("Validation error: %v", err)
		// Return HTTP 422 with validation details
		if verr, ok := err.(*errorsx.ValidationError); ok {
			jsonData, jsonErr := json.Marshal(verr)
			if jsonErr != nil {
				fmt.Printf("JSON marshaling error: %v\n", jsonErr)
				return
			}
			fmt.Printf("HTTP 422 Response: %s\n", jsonData)
		}
	case errorsx.HasType(err, errorsx.TypeUnknown):
		log.Printf("Unknown error: %v", err)
		// Return HTTP 500 with generic message
		fmt.Println("HTTP 500 Response: Internal server error")
	default:
		log.Printf("Unknown error: %v", err)
		fmt.Println("HTTP 500 Response: Unknown error")
	}
}
Output:

HTTP 422 Response: {"id":"user.validation_failed","type":"errorsx.validation","message_data":"Please fix the validation errors","message":"Please fix the validation errors","field_errors":[{"field":"email","code":"required","message":"Email is required","translated_message":"Email is required"},{"field":"password","code":"weak","message":"Password is too weak","translated_message":"Password is too weak"}]}

Index

Examples

Constants

View Source
const (
	// MaxStackFrames defines the maximum number of stack frames to capture
	// when creating a stack trace. This prevents excessive memory usage
	// while still providing sufficient debugging information.
	MaxStackFrames = 32
)

Variables

This section is empty.

Functions

func DefaultFieldTranslator

func DefaultFieldTranslator(field, code string, message any) string

DefaultFieldTranslator returns the message as-is if it's a string, otherwise formats it.

func DefaultSummaryTranslator

func DefaultSummaryTranslator(fieldErrors []FieldError, messageData any) string

DefaultSummaryTranslator returns a simple summary message.

func FullStackTrace

func FullStackTrace(err error) string

FullStackTrace returns the full stack trace chain for the error.

func HTTPStatus

func HTTPStatus(err error) int

HTTPStatus extracts the HTTP status code from any error. If the error is an errorsx.Error with a status code, returns that code. Otherwise returns 0.

This function enables HTTP status code extraction from any error in an error chain, making it useful for middleware and error handlers.

Example:

status := errorsx.HTTPStatus(err)
if status != 0 {
	w.WriteHeader(status)
} else {
	w.WriteHeader(500) // Default to internal server error
}

Returns 0 if no HTTP status is found or if err is nil.

func HasType

func HasType(err error, typ ErrorType) bool

HasType checks if an error chain contains any errors of the specified ErrorType. This is a convenience function that returns true if FilterByType would return a non-empty slice.

Example:

if errorsx.HasType(err, errorsx.TypeValidation) {
	// Handle validation errors
	return handleValidationError(err)
}

This function is more efficient than FilterByType when you only need to check for the presence of a specific error type without accessing the errors themselves.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound checks if any error in the error chain represents a "not found" condition. This function works with any error type and traverses the error chain to find errorsx.Error instances marked as "not found".

Example:

if errorsx.IsNotFound(err) {
	// Handle not found case - typically return 404
	return handleNotFound()
}

Returns false if err is nil or no "not found" errors are found in the chain.

func IsRetryable added in v0.1.2

func IsRetryable(err error) bool

IsRetryable checks if any error in the error chain represents a retryable condition. This function works with any error type and traverses the error chain to find errorsx.Error instances marked as retryable.

Example:

if errorsx.IsRetryable(err) {
	// Retry the operation
	return retryOperation()
}

Returns false if err is nil or no retryable errors are found in the chain.

func Join

func Join(errs ...error) error

Join returns an error that wraps the given errors. Any nil error values are discarded. Returns nil if all errors are nil. The error formats as the concatenation of the strings obtained by calling the Error method of each element with a separating "; ".

This function is compatible with Go's standard errors.Join behavior and supports both errors.Is and errors.As for unwrapping.

Example:

err1 := errorsx.New("validation.email")
err2 := errorsx.New("validation.password")
combined := errorsx.Join(err1, err2)
// combined.Error() returns "validation.email; validation.password"
Example

ExampleJoin demonstrates joining multiple errors together.

err1 := errorsx.New("validation.email", errorsx.WithType(errorsx.TypeValidation))
err2 := errorsx.New("validation.password", errorsx.WithType(errorsx.TypeValidation))
err3 := errors.New("database connection failed")

// Join multiple errors
combined := errorsx.Join(err1, err2, err3)

fmt.Println("Combined error:", combined.Error())

// Check for specific errors
if errors.Is(combined, err1) {
	fmt.Println("Email validation error found")
}

// Filter by type
validationErrors := errorsx.FilterByType(combined, errorsx.TypeValidation)
fmt.Printf("Found %d validation errors\n", len(validationErrors))
Output:

Combined error: validation.email; validation.password; database connection failed
Email validation error found
Found 2 validation errors

func Message

func Message[T any](err error) (T, bool)

Message extracts typed message data from an error. It performs type assertion to convert the message data to the specified type T. Returns the message data and true if successful, or zero value and false if the error is not an errorsx.Error or the type assertion fails.

Example:

err := errorsx.New("user.not_found").WithMessage(map[string]string{
	"en": "User not found",
})

if msg, ok := errorsx.Message[map[string]string](err); ok {
	fmt.Println(msg["en"]) // "User not found"
}

This function is particularly useful for extracting structured message data such as translation maps or validation error details.

func MessageOr

func MessageOr[T any](err error, fallback T) T

MessageOr extracts typed message data from an error with a fallback value. If the error is not an errorsx.Error or the type assertion fails, it returns the provided fallback value instead of a zero value.

Example:

msg := errorsx.MessageOr(err, "Unknown error")
// msg will be the extracted message or "Unknown error" if extraction fails

This function provides a convenient way to safely extract message data without needing to check the boolean return value.

func ReplaceMessage

func ReplaceMessage(err error, data any) error

ReplaceMessage replaces the message data of any error. If the error is an errorsx.Error, it returns a copy with the new message data. If the error is a standard Go error, it wraps it in a new errorsx.Error with the provided message data.

This function is useful for adding user-friendly messages to errors that may come from external libraries or system calls.

Example:

err := os.Open("nonexistent.txt") // returns *os.PathError
userErr := errorsx.ReplaceMessage(err, "File not found")
// userErr is now an errorsx.Error with the original error as cause
Example

ExampleReplaceMessage demonstrates adding user-friendly messages to any error.

// Start with a standard Go error
originalErr := errors.New("sql: no rows in result set")

// Add user-friendly message
userErr := errorsx.ReplaceMessage(originalErr, "User not found")

fmt.Println("Original error:", originalErr.Error())
fmt.Println("User-friendly error:", userErr.Error())

// The original error is still accessible
if errors.Is(userErr, originalErr) {
	fmt.Println("Original error is still in the chain")
}
Output:

Original error: sql: no rows in result set
User-friendly error: unknown.error
Original error is still in the chain

func RootCause

func RootCause(err error) error

RootCause returns the deepest error in the error chain. If an *Error with a cause is found, it follows the cause; otherwise, it unwraps. Returns the last error in the chain (the root cause).

func RootStackTrace

func RootStackTrace(err error) string

RootStackTrace returns the stack trace of the root cause error, if available.

Types

type Error

type Error struct {
	// contains filtered or unexported fields
}

Error represents a structured, chainable error with stack trace and attributes. It provides enhanced error handling capabilities beyond Go's standard error interface, including unique identification, type classification, HTTP status mapping, and structured message data for internationalization.

Error implements the standard error interface and supports error unwrapping through the Unwrap() method, making it compatible with Go's error handling patterns including errors.Is() and errors.As().

func FilterByType

func FilterByType(err error, typ ErrorType) []*Error

FilterByType recursively searches an error chain and returns all errorsx.Error instances that match the specified ErrorType. This function traverses both simple error chains (via Unwrap()) and joined errors (multiple errors).

The function prevents duplicate results by tracking already-seen errors.

Example:

err1 := errorsx.New("validation.required", errorsx.WithType(errorsx.TypeValidation))
err2 := errorsx.New("validation.format", errorsx.WithType(errorsx.TypeValidation))
combined := errorsx.Join(err1, err2)

validationErrors := errorsx.FilterByType(combined, errorsx.TypeValidation)
// Returns []*Error containing both validation errors

Returns an empty slice if no errors of the specified type are found.

Example

ExampleFilterByType demonstrates filtering errors by type.

// Define custom error type
const TypeAuthentication errorsx.ErrorType = "myapp.authentication"

// Create multiple errors of different types
validationErr := errorsx.New("email.invalid", errorsx.WithType(errorsx.TypeValidation))
authErr := errorsx.New("token.expired", errorsx.WithType(TypeAuthentication))

// Join errors together
combined := errorsx.Join(validationErr, authErr)

// Filter by validation type
validationErrors := errorsx.FilterByType(combined, errorsx.TypeValidation)
fmt.Printf("Found %d validation errors\n", len(validationErrors))

// Check if authentication errors exist
hasAuth := errorsx.HasType(combined, TypeAuthentication)
fmt.Printf("Has authentication errors: %v\n", hasAuth)
Output:

Found 1 validation errors
Has authentication errors: true

func New

func New(id string, opts ...Option) *Error

New creates a new Error with the given id and options. The id serves as a unique identifier for the error type and is used for error comparison with errors.Is().

Example:

err := errorsx.New("user.not_found",
	errorsx.WithType(errorsx.TypeNotFound),
	errorsx.WithHTTPStatus(404),
	errorsx.WithMessage("User not found"),
)

The id should follow a hierarchical naming convention (e.g., "domain.operation.reason") to facilitate error categorization and handling.

func NewNotFound

func NewNotFound(idOrMsg string) *Error

NewNotFound creates a new "not found" error with the given ID. This is a convenience constructor for the common pattern of creating errors that represent missing resources.

Example:

err := errorsx.NewNotFound("user.not_found")
// Equivalent to: errorsx.New("user.not_found").WithNotFound()

func NewRetryable added in v0.1.2

func NewRetryable(idOrMsg string) *Error

NewRetryable creates a new retryable error with the given ID. This is a convenience constructor for creating errors that indicate the operation can be safely retried.

Example:

err := errorsx.NewRetryable("connection.timeout")
// Equivalent to: errorsx.New("connection.timeout").WithRetryable()

func (*Error) Error

func (e *Error) Error() string

Error implements the standard Go error interface. It returns the technical message set by WithReason(), or the error ID if no specific message was provided.

func (*Error) HTTPStatus

func (e *Error) HTTPStatus() int

HTTPStatus returns the HTTP status code associated with this error. Returns 0 if no HTTP status code was set.

This method is typically used by web frameworks or middleware to determine the appropriate HTTP response code for an error.

func (*Error) ID

func (e *Error) ID() string

ID returns the unique identifier of the error. This ID is used for error comparison and categorization.

func (*Error) Is

func (e *Error) Is(target error) bool

Is implements custom error comparison for errors.Is(). Two errorsx.Error instances are considered equal if they have the same ID. For non-errorsx errors, it delegates to the underlying error's Is method or compares the cause error.

This enables error identification based on semantic meaning rather than instance equality, supporting error handling patterns like:

if errors.Is(err, errorsx.New("user.not_found")) {
	// Handle user not found error
}

func (*Error) IsNotFound

func (e *Error) IsNotFound() bool

IsNotFound returns true if this error represents a "not found" condition. This provides a semantic way to check for missing resources or entities.

func (*Error) IsRetryable added in v0.1.2

func (e *Error) IsRetryable() bool

IsRetryable returns true if this error represents a retryable condition. This provides a semantic way to check if an operation can be safely retried.

func (*Error) MarshalJSON

func (e *Error) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface for Error, providing structured output for logging and APIs.

func (*Error) StackFrames added in v0.1.1

func (e *Error) StackFrames() []uintptr

StackFrames returns the stack frames for sentry-go compatibility. This method is compatible with go-errors/errors and other libraries that expect a StackFrames() []uintptr method for extracting stack traces.

If multiple stack traces are available, it returns the most recent one (the first in the slice) as this represents the most direct error location. Returns an empty slice if no stack traces are available.

func (*Error) Stacks

func (e *Error) Stacks() []StackTrace

Stacks returns the stack traces associated with the error.

func (*Error) Type

func (e *Error) Type() ErrorType

Type returns the ErrorType of the error. Returns TypeUnknown if no specific type was set.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause error, enabling Go's error unwrapping functionality. This allows errors.Is() and errors.As() to traverse the error chain to find specific error types or values.

Returns nil if no underlying cause was set.

func (*Error) WithCallerStack

func (e *Error) WithCallerStack() *Error

WithCallerStack returns a copy of the error with a stack trace captured from the caller's location. This is a convenience method equivalent to WithStack(1), which excludes the WithCallerStack call itself from the stack trace.

This is the most commonly used method for adding stack traces, as it captures the location where the error was created or wrapped, not the location of the WithCallerStack call.

Example:

func processUser(id string) error {
	if user := findUser(id); user == nil {
		return errorsx.New("user.not_found").WithCallerStack()
		// Stack trace will point to this line, not inside WithCallerStack
	}
	return nil
}
Example

ExampleError_WithCallerStack demonstrates stack trace capture.

err := createUserError()

// Check if error has stack trace
if xerr, ok := err.(*errorsx.Error); ok {
	stacks := xerr.Stacks()
	if len(stacks) > 0 {
		fmt.Printf("Stack trace captured with %d frames\n", len(stacks[0].Frames))
		fmt.Printf("Stack message: %s\n", stacks[0].Msg)
	}
}
Output:

Stack trace captured with 8 frames
Stack message: user.creation_failed

func (*Error) WithCause

func (e *Error) WithCause(cause error) *Error

WithCause returns a copy of the error with the specified underlying cause. If the error doesn't already have a stack trace, this method automatically captures one to preserve the error's origin point.

This method is essential for error chaining, allowing you to wrap lower-level errors with higher-level context while maintaining the full error chain.

Parameters:

  • cause: The underlying error that caused this error

Example:

// Wrap a database error with business logic context
dbErr := db.Query("SELECT ...")
if dbErr != nil {
	return errorsx.New("user.fetch_failed").
		WithCause(dbErr).
		WithMessage("Failed to fetch user data")
}

The resulting error chain allows for:

  • errors.Is(err, dbErr) // true
  • errors.Unwrap(err) // returns dbErr
  • Stack trace pointing to the WithCause call location
Example

ExampleError_WithCause demonstrates error chaining and stack traces.

// Simulate a low-level error
dbErr := errors.New("connection timeout")

// Wrap with business logic error
err := errorsx.New("user.fetch_failed").
	WithCause(dbErr).
	WithMessage("Failed to fetch user data")

// Check if the original error is in the chain
if errors.Is(err, dbErr) {
	fmt.Println("Original database error found in chain")
}

// Unwrap to get the original error
originalErr := errors.Unwrap(err)
fmt.Println("Original error:", originalErr.Error())
Output:

Original database error found in chain
Original error: connection timeout

func (*Error) WithHTTPStatus

func (e *Error) WithHTTPStatus(status int) *Error

WithHTTPStatus returns a copy of the error with the specified HTTP status code. This is useful for web applications that need to map errors to appropriate HTTP response codes.

Example:

err := errorsx.New("user.not_found").
	WithHTTPStatus(404).
	WithMessage("User not found")

Common HTTP status codes for errors:

  • 400 Bad Request: Client errors, validation failures
  • 401 Unauthorized: Authentication required
  • 403 Forbidden: Access denied
  • 404 Not Found: Resource not found
  • 409 Conflict: Resource conflicts
  • 422 Unprocessable Entity: Semantic validation errors
  • 500 Internal Server Error: Server-side errors

func (*Error) WithMessage

func (e *Error) WithMessage(data any) *Error

WithMessage returns a copy of the error with the given message data. This data is typically used for user-facing messages and can be any type, often a string or a map for internationalization.

Example with string message:

err := errorsx.New("user.not_found").WithMessage("User not found")

Example with i18n data:

err := errorsx.New("user.not_found").WithMessage(map[string]string{
	"en": "User not found",
	"ja": "ユーザーが見つかりません",
})

The message data can be extracted using the Message() or MessageOr() functions.

Example

ExampleError_WithMessage demonstrates setting user-facing message data.

// Simple string message
err1 := errorsx.New("user.not_found").
	WithMessage("User not found")

// Internationalization data
err2 := errorsx.New("user.not_found").
	WithMessage(map[string]string{
		"en": "User not found",
		"ja": "ユーザーが見つかりません",
	})

// Extract English message
if msg, ok := errorsx.Message[map[string]string](err2); ok {
	fmt.Println("English:", msg["en"])
	fmt.Println("Japanese:", msg["ja"])
}

// Using MessageOr for fallback
simpleMsg := errorsx.MessageOr[string](err1, "Unknown error")
fmt.Println("Simple message:", simpleMsg)
Output:

English: User not found
Japanese: ユーザーが見つかりません
Simple message: User not found

func (*Error) WithNotFound

func (e *Error) WithNotFound() *Error

WithNotFound returns a copy of the error marked as a "not found" error. This is a convenience method for common "not found" scenarios.

Example:

err := errorsx.New("user.not_found").
	WithNotFound().
	WithHTTPStatus(404)

func (*Error) WithReason

func (e *Error) WithReason(reason string, params ...any) *Error

WithReason returns a copy of the error with the given technical message. This message is intended for logging and debugging purposes and supports format string parameters similar to fmt.Sprintf.

Example:

err := errorsx.New("database.query_failed").
	WithReason("Failed to execute query: %s", query)

Note: This creates a shallow copy of the error, preserving the original error's stack traces and other attributes.

Example

ExampleError_WithReason demonstrates setting technical error messages.

err := errorsx.New("database.query_failed").
	WithReason("Failed to execute query: %s", "SELECT * FROM users")

fmt.Println(err.Error())
Output:

Failed to execute query: SELECT * FROM users

func (*Error) WithRetryable added in v0.1.2

func (e *Error) WithRetryable() *Error

WithRetryable returns a copy of the error marked as retryable. This indicates that the operation that caused the error can be safely retried.

Example:

err := errorsx.New("network.timeout").
	WithRetryable().
	WithHTTPStatus(503)
Example

ExampleError_WithRetryable demonstrates retryable error handling.

// Create a retryable error for temporary failures
err := errorsx.New("service.temporarily_unavailable").
	WithRetryable().
	WithHTTPStatus(503).
	WithMessage("Service temporarily unavailable. Please try again.")

// Check if error is retryable
if errorsx.IsRetryable(err) {
	fmt.Println("Error is retryable")
	fmt.Printf("HTTP Status: %d\n", err.HTTPStatus())
}

// Using the convenience constructor
timeoutErr := errorsx.NewRetryable("connection.timeout").
	WithReason("Connection timed out after 30 seconds")

fmt.Printf("Timeout error is retryable: %v\n", errorsx.IsRetryable(timeoutErr))
Output:

Error is retryable
HTTP Status: 503
Timeout error is retryable: true

func (*Error) WithStack

func (e *Error) WithStack(skip int) *Error

WithStack returns a copy of the error with a stack trace captured at the specified skip level. The skip parameter determines how many stack frames to skip when capturing the trace, allowing you to exclude wrapper functions from the stack trace.

If the error already has a stack trace (isStacked is true), it returns the error unchanged to prevent duplicate stack traces in the same error chain.

Parameters:

  • skip: Number of stack frames to skip (0 = include WithStack call, 1 = exclude it)

Example:

// Capture stack trace including this function call
err := errorsx.New("something.failed").WithStack(0)

// Capture stack trace excluding this function call
err := errorsx.New("something.failed").WithStack(1)

func (*Error) WithStackTraceCleaner

func (e *Error) WithStackTraceCleaner(cleaner StackTraceCleaner) *Error

WithStackTraceCleaner returns a copy of the error with a custom stack trace cleaner function. The cleaner function will be applied when the stack trace is formatted for display, allowing for customization of the stack trace output.

Example:

cleaner := func(frames []string) []string {
	// Filter out Go runtime frames
	var filtered []string
	for _, frame := range frames {
		if !strings.Contains(frame, "runtime.") {
			filtered = append(filtered, frame)
		}
	}
	return filtered
}

err := errorsx.New("db.connection_failed").
	WithCallerStack().
	WithStackTraceCleaner(cleaner)

func (*Error) WithType

func (e *Error) WithType(typ ErrorType) *Error

WithType returns a copy of the error with the specified ErrorType. This method allows changing the error type while preserving all other attributes.

Example:

err := errorsx.New("generic.error")
typedErr := err.WithType(errorsx.TypeValidation)

type ErrorType

type ErrorType string

ErrorType represents a string-based error category for classification and filtering. Error types enable systematic error handling by allowing code to identify and respond to different categories of errors consistently.

Example usage:

// Define custom error types
const TypeAuthentication ErrorType = \"myapp.authentication\"

err := errorsx.New("auth.failed", errorsx.WithType(TypeAuthentication))
if errorsx.HasType(err, TypeAuthentication) {
	// Handle authentication errors
}
const (
	// TypeInitialization represents errors that occur during system or component initialization.
	TypeInitialization ErrorType = "errorsx.initialization"

	// TypeUnknown is the default error type when no specific type is assigned.
	TypeUnknown ErrorType = "errorsx.unknown"

	// TypeValidation represents errors related to input validation and data constraints.
	TypeValidation ErrorType = "errorsx.validation"
)

Predefined error types for common error categories. Applications can define additional custom types as needed.

func Type

func Type(err error) ErrorType

Type extracts the ErrorType from a generic error. If the error is not an errorsx.Error, returns TypeUnknown.

This function enables type checking for any error, including wrapped errors and errors from external libraries.

type FieldError

type FieldError struct {
	Field   string `json:"field"`   // Form field name
	Code    string `json:"code"`    // Error code (e.g., "required", "invalid_format")
	Message any    `json:"message"` // Message data (can be string, object, or any type)
}

FieldError represents individual error information that occurred for a specific field.

type FieldTranslator

type FieldTranslator func(field, code string, message any) string

FieldTranslator is a function type that translates individual field error messages. It receives field name, error code, and message data to generate a localized message.

type Option

type Option func(*Error)

Option represents a function that configures an Error during creation. Options follow the functional options pattern, allowing flexible and extensible error configuration.

func WithHTTPStatus

func WithHTTPStatus(status int) Option

WithHTTPStatus sets the HTTP status code for web API responses. This allows automatic HTTP status code mapping in web handlers.

Example:

err := errorsx.New("user.not_found",
	errorsx.WithHTTPStatus(404),
)

func WithMessage

func WithMessage(data any) Option

WithMessage sets the message data for user-facing display. The data can be any type - string for simple messages, or structured data (maps, structs) for internationalization.

Example with simple message:

err := errorsx.New("user.not_found",
	errorsx.WithMessage("User not found"),
)

Example with i18n data:

err := errorsx.New("user.not_found",
	errorsx.WithMessage(map[string]string{
		"en": "User not found",
		"ja": "ユーザーが見つかりません",
	}),
)

func WithNotFound

func WithNotFound() Option

WithNotFound marks the error as a "not found" error. This is a convenience method that's equivalent to WithType(TypeNotFound) but can be used in combination with other types for more specific classification.

Example:

err := errorsx.New("user.not_found",
	errorsx.WithNotFound(),
	errorsx.WithHTTPStatus(404),
)

func WithRetryable added in v0.1.2

func WithRetryable() Option

WithRetryable marks the error as retryable. This indicates that the operation that caused the error can be safely retried, such as temporary network failures or transient service unavailability.

Example:

err := errorsx.New("service.unavailable",
	errorsx.WithRetryable(),
	errorsx.WithHTTPStatus(503),
)

func WithType

func WithType(errType ErrorType) Option

WithType sets the error type for classification and filtering. Error types help categorize errors for different handling strategies.

Example:

err := errorsx.New("input.invalid",
	errorsx.WithType(errorsx.TypeValidation),
)

type StackTrace

type StackTrace struct {
	// Frames contains the raw program counter values for each stack frame.
	Frames []uintptr

	// Msg is a descriptive message about when this stack trace was captured.
	Msg string
}

StackTrace represents a captured call stack with an associated message. It stores the raw program counter values and a descriptive message about when/why the stack trace was captured.

The stack trace can be formatted and displayed using various methods, and can be customized with StackTraceCleaner functions.

type StackTraceCleaner

type StackTraceCleaner func(frames []string) []string

StackTraceCleaner is a function type for customizing stack trace output. It receives a slice of formatted stack frame strings and returns a modified slice, allowing for filtering, formatting, or annotating stack traces.

Example use cases:

  • Filtering out internal framework code
  • Highlighting application-specific code
  • Adding source code context
  • Anonymizing sensitive paths

Example implementation:

cleaner := func(frames []string) []string {
	var cleaned []string
	for _, frame := range frames {
		if !strings.Contains(frame, "internal/") {
			cleaned = append(cleaned, frame)
		}
	}
	return cleaned
}

type SummaryTranslator

type SummaryTranslator func(fieldErrors []FieldError, messageData any) string

SummaryTranslator is a function type that translates a summary message. It receives field errors and base message data to generate a localized summary.

type ValidationError

type ValidationError struct {
	BaseError   *Error       `json:"-"`            // Existing errorsx.Error (ID, Type, HTTPStatus, etc.)
	FieldErrors []FieldError `json:"field_errors"` // Error list for each field name
	// contains filtered or unexported fields
}

ValidationError is an error type that holds multiple field errors together. By having an existing *Error internally, it can be easily combined with business layer errors. Implements Error() to satisfy the error interface, and additionally implements MarshalJSON() to return field errors for JSON output.

func NewValidationError

func NewValidationError(id string) *ValidationError

NewValidationError creates a new instance as a form input validation error.

id: ID that uniquely identifies the validation error (e.g., "validation.failed")
Example

ExampleNewValidationError demonstrates validation error handling.

verr := errorsx.NewValidationError("user.validation_failed")
verr.WithHTTPStatus(422)
verr.WithMessage("Please fix the following errors")

// Add individual field errors
verr.AddFieldError("email", errorCodeRequired, "Email is required")
verr.AddFieldError("age", "min_value", map[string]int{
	"min":     18,
	"current": 16,
})
verr.AddFieldError("username", "taken", "Username is already taken")

// Demonstrate error message
fmt.Println("Error message:", verr.Error())

// Demonstrate JSON serialization
jsonData, err := json.MarshalIndent(verr, "", "  ")
if err != nil {
	fmt.Printf("JSON marshaling error: %v\n", err)
	return
}
fmt.Println("JSON representation:")
fmt.Println(string(jsonData))
Output:

Error message: user.validation_failed: email: Email is required; age: map[current:16 min:18]; username: Username is already taken
JSON representation:
{
  "id": "user.validation_failed",
  "type": "errorsx.validation",
  "message_data": "Please fix the following errors",
  "message": "Please fix the following errors",
  "field_errors": [
    {
      "field": "email",
      "code": "required",
      "message": "Email is required",
      "translated_message": "Email is required"
    },
    {
      "field": "age",
      "code": "min_value",
      "message": {
        "current": 16,
        "min": 18
      },
      "translated_message": "map[current:16 min:18]"
    },
    {
      "field": "username",
      "code": "taken",
      "message": "Username is already taken",
      "translated_message": "Username is already taken"
    }
  ]
}

func (*ValidationError) AddFieldError

func (v *ValidationError) AddFieldError(field, code string, message any)

AddFieldError adds validation error information for a specific field. This method accumulates field errors that will be included in the final validation error response.

Parameters:

  • field: The name of the form field (e.g., "email", "password")
  • code: Machine-readable error code (e.g., "required", "min_length", "invalid_format")
  • message: Human-readable message or structured data for the error

Examples:

// Simple string message
verr.AddFieldError("email", "required", "Email address is required")

// Structured message data for complex validation rules
verr.AddFieldError("password", "min_length", map[string]int{
	"min": 8,
	"current": 3,
})

// Internationalization data
verr.AddFieldError("username", "taken", map[string]string{
	"en": "Username is already taken",
	"ja": "ユーザー名は既に使用されています",
})

func (*ValidationError) Error

func (v *ValidationError) Error() string

Error implements the standard error interface. It returns a human-readable string that includes the base error message and details about each field error. The format is suitable for logging and debugging purposes.

Example output: "validation failed: email: required; password: too short".

func (*ValidationError) HTTPStatus

func (v *ValidationError) HTTPStatus() int

HTTPStatus returns the HTTP status code associated with this validation error. This is typically used by web frameworks to set appropriate HTTP response codes. Defaults to 0 if no status was explicitly set.

Common validation error status codes:

  • 400 Bad Request: General validation failures
  • 422 Unprocessable Entity: Semantic validation errors

func (*ValidationError) MarshalJSON

func (v *ValidationError) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler to provide custom JSON serialization. This method creates a structured JSON representation suitable for API responses, including both the raw message data and translated messages for each field error.

The JSON structure includes:

  • id: The unique error identifier
  • type: The error type (typically "validation")
  • message_data: The raw message data from WithMessage()
  • message: The translated summary message
  • field_errors: Array of field-specific errors with translations

Example JSON output:

{
  "id": "user.validation_failed",
  "type": "validation",
  "message_data": "Please fix the following errors",
  "message": "Validation failed with 2 error(s)",
  "field_errors": [
    {
      "field": "email",
      "code": "required",
      "message": "Email is required",
      "translated_message": "Email is required"
    },
    {
      "field": "password",
      "code": "min_length",
      "message": {"min": 8, "current": 3},
      "translated_message": "Password must be at least 8 characters"
    }
  ]
}

This format enables both programmatic error handling (using codes and structured data) and user-friendly error display (using translated messages).

func (*ValidationError) Unwrap

func (v *ValidationError) Unwrap() error

Unwrap returns the underlying base error, enabling Go's error unwrapping functionality. This allows ValidationError to participate in error chains and be compatible with errors.Is() and errors.As().

Example:

if errors.Is(validationErr, someSpecificError) {
	// Handle specific error type
}

func (*ValidationError) WithFieldTranslator

func (v *ValidationError) WithFieldTranslator(t FieldTranslator) *ValidationError

WithFieldTranslator sets a custom translator for individual field error messages. This enables localization and custom formatting of field-specific error messages.

Example:

customFieldTranslator := func(field, code string, message any) string {
	switch code {
	case "required":
		return fmt.Sprintf("The %s field is required", field)
	default:
		return fmt.Sprintf("%v", message)
	}
}
verr.WithFieldTranslator(customFieldTranslator)
Example

ExampleValidationError_WithFieldTranslator demonstrates custom field error formatting.

// Custom field translator for better error messages
fieldTranslator := func(field, code string, message any) string {
	switch code {
	case errorCodeRequired:
		return fmt.Sprintf("The %s field is required", field)
	case "min_value":
		if data, ok := message.(map[string]int); ok {
			return fmt.Sprintf("The %s must be at least %d (current: %d)",
				field, data["min"], data["current"])
		}
	case "taken":
		return fmt.Sprintf("The %s '%v' is already taken", field, message)
	}
	return fmt.Sprintf("%v", message)
}

verr := errorsx.NewValidationError("user.validation_failed").
	WithFieldTranslator(fieldTranslator)

verr.AddFieldError("email", errorCodeRequired, nil)
verr.AddFieldError("age", "min_value", map[string]int{"min": 18, "current": 16})
verr.AddFieldError("username", "taken", "john_doe")

fmt.Println(verr.Error())
Output:

user.validation_failed: email: The email field is required; age: The age must be at least 18 (current: 16); username: The username 'john_doe' is already taken

func (*ValidationError) WithHTTPStatus

func (v *ValidationError) WithHTTPStatus(status int) *ValidationError

WithHTTPStatus sets the HTTP status code for the validation error.

func (*ValidationError) WithMessage

func (v *ValidationError) WithMessage(data any) *ValidationError

WithMessage sets the message data for the base error.

func (*ValidationError) WithSummaryTranslator

func (v *ValidationError) WithSummaryTranslator(t SummaryTranslator) *ValidationError

WithSummaryTranslator sets a custom translator for generating the overall validation error summary message. This is useful for internationalization or custom error message formatting.

Example:

customSummary := func(fieldErrors []FieldError, messageData any) string {
	return fmt.Sprintf("Validation failed for %d fields", len(fieldErrors))
}
verr.WithSummaryTranslator(customSummary)

Jump to

Keyboard shortcuts

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