errx

package module
v0.0.0-...-bb3318a Latest Latest
Warning

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

Go to latest
Published: Feb 7, 2026 License: MIT Imports: 7 Imported by: 0

README

errx

Go Reference Go Report Card CI codecov

Structured error handling for Go with standardized error codes, three-tier messaging, and seamless slog integration.

Features

  • Standardized Error Codes — 16 codes aligned with gRPC/ConnectRPC for consistent error classification
  • Three-Tier Messaging — Separate client-safe messages from internal debug details
  • Builder API — Fluent, chainable methods for error construction
  • Automatic Stack Traces — Captures call stack at error creation for debugging
  • Context Metadata — Attach request-scoped metadata via context.Context
  • Structured Logging — Implements slog.LogValuer for rich JSON logs with nested cause chains
  • Go Ecosystem Integration — Full support for errors.Is, errors.As, and errors.Unwrap
  • Zero Dependencies — Only the standard library (plus testify for tests)

Installation

go get github.com/bjaus/errx

Requires Go 1.25 or later.

Quick Start

package main

import (
    "fmt"
    "github.com/bjaus/errx"
)

func main() {
    // Create a simple error
    err := errx.NewNotFound("user not found")
    fmt.Println(err.Code())  // not_found
    fmt.Println(err.Error()) // user not found

    // Create a rich error with context
    err = errx.New(errx.CodePermissionDenied, "access denied").
        WithSource("auth-service").
        WithDetail("resource", "admin-panel").
        WithMeta("user_id", 123).
        WithDebug("user lacks admin role")

    // Client sees only safe data
    fmt.Println(err.Error())   // access denied
    fmt.Println(err.Details()) // map[resource:admin-panel]

    // Logs contain full debug context
    fmt.Println(err.DebugMessage())
    // [permission_denied] access denied | source=auth-service | details=map[resource:admin-panel] | metadata=map[user_id:123] | debug=user lacks admin role
}

Error Codes

Code Description
CodeUnknown Unknown error (default/zero value)
CodeCanceled Operation canceled by caller
CodeInvalidArgument Request is invalid regardless of system state
CodeDeadlineExceeded Deadline expired before operation completed
CodeNotFound Requested resource cannot be found
CodeAlreadyExists Resource already exists
CodePermissionDenied Caller isn't authorized
CodeResourceExhausted Resource exhausted (quota, storage, etc.)
CodeFailedPrecondition System isn't in required state
CodeAborted Operation aborted (concurrency issue)
CodeOutOfRange Operation attempted past valid range
CodeUnimplemented Operation not implemented/supported
CodeInternal Internal error (invariant broken)
CodeUnavailable Service temporarily unavailable
CodeDataLoss Unrecoverable data loss or corruption
CodeUnauthenticated Valid authentication credentials required

Usage

Creating Errors
// Simple error with code
err := errx.New(errx.CodeNotFound, "user not found")

// Formatted message
err := errx.Newf(errx.CodeInvalidArgument, "invalid ID: %d", id)

// Convenience constructors for each code
err := errx.NewNotFound("user not found")
err := errx.NewfInternal("db error: %v", dbErr)
Wrapping Errors
// Wrap an existing error
dbErr := sql.ErrNoRows
err := errx.Wrap(dbErr, errx.CodeNotFound, "user not found")

// Convenience wrappers
err := errx.WrapInternal(dbErr, "query failed")
err := errx.WrapfUnavailable(dbErr, "service %s down", svc)
Adding Context
err := errx.New(errx.CodeInternal, "operation failed").
    WithSource("payment-service").           // Service/component origin
    WithTags("database", "critical").        // Categorization tags
    WithDetail("order_id", "ord-123").       // Client-safe details
    WithMeta("query", "SELECT ...").         // Internal debug metadata
    WithDebug("connection pool exhausted").  // Internal debug message
    WithRetryable()                          // Mark as retryable
Context-Based Metadata

Attach request-scoped metadata that automatically flows to errors:

// In middleware
ctx = errx.WithMetaContext(ctx, "request_id", reqID, "user_id", userID)

// Later in your code
err := errx.NewInternal("operation failed").WithMetaFromContext(ctx)
// err.Metadata() contains request_id and user_id
Checking Errors
// Check if error is an errx.Error
if errx.Is(err) {
    // ...
}

// Extract errx.Error from chain
if e, ok := errx.As(err); ok {
    code := e.Code()
    source := e.Source()
}

// Check specific codes
if errx.CodeIs(err, errx.CodeNotFound) {
    // handle not found
}

// Check multiple codes
if errx.CodeIn(err, errx.CodeNotFound, errx.CodePermissionDenied) {
    // handle client error
}

// Extract code (returns CodeUnknown for non-errx errors)
code := errx.CodeOf(err)

// Check if retryable
if errx.IsRetryable(err) {
    // retry the operation
}
Ensure Functions

Guarantee an error is an *errx.Error without clobbering existing codes:

// At service boundaries - preserves original code if already errx
return errx.Ensure(err, errx.CodeInternal, "unexpected error")

// Convenience variants
return errx.EnsureInternal(err, "unexpected error")
Structured Logging with slog

errx implements slog.LogValuer for rich structured logging:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

innerErr := errx.NewInternal("query failed").
    WithSource("repository").
    WithMeta("table", "users")

outerErr := errx.Wrap(innerErr, errx.CodeNotFound, "user not found").
    WithSource("service").
    WithMeta("user_id", 123)

logger.Error("request failed", "error", outerErr)

Output:

{
  "level": "ERROR",
  "msg": "request failed",
  "error": {
    "code": "not_found",
    "message": "user not found",
    "source": "service",
    "metadata": {"user_id": 123},
    "cause": {
      "code": "internal",
      "message": "query failed",
      "source": "repository",
      "metadata": {"table": "users"}
    }
  }
}

Client vs Internal Data

errx separates data into client-safe and internal categories:

Client-Safe Internal-Only
Error() — Message Source() — Origin service/component
Details() — Safe key-value pairs Tags() — Categorization tags
Code() — Error code Metadata() — Debug key-value pairs
DebugMessage() — Full debug output
StackTrace() — Call stack

Layered Architecture Example

// Repository layer
func (r *UserRepo) FindByID(ctx context.Context, id int) (*User, error) {
    user, err := r.db.Get(ctx, id)
    if err != nil {
        return nil, errx.WrapInternal(err, "database query failed").
            WithSource("user-repository").
            WithMeta("user_id", id)
    }
    if user == nil {
        return nil, errx.NewNotFound("user not found").
            WithSource("user-repository").
            WithMeta("user_id", id)
    }
    return user, nil
}

// Service layer
func (s *UserService) GetUser(ctx context.Context, id int) (*User, error) {
    user, err := s.repo.FindByID(ctx, id)
    if err != nil {
        return nil, errx.Wrap(err, errx.CodeOf(err), "failed to get user").
            WithSource("user-service").
            WithMetaFromContext(ctx)
    }
    return user, nil
}

