errors

package
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2025 License: MIT Imports: 5 Imported by: 0

README

Forge Error Package

A production-ready error handling package with support for the Go standard library's errors.Is and errors.As patterns.

Features

Error Code Constants - Type-safe error codes
errors.Is Support - Pattern matching for error types
errors.As Support - Type extraction from error chains
Structured Errors - Rich context with timestamps and metadata
Error Wrapping - Full chain traversal support
HTTP Integration - Automatic status code mapping
Sentinel Errors - Predefined error instances for comparison

Error Code Constants

All error codes are defined as constants for type safety and consistency:

const (
    CodeConfigError           = "CONFIG_ERROR"
    CodeValidationError       = "VALIDATION_ERROR"
    CodeLifecycleError        = "LIFECYCLE_ERROR"
    CodeContextCancelled      = "CONTEXT_CANCELLED"
    CodeServiceNotFound       = "SERVICE_NOT_FOUND"
    CodeServiceAlreadyExists  = "SERVICE_ALREADY_EXISTS"
    CodeCircularDependency    = "CIRCULAR_DEPENDENCY"
    CodeInvalidConfig         = "INVALID_CONFIG"
    CodeTimeoutError          = "TIMEOUT_ERROR"
)

Quick Start

Creating Errors
// Using constructor functions (recommended)
err := errors.ErrServiceNotFound("database")
err := errors.ErrValidationError("email", fmt.Errorf("invalid format"))
err := errors.ErrTimeoutError("query", 5*time.Second)

// With additional context
err := errors.ErrServiceNotFound("cache").
    WithContext("host", "localhost").
    WithContext("port", 6379)
Checking Error Types
// Using sentinel errors (most concise)
if errors.Is(err, errors.ErrServiceNotFoundSentinel) {
    // Handle service not found
}

// Using helper functions (clearest intent)
if errors.IsServiceNotFound(err) {
    // Handle service not found
}

// Using error codes (most flexible)
var forgeErr *errors.ForgeError
if errors.As(err, &forgeErr) {
    switch forgeErr.Code {
    case errors.CodeServiceNotFound:
        // Handle service not found
    case errors.CodeTimeout:
        // Handle timeout
    }
}
Working with Error Chains
// Create a wrapped error
baseErr := errors.ErrServiceNotFound("auth")
wrappedErr := errors.ErrConfigError("initialization failed", baseErr)

// Check if any error in the chain matches
if errors.Is(wrappedErr, errors.ErrServiceNotFoundSentinel) {
    // This will match even though it's wrapped
}

// Extract specific error type from chain
var serviceErr *errors.ServiceError
if errors.As(wrappedErr, &serviceErr) {
    log.Printf("Service: %s, Operation: %s", serviceErr.Service, serviceErr.Operation)
}
HTTP Error Handling
err := errors.BadRequest("invalid input")

// Extract HTTP status code
statusCode := errors.GetHTTPStatusCode(err) // 400

// Works with wrapped errors too
wrappedErr := errors.ErrConfigError("failed", errors.Unauthorized("token expired"))
statusCode = errors.GetHTTPStatusCode(wrappedErr) // 401

Error Types

ForgeError

Structured error with error code, message, cause, timestamp, and context.

type ForgeError struct {
    Code      string                 // Error code constant
    Message   string                 // Human-readable message
    Cause     error                  // Underlying error (can be nil)
    Timestamp time.Time              // When the error occurred
    Context   map[string]interface{} // Additional context
}
ServiceError

Error specific to service operations.

type ServiceError struct {
    Service   string // Service name
    Operation string // Operation that failed
    Err       error  // Underlying error
}
HTTPError

HTTP-aware error with status code.

type HTTPError struct {
    Code    int    // HTTP status code
    Message string // Error message
    Err     error  // Underlying error
}

Sentinel Errors

Predefined error instances for use with errors.Is:

  • ErrServiceNotFoundSentinel
  • ErrServiceAlreadyExistsSentinel
  • ErrCircularDependencySentinel
  • ErrInvalidConfigSentinel
  • ErrValidationErrorSentinel
  • ErrLifecycleErrorSentinel
  • ErrContextCancelledSentinel
  • ErrTimeoutErrorSentinel
  • ErrConfigErrorSentinel

Helper Functions

Convenience functions for common error checks:

IsServiceNotFound(err)      bool
IsServiceAlreadyExists(err) bool
IsCircularDependency(err)   bool
IsValidationError(err)      bool
IsContextCancelled(err)     bool
IsTimeout(err)              bool
GetHTTPStatusCode(err)      int

Standard Library Integration

Wrapper functions for Go's errors package:

