errors

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2025 License: Apache-2.0 Imports: 9 Imported by: 0

README

go-errors

A Go library for enhanced error handling with stack traces, structured error information, and error wrapping capabilities.

Features

  • Stack Traces: Automatic capture of function location, file, line number, and timestamp
  • Error Wrapping: Chain errors with causes for better error context
  • Structured Errors: Add identifiers, details, and arbitrary properties to errors
  • Method Chaining: Fluent API for building detailed error information
  • Standard Library Compatible: Implements standard error interface and works with errors.Is(), errors.As(), and errors.Unwrap()
  • JSON Serialization: Built-in JSON marshaling for logging and debugging

Installation

go get github.com/scality/go-errors

Quick Start

package main

import (
    "fmt"
    "github.com/scality/go-errors"
)

func main() {
    var ErrDB = errors.New("database error")
    err := errors.From(ErrDB).
        WithIdentifier(1001).
        WithDetail("connection timeout").
        WithProperty("host", "localhost").
        Throw()
    
    fmt.Println(err)
}

Usage Examples

Adding Details and Properties
// Initializing Domain errors
var (
    ErrValidationFailed = errors.New("validation failed")
    ErrRequestFailed = errors.New("request failed")
    ErrDatabaseError = errors.New("database error")
)

// Multiple details
// Each WithDetail() call appends to the Details slice
err1 := errors.From(ErrValidationFailed).
    WithDetail("email is required").
    WithDetail("password must be at least 8 characters").
    Throw()

// Details can also be formatted
err2 := errors.From(ErrRequestFailed).
    WithDetailf("failed to connect to %s:%d", "api.example.com", 443).
    WithDetail("timeout after 30 seconds").
    Throw()

// Single property
err3 := errors.From(ErrRequestFailed).
    WithProperty("url", "https://api.example.com").
    WithProperty("status_code", 500).
    Throw()

// Multiple properties at once
err4 := errors.From(ErrDatabaseError).
    WithProperties(map[string]any{
        "host":     "localhost",
        "port":     5432,
        "database": "myapp",
    }).
    Throw()
Error Wrapping
var ErrUserNotFound = errors.New("user not found")

func getUserByID(id string) (*User, error) {
    user, err := db.Query(id)
    if err != nil {
        return nil, errors.From(ErrUserNotFound).
            WithIdentifier(404000).
            CausedBy(err).
            Throw()
    }
    return user, nil
}

// Convenient wrapping with Wrap() - adds message and stack trace
func getUser(id int) (*User, error) {
    user, err := db.Query(id)
    if err != nil {
        return nil, errors.Wrap(err, "failed to fetch user from database")
    }
    return user, nil
}

// Convenient wrapping with Wrapf() - adds formatted message and stack trace
func getUser(id int) (*User, error) {
    user, err := db.Query(id)
    if err != nil {
        return nil, errors.Wrapf(err, "failed to fetch user with id %d", id)
    }
    return user, nil
}
Stack Traces
var ErrSomethingWentWrong = errors.New("something went wrong")

func layer3() error {
	return errors.From(ErrSomethingWentWrong).Throw()
}

func layer2() error {
	err := layer3()
	if err != nil {
		return errors.Stamp(err) // Adds layer2's location to stack
	}
	return nil
}

func layer1() error {
    err := layer2()
    if err != nil {
        return errors.Stamp(err) // Adds layer1's location to stack
    }
    return nil
}

// The error will contain a complete stack trace through all layers
Working with Standard Library
// Using errors.Is for comparison
notFoundErr := errors.New("not found")

if errors.Is(err, notFoundErr) {
    // Handle not found error
}

// Using errors.As for type assertion
var e *errors.Error
if errors.As(err, &e) {
    fmt.Printf("Error ID: %d\n", e.Identifier)
    fmt.Printf("Details: %v\n", e.Details) // []string
    fmt.Printf("Properties: %v\n", e.Properties)
    
    // Access individual details
    for i, detail := range e.Details {
        fmt.Printf("  Detail %d: %s\n", i, detail)
    }
}
Converting Standard Errors
// Using From() to convert any error
stdErr := fmt.Errorf("something went wrong")
err := errors.From(stdErr).
    WithDetail("additional context").
    Throw()

// Using Intercept() to complete the error
func handleError(err error) error {
    e := errors.Intercept(err)
    e.WithProperty("handled_at", time.Now())
    return e.Throw()
}

Output Format

The Error() method produces output in the following format:

title (id): detail1: detail2: detail3: key1='value1', key2='value2', at=(func='funcName', file='file.go', line='10'), caused by: underlying error

Note: Details are stored as a slice and joined with : when the error is formatted. Each call to WithDetail() or WithDetailf() appends to this slice.

Example with multiple details:

database error (1001): connection timeout: retry limit exceeded: host='localhost', port='5432', at=(func='connectDB', file='db.go', line='42'), caused by: dial tcp: connection refused