// Handler layer
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
    user, err := h.svc.GetUser(r.Context(), id)
    if err != nil {
        if errx.CodeIs(err, errx.CodeNotFound) {
            http.Error(w, "User not found", http.StatusNotFound)
            return
        }
        slog.Error("request failed", "error", err)
        http.Error(w, "Internal error", http.StatusInternalServerError)
        return
    }
    // ...
}

Testing

The package includes comprehensive tests with 100% coverage of core functionality:

go test -v ./...

License

MIT License - see LICENSE for details.

Documentation

Overview

Package errx provides structured error handling with standardized error codes, rich context for debugging, and safe client messaging.

errx is an error handling package that provides:

  • Standardized Error Codes: Typed error codes for consistent error classification
  • Three-Tier Error Messages:
  • System components: Error codes for inter-service communication
  • System maintainers: Rich debug information with stack traces, metadata, and implementation details
  • Clients: Safe error messages without exposing internal details
  • Transport Agnostic: Works with HTTP, gRPC, or any other transport layer
  • Go Ecosystem Integration: Full support for errors.Is, errors.As, and errors.Unwrap
  • Builder API: Fluent, chainable methods for error construction
  • Automatic Stack Traces: Captures stack traces for debugging
  • Error Wrapping: Chain errors while preserving context

Quick Start

Creating Errors:

// Simple error
err := errx.New(errx.CodeNotFound, "user not found")

// Formatted error
err := errx.Newf(errx.CodeInvalidArgument, "invalid user ID: %d", userID)

// Wrapping existing errors
dbErr := errors.New("connection refused")
err := errx.Wrap(dbErr, errx.CodeUnavailable, "database unavailable")

Adding Context:

err := errx.New(errx.CodePermissionDenied, "access denied").
    WithDetail("resource", "admin-panel").        // Client-safe details
    WithSource("auth-service").                   // Set source (service/package/component)
    WithTags("security", "rbac").                 // Add tags for categorization
    WithMeta("user_id", userID).                  // Internal metadata
    WithDebug("user missing admin role").         // Internal debug message
    WithRetryable()                               // Mark as retryable operation

Using Errors:

// Get error code
errCode := err.Code()                 // Returns errx.Code
codeStr := err.Code().String()        // Returns "permission_denied"

// Get client-safe data
clientMsg := err.Error()              // "access denied"
details := err.Details()              // map[string]any{"resource": "admin-panel"}

// Get internal data
source := err.Source()                // "auth-service"
tags := err.Tags()                    // []string{"security", "rbac"}
metadata := err.Metadata()            // map[string]any{"user_id": 123}

// Get debug message (for logging/debugging)
debugMsg := err.DebugMessage()        // "[permission_denied] access denied | class=access-denied | ..."

// Get stack trace
stackTrace := err.FormatStackTrace()  // Human-readable stack trace

Error Codes

The package provides strictly defined standardized error codes. All codes are constants to ensure consistency:

CodeUnknown              // Unknown error (default/zero value)
CodeCanceled             // Operation canceled by caller
CodeInvalidArgument      // Request is invalid, regardless of system state
CodeDeadlineExceeded     // Deadline expired before operation completed
CodeNotFound             // Requested resource cannot be found
CodeAlreadyExists        // Resource already exists
CodePermissionDenied     // Caller isn't authorized
CodeResourceExhausted    // Resource exhausted (quota, storage, etc.)
CodeFailedPrecondition   // System isn't in required state
CodeAborted              // Operation aborted (concurrency issue)
CodeOutOfRange           // Operation attempted past valid range
CodeUnimplemented        // Operation not implemented/supported
CodeInternal             // Internal error (invariant broken)
CodeUnavailable          // Service temporarily unavailable
CodeDataLoss             // Unrecoverable data loss or corruption
CodeUnauthenticated      // Valid authentication credentials required

These error codes align with the Connect RPC protocol specification.

Context-Based Metadata

Use WithMetaContext to store request-scoped metadata in a context, then attach it to errors with WithMetaFromContext:

ctx = errx.WithMetaContext(ctx, "user_id", 123, "action", "delete")

// Later, when creating an error:
err := errx.New(errx.CodeNotFound, "user not found").WithMetaFromContext(ctx)
// err.Metadata() == map[string]any{"user_id": 123, "action": "delete"}

Metadata accumulates across calls:

ctx = errx.WithMetaContext(ctx, "user_id", 123)
ctx = errx.WithMetaContext(ctx, "action", "delete")
// Both "user_id" and "action" are present

WithMetaFromContext uses last-write-wins: if the same key was set via WithMeta, the context value takes precedence. Reverse the call order to give WithMeta priority.

Ensure Functions

Use Ensure and Ensuref to guarantee an error is an *Error without clobbering the original code. If the error is already an *Error (or wraps one), it is returned unchanged. Otherwise, it is wrapped with the given fallback code and message:

// At a service boundary — preserves not_found, only wraps unknown errors as internal
return errx.Ensure(err, errx.CodeInternal, "unexpected error")

// With formatting
return errx.Ensuref(err, errx.CodeInternal, "unexpected error in %s", "user-service")

Convenience variants exist for each code:

return errx.EnsureInternal(err, "unexpected error")
return errx.EnsurefInternal(err, "unexpected error in %s", "user-service")

Convenience Functions

For each error code, the package provides convenience constructors:

errx.New{Code}(msg)                    // e.g., errx.NewNotFound("user not found")
errx.Newf{Code}(format, args...)       // e.g., errx.NewfNotFound("user %d not found", id)
errx.Wrap{Code}(err, msg)              // e.g., errx.WrapInternal(err, "db failed")
errx.Wrapf{Code}(err, format, args...) // e.g., errx.WrapfInternal(err, "db %s failed", name)
errx.Ensure{Code}(err, msg)            // e.g., errx.EnsureInternal(err, "unexpected")
errx.Ensuref{Code}(err, format, args...) // e.g., errx.EnsurefInternal(err, "unexpected in %s", svc)

Usage Patterns

Layered Error Handling:

// Data layer
func (r *UserRepository) FindByID(id int) (*User, error) {
    user, err := r.db.Query("SELECT * FROM users WHERE id = ?", id)
    if err != nil {
        return nil, errx.Wrap(err, errx.CodeInternal, "database query failed").
            WithSource("user-repository").
            WithTags("database").
            WithMeta("user_id", id).
            WithDebugf("query failed: %v", err)
    }

    if user == nil {
        return nil, errx.New(errx.CodeNotFound, "user not found").
            WithSource("user-repository").
            WithMeta("user_id", id)
    }

    return user, nil
}

// Service layer
func (s *UserService) GetUser(id int) (*User, error) {
    user, err := s.repo.FindByID(id)
    if err != nil {
        if e, ok := errx.As(err); ok && e.Code() != errx.CodeNotFound {
            return nil, err
        }
        user, err = s.repo.CreateDefaultUser(id)
        if err != nil {
            return nil, err
        }
    }
    return user, nil
}

Working with Standard Errors:

// Check if error is an errx.Error
if errx.Is(err) {
    // It's an errx error
}

// Extract errx.Error from any error
if e, ok := errx.As(err); ok {
    code := e.Code()
    source := e.Source()
    metadata := e.Metadata()
}