errors.Is(err, target)      // errors.Is wrapper
errors.As(err, target)      // errors.As wrapper
errors.Unwrap(err)          // errors.Unwrap wrapper
errors.New(text)            // errors.New wrapper
errors.Join(errs...)        // errors.Join wrapper

Best Practices

  1. Use constructor functions - They ensure consistent error codes
  2. Add context - Use WithContext() for debugging information
  3. Check error chains - Always use Is() and As() for type checking
  4. Log at boundaries - Don't log at every layer, only at boundaries
  5. Preserve causes - Always wrap underlying errors, don't discard them

Testing

Comprehensive test coverage including:

  • Error matching with Is()
  • Type extraction with As()
  • Error chain traversal
  • HTTP status code extraction
  • Context preservation

Run tests:

go test -v ./v2/internal/errors/...

Examples

See examples_test.go for detailed usage examples.

Documentation

Overview

Example (ErrorCodes)

Example_errorCodes demonstrates using error code constants

// Create errors using constructors (they use the constants internally)
err := ErrServiceNotFound("database")

// You can access the code constant for custom logic
var forgeErr *ForgeError
if As(err, &forgeErr) {
	switch forgeErr.Code {
	case CodeServiceNotFound:
		fmt.Println("Service not found")
	case CodeServiceAlreadyExists:
		fmt.Println("Service already exists")
	case CodeCircularDependency:
		fmt.Println("Circular dependency")
	}
}

// Using sentinel errors for comparison
if Is(err, ErrServiceNotFoundSentinel) {
	fmt.Println("Matched using sentinel")
}

// Using helper functions
if IsServiceNotFound(err) {
	fmt.Println("Matched using helper")
}
Output:

Service not found
Matched using sentinel
Matched using helper
Example (ErrorConstants)

Example_errorConstants demonstrates the available error code constants

// All available error code constants:
codes := []string{
	CodeConfigError,          // "CONFIG_ERROR"
	CodeValidationError,      // "VALIDATION_ERROR"
	CodeLifecycleError,       // "LIFECYCLE_ERROR"
	CodeContextCancelled,     // "CONTEXT_CANCELLED"
	CodeServiceNotFound,      // "SERVICE_NOT_FOUND"
	CodeServiceAlreadyExists, // "SERVICE_ALREADY_EXISTS"
	CodeCircularDependency,   // "CIRCULAR_DEPENDENCY"
	CodeInvalidConfig,        // "INVALID_CONFIG"
	CodeTimeoutError,         // "TIMEOUT_ERROR"
}

for _, code := range codes {
	fmt.Printf("Code: %s\n", code)
}
Output:

Code: CONFIG_ERROR
Code: VALIDATION_ERROR
Code: LIFECYCLE_ERROR
Code: CONTEXT_CANCELLED
Code: SERVICE_NOT_FOUND
Code: SERVICE_ALREADY_EXISTS
Code: CIRCULAR_DEPENDENCY
Code: INVALID_CONFIG
Code: TIMEOUT_ERROR
Example (ErrorMatching)

Example_errorMatching demonstrates various error matching patterns

// Create a wrapped error chain
baseErr := ErrServiceNotFound("auth")
wrappedErr := ErrConfigError("failed to initialize", baseErr)

// Match by code using Is with sentinel
if Is(wrappedErr, ErrServiceNotFoundSentinel) {
	fmt.Println("Found SERVICE_NOT_FOUND in chain")
}

// Match by code using Is with a new error instance
if Is(wrappedErr, &ForgeError{Code: CodeConfigError}) {
	fmt.Println("Found CONFIG_ERROR in chain")
}

// Extract specific error type using As
var configErr *ForgeError
if As(wrappedErr, &configErr) {
	if configErr.Code == CodeConfigError {
		fmt.Println("Top-level error is CONFIG_ERROR")
	}
}
Output:

Found SERVICE_NOT_FOUND in chain
Found CONFIG_ERROR in chain
Top-level error is CONFIG_ERROR
Example (ForgeErrorWithConstants)

Example_forgeErrorWithConstants shows creating custom ForgeErrors with constants

// When creating custom ForgeErrors, use the constants
customErr := &ForgeError{
	Code:    CodeValidationError,
	Message: "custom validation failed",
}

// Add context
customErr.WithContext("field", "email").WithContext("rule", "format")

// Check error type
if Is(customErr, ErrValidationErrorSentinel) {
	fmt.Println("This is a validation error")
}
Output:

This is a validation error

Index

Examples

Constants