Example with wrapped error:

unknown error (0): failed to fetch user from database: at=(func='getUser', file='user.go', line='25'), caused by: connection refused

Best Practices

  1. Always use Throw() when returning errors to capture stack traces
  2. Use Stamp() when passing errors up the call stack to track the error path
  3. Use Wrap() or Wrapf() for convenient error wrapping with automatic stack traces
  4. Use identifiers for errors that need programmatic handling (e.g., HTTP status codes)
  5. Add multiple details using WithDetail() or WithDetailf() - they're stored as a slice and displayed in order
  6. Add properties for debugging context (IDs, URLs, parameters, etc.)
  7. Wrap errors with CausedBy() to maintain error chains for programmatic inspection
  8. Use From() when you need to enhance third-party errors with additional context
  9. Use Intercept() when you need to add context to errors from existing errors
Details vs Properties
  • Details (slice of strings): Human-readable context that appears in error messages, ordered and concatenated with :
  • Properties (key-value map): Structured data for debugging/logging, useful for searching and filtering logs

License

See LICENSE file for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func As

func As(err error, target any) bool

As is a wrapper around errors.As to check if the error is of a specific type.

func Is

func Is(err, target error) bool

Is is a wrapper around errors.Is to compare two errors for equality.

func New

func New(title string) error

New creates a new Error with the given title.

func Stamp

func Stamp(err error) error

Stamp adds a stack trace entry to an existing error.

func Wrap

func Wrap(err error, msg string) error

Wrap wraps an error with a message.

func Wrapf

func Wrapf(err error, format string, args ...any) error

Wrapf wraps an error with a formatted message.

Types

type Error

type Error struct {
	// The error title
	Title string `json:"title" yaml:"title"`

	// A numeric error code for programmatic handling
	Identifier int32 `json:"identifier,omitempty" yaml:"identifier,omitempty"`

	// Additional context as a list of strings
	Details []string `json:"details,omitempty" yaml:"details,omitempty"`

	// Key-value pairs for arbitrary metadata
	Properties map[string]any `json:"properties,omitempty" yaml:"properties,omitempty"`

	// The underlying error that caused this error
	Cause error `json:"cause,omitempty" yaml:"cause,omitempty"`

	// A trace of where the error was thrown
	Stack []*Trace `json:"stack,omitempty" yaml:"stack,omitempty"`
}

Error is an enhanced error implementation that supports structured error information, error wrapping, stack traces, and additional metadata.

Error implements the standard error interface and can be used anywhere a standard Go error is expected.

func From

func From(err error) *Error

From creates a new *Error from any error type. If the error is not an *Error, it creates a new error with title "unknown error" and sets the original error as the cause. If the error is an *Error, it returns a copy of the original error with the same title, identifier, details, properties.

func Intercept

func Intercept(err error) *Error

Intercept converts any error into an *Error type. If the provided error is already an *Error, it returns it as-is. Otherwise, it creates a new *Error wrapping the original error using From().

func (*Error) CausedBy

func (e *Error) CausedBy(err error) *Error

CausedBy sets the underlying cause of this error.

func (*Error) Error

func (e *Error) Error() string

Error returns a formatted string representation of the error, including title, identifier, details, properties, location and cause.

func (*Error) Is

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

Is compares this error with another error for equality. Two errors are considered equal if they have the same Title and Identifier.

func (*Error) String

func (e *Error) String() string

String returns a JSON representation of the error.

func (*Error) Throw

func (e *Error) Throw() error

Throw adds a stack trace entry to the error and returns it as an error interface.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause of this error, nil if no cause.

func (*Error) WithDetail

func (e *Error) WithDetail(detail string) *Error

WithDetail adds a detail string to the error for additional context.

func (*Error) WithDetailf

func (e *Error) WithDetailf(format string, args ...any) *Error

WithDetailf adds a detail string to the error for additional context using a format string.

func (*Error) WithIdentifier

func (e *Error) WithIdentifier(id int32) *Error

WithIdentifier sets a numeric identifier for the error.

func (*Error) WithProperties

func (e *Error) WithProperties(properties map[string]any) *Error

WithProperties adds multiple key-value properties to the error.

func (*Error) WithProperty

func (e *Error) WithProperty(key string, value any) *Error

WithProperty adds a single key-value property to the error.

type Trace

type Trace struct {
	// The function where the error occurred
	Function string `json:"function,omitempty" yaml:"function,omitempty"`

	// The file where the error occurred
	File string `json:"file,omitempty" yaml:"file,omitempty"`

	// The line where the error occurred
	Line int `json:"line,omitempty" yaml:"line,omitempty"`

	// The timestamp when trace was generated
	Timestamp time.Time `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
}

Trace represents a single entry in an error's stack trace. It captures the location and time when an error was thrown or stamped.

Jump to

Keyboard shortcuts

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