// Check if error has a specific code
if errx.CodeIs(err, errx.CodeNotFound) {
    // Handle not found
}

// Check if error has any of multiple codes
if errx.CodeIn(err, errx.CodeNotFound, errx.CodeUnauthenticated) {
    // Handle client errors
}

// Extract error code
errCode := errx.CodeOf(err) // Returns CodeUnknown for non-errx errors

// Check if error is retryable
if errx.IsRetryable(err) {
    // Retry the operation
}

// Also compatible with standard errors package
err1 := errx.New(errx.CodeNotFound, "not found")
err2 := errx.New(errx.CodeNotFound, "different message")
errors.Is(err1, err2)  // true - same code

var errxErr *errx.Error
if errors.As(err, &errxErr) {
    code := errxErr.Code()
}

Generic Type Checking

For most use cases, use the non-generic Is() and As() functions to work with *errx.Error. For checking other custom error types, use the generic IsType[E] and AsType[E] functions:

// Non-generic versions for errx.Error (recommended for common case)
if errx.Is(err) {
    // err is or wraps an *errx.Error
}

if e, ok := errx.As(err); ok {
    // e is an *errx.Error
    code := e.Code()
}

// Generic versions for custom error types
type MyCustomError struct {
    Reason string
}

func (e *MyCustomError) Error() string {
    return e.Reason
}

// Check if error is or wraps a custom type
if errx.IsType[*MyCustomError](err) {
    // err is or wraps a *MyCustomError
}

// Extract custom error type from error chain
if e, ok := errx.AsType[*MyCustomError](err); ok {
    // e is a *MyCustomError
    fmt.Println(e.Reason)
}

Marking Errors as Retryable

Use WithRetryable() to indicate that an operation can be retried:

// Temporary service unavailability - can be retried
err := errx.New(errx.CodeUnavailable, "service temporarily unavailable").
    WithRetryable().
    WithSource("payment-service")

// Check if an error is retryable
if errx.IsRetryable(err) {
    // Implement retry logic (e.g., return to retry queue, exponential backoff, etc.)
}

// Works with wrapped errors too
wrappedErr := fmt.Errorf("payment failed: %w", err)
errx.IsRetryable(wrappedErr)  // true

Common retryable scenarios:

  • CodeUnavailable: Service temporarily down
  • CodeDeadlineExceeded: Request timeout
  • CodeResourceExhausted: Rate limit exceeded
  • CodeAborted: Optimistic locking conflict

Non-retryable errors typically include:

  • CodeInvalidArgument: Bad request data
  • CodeNotFound: Resource doesn't exist
  • CodePermissionDenied: Authorization failure
  • CodeUnimplemented: Feature not supported

Protecting Client-Facing Messages:

// BAD: Exposing implementation details
err := errx.New(errx.CodeInternal, "failed to connect to postgres.internal.company.com:5432")

// GOOD: Safe client message + debug details
err := errx.New(errx.CodeInternal, "service temporarily unavailable").
    WithDebug("failed to connect to postgres.internal.company.com:5432").
    WithMeta("db_query", "SELECT * FROM users WHERE id = ?")

// Client sees: "service temporarily unavailable" (Error())
// Logs contain: full debug message with all details

Client vs. Internal Data

The error package separates data into two categories:

Client-Exposed Data (Safe to Return to End Users):

  • Error(): Human-readable error message safe for clients (standard error interface)
  • Details(): Key-value pairs safe to expose (e.g., {"resource": "admin-panel"})

Internal-Only Data (For Debugging/Logging):

  • Source(): Source (service/package/component) where error occurred (e.g., "user-service")
  • Tags(): Categorization tags (e.g., ["database", "critical"])
  • Metadata(): Debug key-value pairs (e.g., {"db_host": "postgres.internal", "user_id": 123})
  • DebugMessage(): Full debug message with all context

Best Practices

1. Use Safe Client Messages: Never expose implementation details in the error message

// Bad
err := errx.New(errx.CodeInternal, "SQL: connection to db.internal failed")

// Good
err := errx.New(errx.CodeInternal, "service unavailable").
    WithDebug("SQL: connection to db.internal failed")

2. Add Context at Each Layer: Each layer should add relevant context

// Repository layer
err = errx.Wrap(err, code, msg).WithSource("user-repo").WithTags("database")

// Service layer
err = errx.Wrap(err, code, msg).WithSource("user-service").WithMeta("user_id", id)

3. Use Appropriate Error Codes: Choose codes that accurately represent the error

// Not found vs. permission denied
if user == nil {
    return errx.New(errx.CodeNotFound, "user not found")  // User doesn't exist
}
if !canAccess {
    return errx.New(errx.CodePermissionDenied, "access denied")  // User exists but no permission
}

4. Leverage Metadata for Debugging: Add useful debugging information

err.WithMeta("user_id", id).
    WithMeta("action", "delete").
    WithMeta("retry_count", retries)

5. Don't Create Empty Error Chains: Only wrap when you have context to add

// Bad - adds no value
return errx.Wrap(err, errx.CodeInternal, err.Error())

// Good - adds context
return errx.Wrap(err, errx.CodeInternal, "failed to process payment").
    WithMeta("payment_id", paymentID)

Testing

Example test using errx errors:

func TestUserService_GetUser_NotFound(t *testing.T) {
    // ... setup

    user, err := service.GetUser(999)

    require.Error(t, err)
    assert.Nil(t, user)

    // Check if it's an errx error
    assert.True(t, errx.Is(err))

    // Extract and verify details
    e, ok := errx.As(err)
    require.True(t, ok)
    assert.Equal(t, errx.CodeNotFound, e.Code())
    assert.Equal(t, "user-service", e.Source())

    // Or use code helpers
    assert.True(t, errx.CodeIs(err, errx.CodeNotFound))
    assert.Equal(t, errx.CodeNotFound, errx.CodeOf(err))
    assert.True(t, errx.CodeIn(err, errx.CodeNotFound, errx.CodeUnauthenticated))
}

Educational Resources

Error Handling as Domain Design

Failure is your Domain by Ben Johnson (https://web.archive.org/web/2020/https://middlemost.com/failure-is-your-domain/)

This article advocates treating errors as part of your application's domain rather than as afterthoughts. It introduces a three-consumer model for errors:

1. Application: Needs machine-readable error codes for programmatic handling and inter-service communication

2. End User: Needs human-readable messages that are safe to display (without exposing implementation details)

3. Operator: Needs rich debugging context including logical stack traces, metadata, and internal details

The errx package implements these principles with its standardized error codes (aligned with ConnectRPC), three-tier messaging system (Code/Error()/Details for clients, Source/Tags/Metadata/DebugMessage for operators), and automatic stack trace capture.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsType

func AsType[E error](err error) (E, bool)

AsType finds the first error in err's tree that matches the type E. Returns the error value and true if found, or zero value and false otherwise.

func CodeIn

func CodeIn(err error, codes ...Code) bool

CodeIn checks if an error has a code matching any of the provided codes. It unwraps the error chain to find an *Error.

Example

ExampleCodeIn demonstrates checking if error matches any of several codes.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeNotFound, "resource not found")

	// Check if error matches any of the provided codes
	if errx.CodeIn(err, errx.CodeNotFound, errx.CodeUnauthenticated) {
		fmt.Println("Client error occurred")
	}

}
Output:
Client error occurred