View Source
const (
	CodeConfigError          = "CONFIG_ERROR"
	CodeValidationError      = "VALIDATION_ERROR"
	CodeLifecycleError       = "LIFECYCLE_ERROR"
	CodeContextCancelled     = "CONTEXT_CANCELLED"
	CodeServiceNotFound      = "SERVICE_NOT_FOUND"
	CodeServiceAlreadyExists = "SERVICE_ALREADY_EXISTS"
	CodeCircularDependency   = "CIRCULAR_DEPENDENCY"
	CodeInvalidConfig        = "INVALID_CONFIG"
	CodeTimeoutError         = "TIMEOUT_ERROR"
	CodeHealthCheckFailed    = "HEALTH_CHECK_FAILED"
	CodeServiceStartFailed   = "SERVICE_START_FAILED"
)

Error code constants for structured errors

Variables

View Source
var (
	// ErrServiceNotFound      = errors.New("service not found")
	// ErrServiceAlreadyExists = errors.New("service already registered")
	// ErrCircularDependency   = errors.New("circular dependency detected")
	ErrInvalidFactory   = errors.New("factory must be a function")
	ErrTypeMismatch     = errors.New("service type mismatch")
	ErrLifecycleTimeout = errors.New("lifecycle operation timed out")
	ErrContainerStarted = errors.New("container already started")
	ErrContainerStopped = errors.New("container already stopped")
	ErrScopeEnded       = errors.New("scope already ended")
)

Standard DI/service errors

View Source
var (
	// ErrServiceNotFoundSentinel is a sentinel error for service not found
	ErrServiceNotFoundSentinel = &ForgeError{Code: CodeServiceNotFound}

	// ErrServiceAlreadyExistsSentinel is a sentinel error for service already exists
	ErrServiceAlreadyExistsSentinel = &ForgeError{Code: CodeServiceAlreadyExists}

	// ErrCircularDependencySentinel is a sentinel error for circular dependency
	ErrCircularDependencySentinel = &ForgeError{Code: CodeCircularDependency}

	// ErrInvalidConfigSentinel is a sentinel error for invalid config
	ErrInvalidConfigSentinel = &ForgeError{Code: CodeInvalidConfig}

	// ErrValidationErrorSentinel is a sentinel error for validation errors
	ErrValidationErrorSentinel = &ForgeError{Code: CodeValidationError}

	// ErrLifecycleErrorSentinel is a sentinel error for lifecycle errors
	ErrLifecycleErrorSentinel = &ForgeError{Code: CodeLifecycleError}

	// ErrContextCancelledSentinel is a sentinel error for context cancellation
	ErrContextCancelledSentinel = &ForgeError{Code: CodeContextCancelled}

	// ErrTimeoutErrorSentinel is a sentinel error for timeout errors
	ErrTimeoutErrorSentinel = &ForgeError{Code: CodeTimeoutError}

	// ErrConfigErrorSentinel is a sentinel error for config errors
	ErrConfigErrorSentinel = &ForgeError{Code: CodeConfigError}
)

Sentinel errors that can be used with errors.Is comparisons

Functions

func As

func As(err error, target interface{}) bool

As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true. Otherwise, it returns false. This is a convenience wrapper around errors.As from the standard library.

Example:

var httpErr *HTTPError
if As(err, &httpErr) {
    // handle HTTP error with httpErr.Code
}
Example

Example showing error unwrapping

// Create a wrapped error
innerErr := BadRequest("invalid input")
wrappedErr := ErrConfigError("config failed", innerErr)

// Extract the HTTPError from the chain
var httpErr *HTTPError
if As(wrappedErr, &httpErr) {
	// Use the extracted error
	statusCode := httpErr.Code
	_ = statusCode
}

func GetHTTPStatusCode

func GetHTTPStatusCode(err error) int

GetHTTPStatusCode extracts HTTP status code from error, returns 500 if not found

func Is

func Is(err, target error) bool

Is reports whether any error in err's chain matches target. This is a convenience wrapper around errors.Is from the standard library.

Example:

err := ErrServiceNotFound("auth")
if Is(err, &ForgeError{Code: "SERVICE_NOT_FOUND"}) {
    // handle service not found
}
Example

Example usage demonstrating the new Is functionality

// Create an error
err := ErrServiceNotFound("database")

// Check using Is with sentinel error
if Is(err, ErrServiceNotFoundSentinel) {
	// Handle service not found
}

// Or use the convenience helper
if IsServiceNotFound(err) {
	// Handle service not found
}

func IsCircularDependency

func IsCircularDependency(err error) bool

IsCircularDependency checks if the error is a circular dependency error

func IsContextCancelled

func IsContextCancelled(err error) bool

IsContextCancelled checks if the error is a context cancelled error