func CodeIs

func CodeIs(err error, code Code) bool

CodeIs checks if an error has a specific error code. It unwraps the error chain to find an *Error.

Example

ExampleCodeIs demonstrates checking error codes.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeNotFound, "resource not found")

	// Check if error has specific code
	if errx.CodeIs(err, errx.CodeNotFound) {
		fmt.Println("Resource not found")
	}

	// Works with wrapped errors too
	wrappedErr := fmt.Errorf("operation failed: %w", err)
	if errx.CodeIs(wrappedErr, errx.CodeNotFound) {
		fmt.Println("Still found through wrapper")
	}

}
Output:
Resource not found
Still found through wrapper

func CodeNames

func CodeNames() []string

CodeNames returns a list of possible string values of Code.

func Is

func Is(err error) bool

Is checks if err is or wraps an *Error.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable checks if an error indicates a retryable operation. Returns false if the error is not an *Error.

func IsType

func IsType[E error](err error) bool

IsType checks if err is or wraps the specified error type E.

func WithMetaContext

func WithMetaContext(ctx context.Context, keyvals ...any) context.Context

WithMetaContext stores key-value metadata in the context for later attachment to errors via Error.WithMetaFromContext. It accepts alternating key-value pairs where each key should be a string. Non-string keys are silently skipped, and a trailing key with no value is silently dropped.

Each call copies the parent context's metadata into a new map, then applies the provided key-value pairs on top (last-write-wins). The parent's map is never mutated, so concurrent goroutines that derive from the same parent context each get an independent snapshot with the correct metadata for their scope.

Concurrency example:

ctx = errx.WithMetaContext(ctx, "request_id", reqID)

for _, postID := range postIDs {
    group.Go(func() error {
        ctx := errx.WithMetaContext(ctx, "post_id", postID)
        // Each goroutine gets its own metadata snapshot.
        // Errors created here carry post_id specific to this goroutine.
        return errx.NewInternal("failed").WithMetaFromContext(ctx)
    })
}

// Whatever error the errgroup returns will have the correct post_id
// for the goroutine that failed — no cross-contamination.

Basic usage:

ctx = errx.WithMetaContext(ctx, "user_id", 123, "action", "delete")
err := errx.New(errx.CodeNotFound, "user not found").WithMetaFromContext(ctx)

Types

type Code

type Code uint8

Code represents a standardized error code. These codes provide a consistent error classification system that is transport-agnostic. Transport layers (HTTP, gRPC, etc.) can map these codes to their specific status codes.

  ENUM(
	  unknown,              // Unknown error (default/zero value)
		canceled,             // Operation canceled by caller
		invalid_argument,     // Request is invalid, regardless of system state
		deadline_exceeded,    // Deadline expired before operation completed
		not_found,            // Requested resource cannot be found
		already_exists,       // Resource already exists
		permission_denied,    // Caller isn't authorized
		resource_exhausted,   // Resource exhausted (quota, storage, etc.)
		failed_precondition,  // System isn't in required state
		aborted,              // Operation aborted (concurrency issue)
		out_of_range,         // Operation attempted past valid range
		unimplemented,        // Operation not implemented/supported
		internal,             // Internal error (invariant broken)
		unavailable,          // Service temporarily unavailable
		data_loss,            // Unrecoverable data loss or corruption
		unauthenticated,      // Valid authentication credentials required
	)
const (
	// Unknown error (default/zero value)
	CodeUnknown Code = 0
	// Operation canceled by caller
	CodeCanceled Code = 1
	// Request is invalid, regardless of system state
	CodeInvalidArgument Code = 2
	// Deadline expired before operation completed
	CodeDeadlineExceeded Code = 3
	// Requested resource cannot be found
	CodeNotFound Code = 4
	// Resource already exists
	CodeAlreadyExists Code = 5
	// Caller isn't authorized
	CodePermissionDenied Code = 6
	// Resource exhausted (quota, storage, etc.)
	CodeResourceExhausted Code = 7
	// System isn't in required state
	CodeFailedPrecondition Code = 8
	// Operation aborted (concurrency issue)
	CodeAborted Code = 9
	// Operation attempted past valid range
	CodeOutOfRange Code = 10
	// Operation not implemented/supported
	CodeUnimplemented Code = 11
	// Internal error (invariant broken)
	CodeInternal Code = 12
	// Service temporarily unavailable
	CodeUnavailable Code = 13
	// Unrecoverable data loss or corruption
	CodeDataLoss Code = 14
	// Valid authentication credentials required
	CodeUnauthenticated Code = 15
)

func CodeOf

func CodeOf(err error) Code

CodeOf extracts the error code from an error. Returns CodeUnknown if the error is not an *Error.

Example

ExampleCodeOf demonstrates extracting error code from any error.

package main

import (
	"errors"
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	// From errx error
	err1 := errx.New(errx.CodeInvalidArgument, "bad input")
	fmt.Println("errx error code:", errx.CodeOf(err1))

	// From standard error (returns CodeUnknown)
	err2 := errors.New("standard error")
	fmt.Println("standard error code:", errx.CodeOf(err2))

	// From wrapped errx error
	err3 := fmt.Errorf("wrapped: %w", err1)
	fmt.Println("wrapped error code:", errx.CodeOf(err3))

}
Output:
errx error code: invalid_argument
standard error code: unknown
wrapped error code: invalid_argument

func CodeValues

func CodeValues() []Code

CodeValues returns a list of the values for Code

func (Code) IsValid

func (x Code) IsValid() bool

IsValid provides a quick way to determine if the typed value is part of the allowed enumerated values

func (Code) String

func (x Code) String() string

String implements the Stringer interface.

type Error

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

Error represents a rich error with code, context, and debugging information. It implements the standard error interface and supports error wrapping.

Example (ClientVsDebug)

Example test to demonstrate usage patterns

package main

import (
	"errors"
	"fmt"
	"strings"

	"github.com/bjaus/errx"
)

func main() {
	// Simulating a database error in production
	dbError := errors.New("pq: connection refused on host db.internal.company.com:5432")

	// Wrap with safe client message
	err := errx.Wrap(dbError, errx.CodeUnavailable, "Service temporarily unavailable").
		WithSource("payment-service").
		WithTags("database", "postgres").
		WithDetail("service", "payment").
		WithMeta("db_host", "db.internal.company.com").
		WithMeta("retry_count", 3).
		WithDebug("PostgreSQL connection pool exhausted after 3 retries")

	// What the client sees
	fmt.Println("Client sees:", err.Error())

	// What gets logged for debugging
	fmt.Println("Logs contain:", strings.Contains(err.DebugMessage(), "db.internal.company.com"))

}
Output:
Client sees: Service temporarily unavailable
Logs contain: true
Example (ClientVsInternalData)

ExampleError_clientVsInternalData demonstrates the separation between client-safe and internal debugging data.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	// Create an error with both client-safe and internal data
	err := errx.New(errx.CodeInternal, "service temporarily unavailable").
		WithDetail("retry_after", "30s").                        // Client sees this
		WithSource("payment-service").                           // Internal only
		WithTags("external", "payment-provider").                // Internal only
		WithMeta("transaction_id", "txn_123456").                // Internal only
		WithMeta("retry_count", 3).                              // Internal only
		WithDebug("payment provider returned 500 after retries") // Internal only

	fmt.Println("=== Client-Safe Data (can be exposed in API responses) ===")
	fmt.Println("Message:", err.Error())
	// Print details field individually for deterministic output
	details := err.Details()
	fmt.Println("retry_after:", details["retry_after"])

	fmt.Println("\n=== Internal Data (for logs/debugging only) ===")
	fmt.Println("Source:", err.Source())
	fmt.Println("Tags:", err.Tags())
	// Print metadata fields individually for deterministic output
	metadata := err.Metadata()
	fmt.Println("transaction_id:", metadata["transaction_id"])
	fmt.Println("retry_count:", metadata["retry_count"])

}
Output:
=== Client-Safe Data (can be exposed in API responses) ===
Message: service temporarily unavailable
retry_after: 30s

=== Internal Data (for logs/debugging only) ===
Source: payment-service
Tags: [external payment-provider]
transaction_id: txn_123456
retry_count: 3
Example (LayeredArchitecture)

ExampleError_layeredArchitecture demonstrates error handling through application layers.

package main

import (
	"errors"
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	// 1. Database layer error
	dbErr := errors.New("connection refused")

	// 2. Repository layer wraps and adds context
	repoErr := errx.Wrap(dbErr, errx.CodeUnavailable, "database query failed").
		WithSource("user-repository").
		WithTags("database").
		WithMeta("query", "SELECT * FROM users WHERE id = ?").
		WithDebug("postgres connection pool exhausted")

	// 3. Service layer wraps again with business context
	serviceErr := errx.Wrap(repoErr, errx.CodeNotFound, "user not found").
		WithSource("user-service").
		WithDetail("user_id", "12345")

	// Each layer sees its own context
	fmt.Println("Service layer sees:", serviceErr.Error())
	fmt.Println("Source:", serviceErr.Source())

	// Can check for any error in the chain
	fmt.Println("Contains DB error:", errors.Is(serviceErr, dbErr))

}
Output:
Service layer sees: user not found
Source: user-service
Contains DB error: true
Example (SlogJSON)

ExampleError_slogJSON demonstrates structured logging output with nested errors.

package main

import (
	"log/slog"
	"os"

	"github.com/bjaus/errx"
)

func main() {
	// Create a logger that outputs JSON
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelError,
		ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
			// Remove timestamp for consistent output
			if a.Key == "time" {
				return slog.Attr{}
			}
			return a
		},
	}))

	// Scenario: Inner error from repository
	innerErr := errx.New(errx.CodeInternal, "query execution failed").
		WithSource("user-repository").
		WithTags("database", "postgres").
		WithMeta("query", "SELECT * FROM users").
		WithDebug("deadlock detected")

	// Outer error from service
	outerErr := errx.Wrap(innerErr, errx.CodeNotFound, "user not found").
		WithSource("user-service").
		WithDetail("user_id", "12345").
		WithMeta("request_id", "req-789")

	// Log it
	logger.Error("operation failed", "error", outerErr)

}
Output:
{"level":"ERROR","msg":"operation failed","error":{"code":"not_found","message":"user not found","source":"user-service","details":{"user_id":"12345"},"metadata":{"request_id":"req-789"},"cause":{"code":"internal","message":"query execution failed","source":"user-repository","tags":["database","postgres"],"metadata":{"query":"SELECT * FROM users"},"debug":"deadlock detected"}}}
Example (SlogJSON_threeLevels)

ExampleError_slogJSON_threeLevels demonstrates three-level nested error logging.

package main

import (
	"log/slog"
	"os"

	"github.com/bjaus/errx"
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelError,
		ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
			if a.Key == "time" {
				return slog.Attr{}
			}
			return a
		},
	}))

	// Level 1: Database driver
	dbErr := errx.New(errx.CodeInternal, "connection refused").
		WithSource("postgres-driver").
		WithMeta("port", 5432)

	// Level 2: Repository
	repoErr := errx.Wrap(dbErr, errx.CodeUnavailable, "database unavailable").
		WithSource("repository").
		WithMeta("operation", "findByID")

	// Level 3: Service
	serviceErr := errx.Wrap(repoErr, errx.CodeNotFound, "resource not found").
		WithSource("service").
		WithMeta("resource_type", "user")

	logger.Error("request failed", "error", serviceErr)

	// Note how each layer is nested in the "cause" field, preserving all context

}
Output:
{"level":"ERROR","msg":"request failed","error":{"code":"not_found","message":"resource not found","source":"service","metadata":{"resource_type":"user"},"cause":{"code":"unavailable","message":"database unavailable","source":"repository","metadata":{"operation":"findByID"},"cause":{"code":"internal","message":"connection refused","source":"postgres-driver","metadata":{"port":5432}}}}}

func As

func As(err error) (*Error, bool)

As finds the first *Error in err's tree. Returns the error value and true if found, or nil and false otherwise.

func Ensure

func Ensure(err error, code Code, message string) *Error

Ensure guarantees the returned error is an *Error. If err is nil, returns nil. If err is already an *Error (or wraps one), returns the existing *Error unchanged. Otherwise, wraps err as the cause with the given code and message.

func EnsureAborted

func EnsureAborted(err error, msg string) *Error

EnsureAborted ensures the error is an *Error, falling back to Aborted with the given message.

func EnsureAlreadyExists

func EnsureAlreadyExists(err error, msg string) *Error

EnsureAlreadyExists ensures the error is an *Error, falling back to Already_exists with the given message.

func EnsureCanceled

func EnsureCanceled(err error, msg string) *Error

EnsureCanceled ensures the error is an *Error, falling back to Canceled with the given message.

func EnsureDataLoss

func EnsureDataLoss(err error, msg string) *Error

EnsureDataLoss ensures the error is an *Error, falling back to Data_loss with the given message.

func EnsureDeadlineExceeded

func EnsureDeadlineExceeded(err error, msg string) *Error

EnsureDeadlineExceeded ensures the error is an *Error, falling back to Deadline_exceeded with the given message.

func EnsureFailedPrecondition

func EnsureFailedPrecondition(err error, msg string) *Error

EnsureFailedPrecondition ensures the error is an *Error, falling back to Failed_precondition with the given message.

func EnsureInternal

func EnsureInternal(err error, msg string) *Error

EnsureInternal ensures the error is an *Error, falling back to Internal with the given message.

func EnsureInvalidArgument

func EnsureInvalidArgument(err error, msg string) *Error

EnsureInvalidArgument ensures the error is an *Error, falling back to Invalid_argument with the given message.

func EnsureNotFound

func EnsureNotFound(err error, msg string) *Error

EnsureNotFound ensures the error is an *Error, falling back to Not_found with the given message.