func IsServiceAlreadyExists

func IsServiceAlreadyExists(err error) bool

IsServiceAlreadyExists checks if the error is a service already exists error

func IsServiceNotFound

func IsServiceNotFound(err error) bool

IsServiceNotFound checks if the error is a service not found error

func IsTimeout

func IsTimeout(err error) bool

IsTimeout checks if the error is a timeout error

func IsValidationError

func IsValidationError(err error) bool

IsValidationError checks if the error is a validation error

func Join

func Join(errs ...error) error

Join returns an error that wraps the given errors. Any nil error values are discarded. This is a convenience wrapper around errors.Join from the standard library. Requires Go 1.20+

func New

func New(text string) error

New returns an error that formats as the given text. This is a convenience wrapper around errors.New from the standard library.

func Unwrap

func Unwrap(err error) error

Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil. This is a convenience wrapper around errors.Unwrap from the standard library.

Types

type ForgeError

type ForgeError struct {
	Code      string
	Message   string
	Cause     error
	Timestamp time.Time
	Context   map[string]interface{}
}

ForgeError represents a structured error with context

func ErrCircularDependency

func ErrCircularDependency(services []string) *ForgeError

func ErrConfigError

func ErrConfigError(message string, cause error) *ForgeError

ErrConfigError creates a config error

func ErrContainerError

func ErrContainerError(operation string, cause error) *ForgeError

func ErrContextCancelled

func ErrContextCancelled(operation string) *ForgeError

func ErrDependencyNotFound

func ErrDependencyNotFound(deps ...string) *ForgeError

func ErrHealthCheckFailed

func ErrHealthCheckFailed(serviceName string, cause error) *ForgeError

func ErrInvalidConfig

func ErrInvalidConfig(configKey string, cause error) *ForgeError

func ErrLifecycleError

func ErrLifecycleError(phase string, cause error) *ForgeError

ErrLifecycleError creates a lifecycle error

func ErrServiceAlreadyExists

func ErrServiceAlreadyExists(serviceName string) *ForgeError

func ErrServiceNotFound

func ErrServiceNotFound(serviceName string) *ForgeError

func ErrServiceStartFailed

func ErrServiceStartFailed(serviceName string, cause error) *ForgeError

func ErrTimeoutError

func ErrTimeoutError(operation string, timeout time.Duration) *ForgeError

func ErrValidationError

func ErrValidationError(field string, cause error) *ForgeError

ErrValidationError creates a validation error

func (*ForgeError) Error

func (e *ForgeError) Error() string

func (*ForgeError) Is

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

Is implements errors.Is interface for ForgeError Compares by error code, allowing matching against sentinel errors

func (*ForgeError) Unwrap

func (e *ForgeError) Unwrap() error

func (*ForgeError) WithContext

func (e *ForgeError) WithContext(key string, value interface{}) *ForgeError

WithContext adds context to the error

type HTTPError

type HTTPError struct {
	Code    int
	Message string
	Err     error
}

HTTPError represents an HTTP error with status code

func BadRequest

func BadRequest(message string) *HTTPError

func Forbidden

func Forbidden(message string) *HTTPError

func InternalError

func InternalError(err error) *HTTPError

func NewHTTPError

func NewHTTPError(code int, message string) *HTTPError

HTTP error constructors

func NotFound

func NotFound(message string) *HTTPError

func Unauthorized

func Unauthorized(message string) *HTTPError

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Is

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

Is implements errors.Is interface for HTTPError Compares by HTTP status code

func (*HTTPError) Unwrap

func (e *HTTPError) Unwrap() error

type ServiceError

type ServiceError struct {
	Service   string
	Operation string
	Err       error
}

ServiceError wraps service-specific errors

func NewServiceError

func NewServiceError(service, operation string, err error) *ServiceError

NewServiceError creates a new service error

func (*ServiceError) Error

func (e *ServiceError) Error() string

func (*ServiceError) Is

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

Is implements errors.Is interface for ServiceError

func (*ServiceError) Unwrap

func (e *ServiceError) Unwrap() error

type Severity

type Severity string

Severity represents the severity of a validation issue

const (
	SeverityError   Severity = "error"
	SeverityWarning Severity = "warning"
	SeverityInfo    Severity = "info"
)

type ValidationError

type ValidationError struct {
	Key        string      `json:"key"`
	Value      interface{} `json:"value,omitempty"`
	Rule       string      `json:"rule"`
	Message    string      `json:"message"`
	Severity   Severity    `json:"severity"`
	Suggestion string      `json:"suggestion,omitempty"`
}

ValidationError represents a validation error

Jump to

Keyboard shortcuts

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