func EnsureOutOfRange

func EnsureOutOfRange(err error, msg string) *Error

EnsureOutOfRange ensures the error is an *Error, falling back to Out_of_range with the given message.

func EnsurePermissionDenied

func EnsurePermissionDenied(err error, msg string) *Error

EnsurePermissionDenied ensures the error is an *Error, falling back to Permission_denied with the given message.

func EnsureResourceExhausted

func EnsureResourceExhausted(err error, msg string) *Error

EnsureResourceExhausted ensures the error is an *Error, falling back to Resource_exhausted with the given message.

func EnsureUnauthenticated

func EnsureUnauthenticated(err error, msg string) *Error

EnsureUnauthenticated ensures the error is an *Error, falling back to Unauthenticated with the given message.

func EnsureUnavailable

func EnsureUnavailable(err error, msg string) *Error

EnsureUnavailable ensures the error is an *Error, falling back to Unavailable with the given message.

func EnsureUnimplemented

func EnsureUnimplemented(err error, msg string) *Error

EnsureUnimplemented ensures the error is an *Error, falling back to Unimplemented with the given message.

func EnsureUnknown

func EnsureUnknown(err error, msg string) *Error

EnsureUnknown ensures the error is an *Error, falling back to Unknown with the given message.

func Ensuref

func Ensuref(err error, code Code, format string, args ...any) *Error

Ensuref is like Ensure but with a formatted message for the fallback case.

func EnsurefAborted

func EnsurefAborted(err error, format string, args ...any) *Error

EnsurefAborted ensures the error is an *Error, falling back to Aborted with a formatted message.

func EnsurefAlreadyExists

func EnsurefAlreadyExists(err error, format string, args ...any) *Error

EnsurefAlreadyExists ensures the error is an *Error, falling back to Already_exists with a formatted message.

func EnsurefCanceled

func EnsurefCanceled(err error, format string, args ...any) *Error

EnsurefCanceled ensures the error is an *Error, falling back to Canceled with a formatted message.

func EnsurefDataLoss

func EnsurefDataLoss(err error, format string, args ...any) *Error

EnsurefDataLoss ensures the error is an *Error, falling back to Data_loss with a formatted message.

func EnsurefDeadlineExceeded

func EnsurefDeadlineExceeded(err error, format string, args ...any) *Error

EnsurefDeadlineExceeded ensures the error is an *Error, falling back to Deadline_exceeded with a formatted message.

func EnsurefFailedPrecondition

func EnsurefFailedPrecondition(err error, format string, args ...any) *Error

EnsurefFailedPrecondition ensures the error is an *Error, falling back to Failed_precondition with a formatted message.

func EnsurefInternal

func EnsurefInternal(err error, format string, args ...any) *Error

EnsurefInternal ensures the error is an *Error, falling back to Internal with a formatted message.

func EnsurefInvalidArgument

func EnsurefInvalidArgument(err error, format string, args ...any) *Error

EnsurefInvalidArgument ensures the error is an *Error, falling back to Invalid_argument with a formatted message.

func EnsurefNotFound

func EnsurefNotFound(err error, format string, args ...any) *Error

EnsurefNotFound ensures the error is an *Error, falling back to Not_found with a formatted message.

func EnsurefOutOfRange

func EnsurefOutOfRange(err error, format string, args ...any) *Error

EnsurefOutOfRange ensures the error is an *Error, falling back to Out_of_range with a formatted message.

func EnsurefPermissionDenied

func EnsurefPermissionDenied(err error, format string, args ...any) *Error

EnsurefPermissionDenied ensures the error is an *Error, falling back to Permission_denied with a formatted message.

func EnsurefResourceExhausted

func EnsurefResourceExhausted(err error, format string, args ...any) *Error

EnsurefResourceExhausted ensures the error is an *Error, falling back to Resource_exhausted with a formatted message.

func EnsurefUnauthenticated

func EnsurefUnauthenticated(err error, format string, args ...any) *Error

EnsurefUnauthenticated ensures the error is an *Error, falling back to Unauthenticated with a formatted message.

func EnsurefUnavailable

func EnsurefUnavailable(err error, format string, args ...any) *Error

EnsurefUnavailable ensures the error is an *Error, falling back to Unavailable with a formatted message.

func EnsurefUnimplemented

func EnsurefUnimplemented(err error, format string, args ...any) *Error

EnsurefUnimplemented ensures the error is an *Error, falling back to Unimplemented with a formatted message.

func EnsurefUnknown

func EnsurefUnknown(err error, format string, args ...any) *Error

EnsurefUnknown ensures the error is an *Error, falling back to Unknown with a formatted message.

func New

func New(code Code, message string) *Error

New creates a new Error with the given code and message. The message should be safe to expose to clients.

Example

ExampleNew demonstrates creating a simple error with a code and message.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeNotFound, "user not found")

	fmt.Println("Code:", err.Code())
	fmt.Println("Message:", err.Error())
	fmt.Println("Error:", err.Error())

}
Output:
Code: not_found
Message: user not found
Error: user not found

func NewAborted

func NewAborted(msg string) *Error

NewAborted creates a new error of type Aborted with the given message.

func NewAlreadyExists

func NewAlreadyExists(msg string) *Error

NewAlreadyExists creates a new error of type Already_exists with the given message.

func NewCanceled

func NewCanceled(msg string) *Error

NewCanceled creates a new error of type Canceled with the given message.

func NewDataLoss

func NewDataLoss(msg string) *Error

NewDataLoss creates a new error of type Data_loss with the given message.

func NewDeadlineExceeded

func NewDeadlineExceeded(msg string) *Error

NewDeadlineExceeded creates a new error of type Deadline_exceeded with the given message.

func NewFailedPrecondition

func NewFailedPrecondition(msg string) *Error

NewFailedPrecondition creates a new error of type Failed_precondition with the given message.

func NewInternal

func NewInternal(msg string) *Error

NewInternal creates a new error of type Internal with the given message.

func NewInvalidArgument

func NewInvalidArgument(msg string) *Error

NewInvalidArgument creates a new error of type Invalid_argument with the given message.

func NewNotFound

func NewNotFound(msg string) *Error

NewNotFound creates a new error of type Not_found with the given message.

func NewOutOfRange

func NewOutOfRange(msg string) *Error

NewOutOfRange creates a new error of type Out_of_range with the given message.

func NewPermissionDenied

func NewPermissionDenied(msg string) *Error

NewPermissionDenied creates a new error of type Permission_denied with the given message.

func NewResourceExhausted

func NewResourceExhausted(msg string) *Error

NewResourceExhausted creates a new error of type Resource_exhausted with the given message.

func NewUnauthenticated

func NewUnauthenticated(msg string) *Error

NewUnauthenticated creates a new error of type Unauthenticated with the given message.

func NewUnavailable

func NewUnavailable(msg string) *Error

NewUnavailable creates a new error of type Unavailable with the given message.

func NewUnimplemented

func NewUnimplemented(msg string) *Error

NewUnimplemented creates a new error of type Unimplemented with the given message.

func NewUnknown

func NewUnknown(msg string) *Error

NewUnknown creates a new error of type Unknown with the given message.

func Newf

func Newf(code Code, format string, args ...any) *Error

Newf creates a new Error with a formatted message. The message should be safe to expose to clients.

Example

ExampleNewf demonstrates creating an error with formatted message.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	userID := 12345
	err := errx.Newf(errx.CodeInvalidArgument, "invalid user ID: %d", userID)

	fmt.Println(err.Error())

}
Output:
invalid user ID: 12345

func NewfAborted

func NewfAborted(format string, args ...any) *Error

NewfAborted creates a new error of type Aborted with a formatted message.

func NewfAlreadyExists

func NewfAlreadyExists(format string, args ...any) *Error

NewfAlreadyExists creates a new error of type Already_exists with a formatted message.

func NewfCanceled

func NewfCanceled(format string, args ...any) *Error

NewfCanceled creates a new error of type Canceled with a formatted message.

func NewfDataLoss

func NewfDataLoss(format string, args ...any) *Error

NewfDataLoss creates a new error of type Data_loss with a formatted message.

func NewfDeadlineExceeded

func NewfDeadlineExceeded(format string, args ...any) *Error

NewfDeadlineExceeded creates a new error of type Deadline_exceeded with a formatted message.

func NewfFailedPrecondition

func NewfFailedPrecondition(format string, args ...any) *Error

NewfFailedPrecondition creates a new error of type Failed_precondition with a formatted message.

func NewfInternal

func NewfInternal(format string, args ...any) *Error

NewfInternal creates a new error of type Internal with a formatted message.

func NewfInvalidArgument

func NewfInvalidArgument(format string, args ...any) *Error

NewfInvalidArgument creates a new error of type Invalid_argument with a formatted message.

func NewfNotFound

func NewfNotFound(format string, args ...any) *Error

NewfNotFound creates a new error of type Not_found with a formatted message.

func NewfOutOfRange

func NewfOutOfRange(format string, args ...any) *Error

NewfOutOfRange creates a new error of type Out_of_range with a formatted message.

func NewfPermissionDenied

func NewfPermissionDenied(format string, args ...any) *Error

NewfPermissionDenied creates a new error of type Permission_denied with a formatted message.

func NewfResourceExhausted

func NewfResourceExhausted(format string, args ...any) *Error

NewfResourceExhausted creates a new error of type Resource_exhausted with a formatted message.

func NewfUnauthenticated

func NewfUnauthenticated(format string, args ...any) *Error

NewfUnauthenticated creates a new error of type Unauthenticated with a formatted message.

func NewfUnavailable

func NewfUnavailable(format string, args ...any) *Error

NewfUnavailable creates a new error of type Unavailable with a formatted message.

func NewfUnimplemented

func NewfUnimplemented(format string, args ...any) *Error

NewfUnimplemented creates a new error of type Unimplemented with a formatted message.

func NewfUnknown

func NewfUnknown(format string, args ...any) *Error

NewfUnknown creates a new error of type Unknown with a formatted message.

func Wrap

func Wrap(err error, code Code, message string) *Error

Wrap wraps an existing error with additional context and an error code. The message should be safe to expose to clients.

Example

ExampleWrap demonstrates wrapping an existing error with errx context.

package main

import (
	"errors"
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	// Simulate a database error
	dbErr := errors.New("connection timeout")

	// Wrap it with errx error
	err := errx.Wrap(dbErr, errx.CodeUnavailable, "database unavailable")

	fmt.Println(err.Error())
	fmt.Println("Code:", err.Code())
	fmt.Println("Original error found:", errors.Is(err, dbErr))

}
Output:
database unavailable
Code: unavailable
Original error found: true

func WrapAborted

func WrapAborted(err error, msg string) *Error

WrapAborted wraps an existing error of type Aborted with the given message.

func WrapAlreadyExists

func WrapAlreadyExists(err error, msg string) *Error

WrapAlreadyExists wraps an existing error of type Already_exists with the given message.

func WrapCanceled

func WrapCanceled(err error, msg string) *Error

WrapCanceled wraps an existing error of type Canceled with the given message.

func WrapDataLoss

func WrapDataLoss(err error, msg string) *Error

WrapDataLoss wraps an existing error of type Data_loss with the given message.

func WrapDeadlineExceeded

func WrapDeadlineExceeded(err error, msg string) *Error

WrapDeadlineExceeded wraps an existing error of type Deadline_exceeded with the given message.

func WrapFailedPrecondition

func WrapFailedPrecondition(err error, msg string) *Error

WrapFailedPrecondition wraps an existing error of type Failed_precondition with the given message.

func WrapInternal

func WrapInternal(err error, msg string) *Error

WrapInternal wraps an existing error of type Internal with the given message.

func WrapInvalidArgument

func WrapInvalidArgument(err error, msg string) *Error

WrapInvalidArgument wraps an existing error of type Invalid_argument with the given message.

func WrapNotFound

func WrapNotFound(err error, msg string) *Error

WrapNotFound wraps an existing error of type Not_found with the given message.

func WrapOutOfRange

func WrapOutOfRange(err error, msg string) *Error

WrapOutOfRange wraps an existing error of type Out_of_range with the given message.

func WrapPermissionDenied

func WrapPermissionDenied(err error, msg string) *Error

WrapPermissionDenied wraps an existing error of type Permission_denied with the given message.

func WrapResourceExhausted

func WrapResourceExhausted(err error, msg string) *Error

WrapResourceExhausted wraps an existing error of type Resource_exhausted with the given message.

func WrapUnauthenticated

func WrapUnauthenticated(err error, msg string) *Error

WrapUnauthenticated wraps an existing error of type Unauthenticated with the given message.

func WrapUnavailable

func WrapUnavailable(err error, msg string) *Error

WrapUnavailable wraps an existing error of type Unavailable with the given message.

func WrapUnimplemented

func WrapUnimplemented(err error, msg string) *Error

WrapUnimplemented wraps an existing error of type Unimplemented with the given message.

func WrapUnknown

func WrapUnknown(err error, msg string) *Error

WrapUnknown wraps an existing error of type Unknown with the given message.

func Wrapf

func Wrapf(err error, code Code, format string, args ...any) *Error

Wrapf wraps an existing error with a formatted message. The message should be safe to expose to clients.

func WrapfAborted

func WrapfAborted(err error, format string, args ...any) *Error

WrapfAborted wraps an existing error of type Aborted with a formatted message.

func WrapfAlreadyExists

func WrapfAlreadyExists(err error, format string, args ...any) *Error

WrapfAlreadyExists wraps an existing error of type Already_exists with a formatted message.

func WrapfCanceled

func WrapfCanceled(err error, format string, args ...any) *Error

WrapfCanceled wraps an existing error of type Canceled with a formatted message.

func WrapfDataLoss

func WrapfDataLoss(err error, format string, args ...any) *Error

WrapfDataLoss wraps an existing error of type Data_loss with a formatted message.

func WrapfDeadlineExceeded

func WrapfDeadlineExceeded(err error, format string, args ...any) *Error

WrapfDeadlineExceeded wraps an existing error of type Deadline_exceeded with a formatted message.

func WrapfFailedPrecondition

func WrapfFailedPrecondition(err error, format string, args ...any) *Error

WrapfFailedPrecondition wraps an existing error of type Failed_precondition with a formatted message.

func WrapfInternal

func WrapfInternal(err error, format string, args ...any) *Error

WrapfInternal wraps an existing error of type Internal with a formatted message.

func WrapfInvalidArgument

func WrapfInvalidArgument(err error, format string, args ...any) *Error

WrapfInvalidArgument wraps an existing error of type Invalid_argument with a formatted message.

func WrapfNotFound

func WrapfNotFound(err error, format string, args ...any) *Error

WrapfNotFound wraps an existing error of type Not_found with a formatted message.

func WrapfOutOfRange

func WrapfOutOfRange(err error, format string, args ...any) *Error

WrapfOutOfRange wraps an existing error of type Out_of_range with a formatted message.

func WrapfPermissionDenied

func WrapfPermissionDenied(err error, format string, args ...any) *Error

WrapfPermissionDenied wraps an existing error of type Permission_denied with a formatted message.

func WrapfResourceExhausted

func WrapfResourceExhausted(err error, format string, args ...any) *Error

WrapfResourceExhausted wraps an existing error of type Resource_exhausted with a formatted message.

func WrapfUnauthenticated

func WrapfUnauthenticated(err error, format string, args ...any) *Error

WrapfUnauthenticated wraps an existing error of type Unauthenticated with a formatted message.

func WrapfUnavailable

func WrapfUnavailable(err error, format string, args ...any) *Error

WrapfUnavailable wraps an existing error of type Unavailable with a formatted message.

func WrapfUnimplemented

func WrapfUnimplemented(err error, format string, args ...any) *Error

WrapfUnimplemented wraps an existing error of type Unimplemented with a formatted message.

func WrapfUnknown

func WrapfUnknown(err error, format string, args ...any) *Error

WrapfUnknown wraps an existing error of type Unknown with a formatted message.

func (*Error) Code

func (e *Error) Code() Code

Code returns the error code. Returns CodeUnknown if the error is nil.

func (*Error) DebugMessage

func (e *Error) DebugMessage() string

DebugMessage returns a detailed debug message with all context. This should only be logged or shown to system maintainers, never to clients.

func (*Error) Details

func (e *Error) Details() map[string]any

Details returns the client-safe error details.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface. It returns the client-safe message.

func (*Error) FormatStackTrace

func (e *Error) FormatStackTrace() string

FormatStackTrace returns a human-readable stack trace.

func (*Error) Is

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

Is supports error comparison with errors.Is. Two errors are considered equal if they have the same code.

func (*Error) IsRetryable

func (e *Error) IsRetryable() bool

IsRetryable returns whether the error indicates a retryable operation.

func (*Error) LogValue

func (e *Error) LogValue() slog.Value

LogValue implements slog.LogValuer for structured logging integration. Returns a slog.GroupValue containing all error fields for debugging.

func (*Error) Metadata

func (e *Error) Metadata() map[string]any

Metadata returns the error's internal debug metadata.

func (*Error) Source

func (e *Error) Source() string

Source returns the source (service/package/component) where the error occurred.

func (*Error) StackTrace

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

StackTrace returns the captured stack trace.

func (*Error) Tags

func (e *Error) Tags() []string

Tags returns the error's tags.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the wrapped error, supporting errors.Unwrap.

func (*Error) WithDebug

func (e *Error) WithDebug(message string) *Error

WithDebug sets an internal debug message with additional implementation details. This is only shown in debug messages, never to clients.

Example

ExampleError_WithDebug demonstrates separating client messages from debug details.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeInternal, "service temporarily unavailable").
		WithDebug("failed to connect to postgres.internal.company.com:5432")

	fmt.Println("Client sees:", err.Error())
	fmt.Println("Logs contain:", err.DebugMessage())

}
Output:
Client sees: service temporarily unavailable
Logs contain: [internal] service temporarily unavailable | debug=failed to connect to postgres.internal.company.com:5432

func (*Error) WithDebugf

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

WithDebugf sets a formatted internal debug message.

func (*Error) WithDetail

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

WithDetail adds a client-safe key-value detail to the error. These details are safe to expose to clients and are typically included in error responses.

Example

ExampleError_WithDetail demonstrates adding client-safe details.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeInvalidArgument, "validation failed").
		WithDetail("field", "email").
		WithDetail("reason", "invalid format")

	fmt.Println("Details:", err.Details())

	// These details are safe to expose to clients in the error response

}
Output:
Details: map[field:email reason:invalid format]

func (*Error) WithMeta

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

WithMeta adds a key-value pair to the error's internal metadata. This metadata is included in debug messages but NOT exposed to clients.

Example

ExampleError_WithMeta demonstrates adding internal metadata for debugging.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeInternal, "operation failed").
		WithMeta("user_id", 12345).
		WithMeta("operation", "update_profile").
		WithMeta("retry_count", 3)

	// Print metadata fields individually for deterministic output
	metadata := err.Metadata()
	fmt.Println("user_id:", metadata["user_id"])
	fmt.Println("operation:", metadata["operation"])
	fmt.Println("retry_count:", metadata["retry_count"])

	// This metadata is NOT exposed to clients, only used in logs/debugging

}
Output:
user_id: 12345
operation: update_profile
retry_count: 3

func (*Error) WithMetaFromContext

func (e *Error) WithMetaFromContext(ctx context.Context) *Error

WithMetaFromContext pulls metadata stored via WithMetaContext from the context and merges it into the error's internal metadata map. Context values overwrite existing keys (last-write-wins), so call ordering determines precedence:

// Context wins — post_id will be 42:
errx.New(code, msg).WithMeta("post_id", 99).WithMetaFromContext(ctx)

// WithMeta wins — post_id will be 99:
errx.New(code, msg).WithMetaFromContext(ctx).WithMeta("post_id", 99)

func (*Error) WithRetryable

func (e *Error) WithRetryable() *Error

WithRetryable marks the error as representing a retryable operation. This indicates that the same request can be retried and may succeed.

func (*Error) WithSource

func (e *Error) WithSource(source string) *Error

WithSource sets the source (service/package/component) where the error occurred.

Example

ExampleError_WithSource demonstrates tagging errors with source (service/package/component).

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeUnavailable, "service unavailable").
		WithSource("payment-service")

	fmt.Println("Source:", err.Source())

	// Useful for identifying which service/component generated the error

}
Output:
Source: payment-service

func (*Error) WithTags

func (e *Error) WithTags(tags ...string) *Error

WithTags adds one or more tags to categorize the error.

Example

ExampleError_WithTags demonstrates categorizing errors with tags.

package main

import (
	"fmt"

	"github.com/bjaus/errx"
)

func main() {
	err := errx.New(errx.CodeInternal, "operation failed").
		WithTags("database", "postgres").
		WithTags("critical")

	fmt.Println("Tags:", err.Tags())

	// Tags can be used for filtering, alerting, or categorizing errors

}
Output:
Tags: [database postgres critical]

Jump to

Keyboard shortcuts

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