errdef

package module
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Dec 29, 2025 License: MIT Imports: 22 Imported by: 0

README

errdef

Tag Go Version Go Reference Ask DeepWiki Build Status Go Report Card

errdef separates Go errors into Definitions and Error instances, making them typed, structured, and uniform. It integrates cleanly with the standard ecosystem — errors.Is / errors.As, fmt.Formatter, json.Marshaler, and slog.LogValuer — while adding fields, stack traces, and flexible error composition.

Status: The core API is stable, but minor breaking changes may occur before v1.0.0.

Table of Contents

Getting Started

go get github.com/shiwano/errdef
package main

import (
    "context"
    "errors"
    "fmt"

    "github.com/shiwano/errdef"
)

var (
    // Reusable error definition (sentinel-like, extensible).
    ErrNotFound = errdef.Define("not_found", errdef.HTTPStatus(404))

    // Type-safe field (constructor + extractor pair).
    UserID, UserIDFrom = errdef.DefineField[string]("user_id")
)

func findUser(ctx context.Context, id string) error {
    // Create an error; attach typed fields as needed.
    return ErrNotFound.With(ctx, UserID(id)).New("user not found")
}

func main() {
    err := findUser(context.TODO(), "u123")

    // Standard errors.Is still works.
    if errors.Is(err, ErrNotFound) {
        fmt.Println("user not found")
    }

    // Extract fields in a type-safe way.
    if userID, ok := UserIDFrom(err); ok {
        fmt.Println("user id:", userID)
    }
}

Note: Both errors.Is and field extractors compare identities, not string names. Each Define or DefineField call creates a unique identity, even with identical strings. Use unique names throughout your application to avoid confusion.

// Different definitions with the same kind string
ErrNotFound1 := errdef.Define("not_found")
ErrNotFound2 := errdef.Define("not_found")

err1 := ErrNotFound1.New("not found")
errors.Is(err1, ErrNotFound1) // true
errors.Is(err1, ErrNotFound2) // false - different definition

// Different field definitions with the same name
UserID1, UserID1From := errdef.DefineField[string]("user_id")
UserID2, UserID2From := errdef.DefineField[string]("user_id")

err2 := ErrNotFound1.WithOptions(UserID1("u123")).New("not found")
_, ok := UserID1From(err2) // ok: true
_, ok = UserID2From(err2)  // ok: false - different field

Features

Error Constructors

Choose how to construct an error depending on whether you create a new one or keep a cause.

  • New(msg): Create a new error.
  • Errorf(fmt, ...): Create with a formatted message.
  • Wrap(cause): Wrap and keep the cause (errors.Is(err, cause) stays true).
  • Wrapf(cause, fmt, ...): Wrap with a cause and a formatted message.
  • Join(causes...): Join multiple causes (errors.Is(err, cause) stays true).
// New / Errorf
e1 := ErrNotFound.New("user not found")
e2 := ErrNotFound.Errorf("user %s not found", "u123")

// Wrap / Wrapf (keep the cause)
e3 := ErrNotFound.Wrap(sql.ErrNoRows)
e4 := ErrNotFound.Wrapf(sql.ErrNoRows, "lookup failed: %s", "u123")

errors.Is(e3, sql.ErrNoRows) // true
errors.Is(e4, sql.ErrNoRows) // true

// Join (keep multiple causes)
e5 := ErrNotFound.Join(sql.ErrNoRows, sql.ErrConnDone)

errors.Is(e5, sql.ErrNoRows)   // true
errors.Is(e5, sql.ErrConnDone) // true
Attaching Additional Options
  • With(ctx, ...opts): Requires ctx. Use when options need request-scoped data.
  • WithOptions(...opts): No ctx. Use for context-independent options.
// Context-aware (requires ctx)
e1 := ErrNotFound.With(context.TODO(), UserID("u123")).New("user not found")

// Context-free (no ctx)
e2 := ErrNotFound.WithOptions(UserID("u123")).New("user not found")
Error Inspection

You can inspect the kind, fields, stack trace, or causes from both errdef.Definition and any errdef.Error in the error chain.

if kind, ok := errdef.KindFrom(err); ok {
    fmt.Println("kind:", kind)
}

if fields, ok := errdef.FieldsFrom(err); ok {
    fmt.Println("fields:", fields)
}

if stack, ok := errdef.StackFrom(err); ok {
    fmt.Println("stack:", stack)
}

if causes, ok := errdef.UnwrapTreeFrom(err); ok {
    fmt.Println("causes:", causes)
}
Detailed Error Formatting

Using the %+v format specifier will print the error message, kind, fields, stack trace, and any wrapped errors.

err := findUser(ctx, "u-123")
fmt.Printf("%+v\n", err)

Example Output:

user not found
---
kind: not_found
fields:
  http_status: 404
  user_id: u-123
stack:
  main.findUser
    /path/to/your/project/main.go:23
  main.main
    /path/to/your/project/main.go:35
  runtime.main
    /usr/local/go/src/runtime/proc.go:250
causes: (1 error)
  [1] record not found
      ---
      stack:
        ...
Source Code Snippets

You can enhance stack traces with source code snippets using the StackSource(around, depth) option. This displays around lines before and after each stack frame, with depth controlling how many frames to show (use -1 for all frames, 0 to disable with zero overhead).

var ErrNotFound = errdef.Define("not_found", errdef.StackSource(3, 1))
err := findUser(ctx, "u-123")
fmt.Printf("%+v\n", err)

Example Output:

user not found
---
kind: not_found
fields:
  http_status: 404
  user_id: u-123
stack:
  main.findUser
    /path/to/your/project/main.go:23
      20: var ErrNotFound = errdef.Define("not_found", errdef.StackSource(3, 1))
      21:
      22: func findUser(ctx context.Context, id string) error {
    > 23:     return ErrNotFound.With(ctx, UserID(id)).New("user not found")
      24: }
      25:
      26: func main() {
  main.main
    /path/to/your/project/main.go:35
  runtime.main
    /usr/local/go/src/runtime/proc.go:250
causes: (1 error)
  [1] record not found

Note: Source code is read from disk at runtime only when printing errors with %+v format. If source files are unavailable (e.g., in production binaries without deployed source files), it shows only the stack trace. The implementation is designed to minimize overhead.

JSON Marshaling

errdef.Error implements json.Marshaler to produce structured JSON output.

Example Output:

{
  "message": "user not found",
  "kind": "not_found",
  "fields": {
    "http_status": 404,
    "user_id": "u-123"
  },
  "stack": [
    { "function":"main.findUser","file":"/path/to/your/project/main.go","line":23 },
    { "function":"main.main","file":"/path/to/your/project/main.go","line":35 },
    { "function":"runtime.main","file":"/usr/local/go/src/runtime/proc.go","line":250 }
  ],
  "causes": [
    { "message": "record not found", "stack":[] }
  ]
}

Note: If multiple fields have the same name, the last one in insertion order will be used in the JSON output.

Structured Logging (slog)

errdef.Error implements slog.LogValuer out-of-the-box to provide structured logging with zero configuration.

slog.Error("failed to find user", "error", err)

Example Output:

{
  "level": "ERROR",
  "msg": "failed to find user",
  "error": {
    "message": "user not found",
    "kind": "not_found",
    "fields": {
      "http_status": 404,
      "user_id": "u-123"
    },
    "origin": {
      "file": "/path/to/your/project/main.go",
      "line": 23,
      "func": "main.findUser"
    }
  }
}

Note: If multiple fields have the same name, the last one in insertion order will be used in the log output.

By default, the slog output keeps stack traces concise (showing only the error origin) and omits the causes tree to avoid excessive verbosity. If you need more detailed information, such as full stack traces or the complete causes tree, you can use the following methods:

  • Log the full stack trace: The Stack type also implements slog.LogValuer.

    stack, _ := errdef.StackFrom(err)
    slog.Error("failed to find user", "stack", stack)
    
  • Log the full causes tree: The Node type also implements slog.LogValuer.

    causes, _ := errdef.UnwrapTreeFrom(err)
    slog.Error("failed to find user", "causes", causes)
    
Field Constructors

The field constructor can be chained with methods like WithValue or WithValueFunc to create new, simplified constructors. This is useful for creating options with predefined or dynamically generated values.

var (
    errorCode, _            = errdef.DefineField[int]("error_code")
    ErrorCodeAmountTooLarge = errorCode.WithValue(2002)

    errorUniqueID, _ = errdef.DefineField[string]("error_unique_id")
    ErrorUniqueID    = errorUniqueID.WithValueFunc(func() string {
        return generateRandomID()
    })
)

err := ErrPaymentFailed.WithOptions(
    ErrorCodeAmountTooLarge(),
    ErrorUniqueID(),
).New("amount too large")
Field Extractors

The field extractor provides several helper methods for retrieving values from an error instance, especially for handling cases where a field might not exist.

errWithCode := ErrPaymentFailed.New("payment failed")
errWithoutCode := ErrNotFound.New("not found")

code, ok := ErrorCodeFrom(errWithCode)
// code: 2001, ok: true

defaultCode := ErrorCodeFrom.OrDefault(errWithoutCode, 9999)
// defaultCode: 9999

fallbackCode := ErrorCodeFrom.OrFallback(errWithoutCode, func(err error) int {
    return 10000
})
// fallbackCode: 10000

codeWithDefault := ErrorCodeFrom.WithDefault(9999)
// codeWithDefault(errWithCode)    -> 2001
// codeWithDefault(errWithoutCode) -> 9999

Note: Extractors follow the same rules as errors.As. They search the error chain and extract the value from the first matching errdef.Error, then stop searching. If you need inner fields at the outer layer, prefer explicitly copying the needed fields when wrapping.

Free-Form Details

You can attach free-form diagnostic details to an error under the "details" field.

err := ErrNotFound.With(
  errdef.Details{"tenant_id": 1, "user_ids": []int{1,2,4}},
).Wrap(err)

details := errdef.DetailsFrom.OrZero(err)
// details: errdef.Details{
//   "tenant_id": 1,
//   "user_ids": []int{1,2,4},
// }

Note: Details is derived from a map[string]any type and implements Option, allowing you to attach arbitrary key-value pairs.

Context Integration

You can use context.Context to automatically attach request-scoped information to your errors.

func tracingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Attach the TraceID option to the context.
        ctx := errdef.ContextWithOptions(
            r.Context(),
            errdef.TraceID(r.Header.Get("X-Request-ID")),
        )
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

var ErrRateLimited = errdef.Define("rate_limited", errdef.HTTPStatus(429))

func someHandler(ctx context.Context) error {
    // The TraceID option is automatically attached from the context.
    return ErrRateLimited.With(ctx).New("too many requests")
}
Redaction

Wrap secrets (tokens, emails, IDs, etc.) with Redacted[T] to ensure they always render as "[REDACTED]" in logs and serialized output (fmt, json, slog, encoding). The original value remains accessible via .Value() for internal use.

var UserEmail, UserEmailFrom = errdef.DefineField[errdef.Redacted[string]]("user_email")

err := ErrInvalidArgument.With(
  UserEmail(errdef.Redact("[email protected]")),
).Wrap(err)

// fmt.Printf("%+v\n", err): user_email: [REDACTED]
// log/slog prints         : user_email="[REDACTED]"
// json.Marshal(err)       : { "user_email": "[REDACTED]" }
// internal access         : email, _ := UserEmailFrom(err); _ = email.Value()
Joining Errors

You can join multiple errors into one using the Join method on a Definition.

var (
  ErrLeft  = errdef.Define("left")
  ErrRight = errdef.Define("right")
  ErrTop   = errdef.Define("top")
)

l := ErrLeft.New("L")
r := ErrRight.New("R")
err := ErrTop.Join(l, r)
causes := err.(errdef.Error).Unwrap()
// causes: [l, r]
Panic Recovery

errdef provides a convenient way to convert panics into structured errors, ensuring that even unexpected failures are handled consistently.

var ErrPanic = errdef.Define("panic", errdef.HTTPStatus(500))

func processRequest(w http.ResponseWriter, r *http.Request) error {
    err := ErrPanic.Recover(func() error {
        maybePanic()
        return nil
    })
    if errors.Is(err, ErrPanic) {
        slog.Warn("a panic was captured")
        // Perform cleanup or additional logging
    }
    return err
}

if err := processRequest(w, r); err != nil {
    var pe errdef.PanicError
    if errors.As(err, &pe) {
        slog.Error("a panic occurred", "panic_value", pe.PanicValue())
    }
    // ...
}
Error Resolution

For advanced use cases like mapping error codes from external APIs, use a Resolver.

import (
    "github.com/shiwano/errdef"
    "github.com/shiwano/errdef/resolver"
)

var (
    ErrStripeCardDeclined = errdef.Define("card_declined", errdef.HTTPStatus(400))
    ErrStripeRateLimit    = errdef.Define("rate_limit", errdef.HTTPStatus(429))
    ErrStripeUnhandled    = errdef.Define("stripe_unhandled", errdef.HTTPStatus(500))

    // Order defines priority (first-wins).
    ErrStripe = resolver.New(
        ErrStripeCardDeclined,
        ErrStripeRateLimit,
    ).WithDefault(ErrStripeUnhandled) // Remove if you want strict matching.
)

func handleStripeError(code, msg string) error {
    return ErrStripe.ResolveKindOrDefault(errdef.Kind(code)).New(msg)
}

func handleStripeHTTPError(statusCode int, msg string) error {
    return ErrStripe.ResolveFieldOrDefault(errdef.HTTPStatus.Key(), statusCode).New(msg)
}

Note: If multiple definitions have the same Kind or field value, the first one in the resolver's definition order will be used.

Error Deserialization

The errdef/unmarshaler package allows you to deserialize errdef.Error instances from JSON or other formats. Use a Resolver to map kind strings to error definitions, and the unmarshaler will restore typed errors with their fields and stack traces.

package main

import (
    "encoding/json"
    "fmt"
    "io"

    "github.com/shiwano/errdef"
    "github.com/shiwano/errdef/resolver"
    "github.com/shiwano/errdef/unmarshaler"
)

var (
    ErrNotFound        = errdef.Define("not_found")
    UserID, UserIDFrom = errdef.DefineField[string]("user_id")
)

func main() {
    // Serialize an errdef.Error to JSON
    original := ErrNotFound.WithOptions(UserID("u123")).Wrapf(io.EOF, "user not found")
    data, _ := json.Marshal(original)

    // Deserialize JSON back into an errdef.Error
    r := resolver.New(ErrNotFound)
    u := unmarshaler.NewJSON(r,
        unmarshaler.WithBuiltinFields(),
        unmarshaler.WithStandardSentinelErrors(),
    )
    restored, _ := u.Unmarshal(data)

    fmt.Println(restored.Error())                 // "user not found"
    fmt.Println(UserIDFrom.OrZero(restored))      // "u123"
    fmt.Println(errors.Is(restored, ErrNotFound)) // true
    fmt.Println(errors.Is(restored, io.EOF))      // true
}

Note: The unmarshaler package is designed to work with any serialization format, not just JSON. You can implement custom Decoder functions for formats like Protocol Buffers, XML, MessagePack, or any proprietary format.

// Custom decoder for your format
func protoDecoder(msg *ErrorProto) (*unmarshaler.DecodedData, error) {
    return convertToDecodedData(msg), nil
}

// Use it with the unmarshaler
u := unmarshaler.New(resolver, protoDecoder)
msg := createProtoMessage() // *ErrorProto
restored, _ := u.Unmarshal(msg) // Type-safe: accepts *ErrorProto by generics

For a complete example with Protocol Buffers including marshal functions and full round-trip demonstration, see examples/protobuf.

Ecosystem Integration

errdef is designed to work seamlessly with the broader Go ecosystem.

  • Structured Logging:
    • slog: Implements slog.LogValuer for rich, structured logs out-of-the-box.
    • zap: Compatible with zap via helper functions. See examples/zap.
    • zerolog: Compatible with zerolog via helper functions. See examples/zerolog.
  • Error Reporting Services:
    • Sentry: Compatible with the Sentry Go SDK by implementing the StackTracer interface. See examples/sentry.
    • Google Cloud Error Reporting: Compatible with Google Cloud Error Reporting by implementing the DebugStacker interface. See examples/gcloud_error_reporting.
  • Legacy Error Handling:
    • pkg/errors: Supports interoperability with pkg/errors by implementing the causer interface.
Built-in Options
Option Description Extractor
HTTPStatus(int) Attaches an HTTP status code. HTTPStatusFrom
LogLevel(slog.Level) Attaches a log level of type slog.Level. LogLevelFrom
TraceID(string) Attaches a trace or request ID. TraceIDFrom
Domain(string) Labels the error with a service or subsystem name. DomainFrom
UserHint(string) Provides a safe, user-facing hint message. UserHintFrom
Public() Marks the error as safe to expose externally. IsPublic
Retryable() Marks the operation as retryable. IsRetryable
RetryAfter(time.Duration) Recommends a delay to wait before retrying. RetryAfterFrom
Unreportable() Prevents the error from being sent to error tracking. IsUnreportable
ExitCode(int) Sets the exit code for a CLI application. ExitCodeFrom
HelpURL(string) Provides a URL for documentation or help guides. HelpURLFrom
Details{} Attaches free-form diagnostic details to an error. DetailsFrom
NoTrace() Disables stack trace collection for the error. -
StackSkip(int) Skips a specified number of frames during stack capture. -
StackDepth(int) Sets the depth of the stack capture (default: 32). -
StackSource(around, depth) Shows source code around stack frames in %+v output. -
Formatter(f) Overrides the default fmt.Formatter behavior. -
JSONMarshaler(f) Overrides the default json.Marshaler behavior. -
LogValuer(f) Overrides the default slog.LogValuer behavior. -

Examples

Performance

Last updated: 2025-10-18

errdef adds structured error handling on top of Go's standard library. Here are the key performance metrics:

Operation stdlib errdef (NoTrace) errdef (default) errdef (dynamic field)
New ~17 ns ~28 ns ~303 ns ~510 ns
Wrap ~106 ns ~28 ns ~289 ns ~534 ns
Memory 16-56 B 80 B 336 B 896 B

Note: Benchmarked on Apple M1 Pro, Go 1.25 (stack depth: 32)

How to Run the Benchmarks
GOMAXPROCS=1 go test -bench=. -benchmem -benchtime=3s ./...
Performance Best Practices

In practice, error handling is rarely the bottleneck. Focus on correctness first, then optimize if profiling shows that error creation is a significant cost.

  1. Default is fine for most cases: The ~303 ns overhead is negligible unless you're creating thousands of errors per second.

  2. Disable stack traces in hot paths: Stack trace capture takes ~275 ns per error. Use NoTrace() in tight loops or high-frequency code paths where errors are common:

    // ✅ Hot paths (loops, frequently called functions)
    var ErrValidation = errdef.Define("validation", errdef.NoTrace())
    for _, item := range items {
        if err := validate(item); err != nil {
            return ErrValidation.Wrap(err) // Fast: ~28 ns, 80 B
        }
    }
    
    // ❌ API boundaries, critical errors (keep stack traces!)
    var ErrDatabaseFailure = errdef.Define("db_failure") // ~303 ns, 336 B
    
  3. Minimize dynamic fields: Using With() or WithOptions() adds ~202 ns base overhead plus ~59 ns per field. For hot paths where fields are constant, prefer defining errors with fields upfront:

    // ✅ Attach fields to definition upfront
    var ErrInvalidInput = errdef.Define("invalid_input", errdef.HTTPStatus(400), errdef.NoTrace())
    for _, input := range inputs {
        if !input.IsValid() {
            return ErrInvalidInput.New("invalid") // Fast: ~28 ns, 80 B
        }
    }
    
    // ❌ Attach fields dynamically in hot path
    var ErrInvalidInput = errdef.Define("invalid_input", errdef.NoTrace())
    for _, input := range inputs {
        if !input.IsValid() {
            return ErrInvalidInput.WithOptions(errdef.HTTPStatus(400)).New("invalid") // ~230 ns, 640 B
        }
    }
    
  4. Limit stack depth: For hot paths where you still want to capture the error origin, use StackDepth(1) to keep just the first frame:

    // ✅ Capture only the immediate origin
    var ErrDBQuery = errdef.Define("db_query", errdef.StackDepth(1))
    if err := db.Query(...); err != nil {
        return ErrDBQuery.Wrap(err) // Fast: ~177 ns, 88 B
    }
    
    // ❌ Keep full stack trace (default depth: 32)
    var ErrDBQuery = errdef.Define("db_query")
    if err := db.Query(...); err != nil {
        return ErrDBQuery.Wrap(err) // ~289 ns, 336 B
    }
    
Additional Operations

Other operations also have measurable overhead:

Operation Time Memory
Detailed Error Formatting ~1.4 µs 1.3 KB
Structured Logging (slog) ~329 ns 544 B
Field Extraction ~154 ns 32 B
Resolver (kind lookup) ~7 ns 0 B
Resolver (field lookup) ~214-245 ns 528 B
JSON Marshaling (NoTrace) ~910 ns 800 B
JSON Marshaling (simple) ~2.1 µs 1.8 KB
JSON Marshaling (deep chain) ~249 µs 76 KB
JSON unmarshaling (simple) ~5.6 µs 2.1 KB
JSON unmarshaling (deep chain) ~56 µs 19 KB

Library Comparison

Last updated: 2025-10-07

If you spot inaccuracies or want another library included, please open an issue or PR.

Comparison Table
Feature stdlib pkg/errors cockroach
db/errors
eris errorx merry v2 errdef
errors.Is/As Compatibility
Automatic Stack Traces
Stack Control (Disable/Depth) ⚠️
Structured Data ⚠️ ⚠️ ⚠️ ✅ (Type-Safe)
Redaction
Structured JSON ⚠️ (Proto) ⚠️ (Logging) ⚠️ (Formatted)
slog Integration
Panic Recovery
Multiple Causes (errors.Join)
JSON Deserialization ⚠️
Protobuf Deserialization ⚠️ (Extensible)
Quick Notes
  • pkg/errors: A historical library that added stack trace functionality, but is now archived.
  • cockroachdb/errors: Specializes in distributed systems and has a very powerful Protobuf serialization/deserialization feature.
  • eris: Provides good stack trace formatting but lacks structured field attachment feature.
  • errorx / merry v2: Although not type-safe, they provide a feature to attach information to errors in a simple key-value format.
  • errdef: Features a design that separates Definitions from Instances, enabling type-safe fields, native slog integration, and full JSON round-trip capabilities.

Contributing

Contributions are welcome! Feel free to send issues or pull requests.

License

This project is licensed under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// HTTPStatus attaches an HTTP status code.
	HTTPStatus, HTTPStatusFrom = DefineField[int]("http_status")

	// LogLevel attaches a log level of type `slog.Level`.
	LogLevel, LogLevelFrom = DefineField[slog.Level]("log_level")

	// TraceID attaches a trace or request ID.
	TraceID, TraceIDFrom = DefineField[string]("trace_id")

	// Domain labels the error with a service or subsystem name.
	Domain, DomainFrom = DefineField[string]("domain")

	// Provides a safe, user-facing hint message.
	UserHint, UserHintFrom = DefineField[string]("user_hint")

	// Public marks the error as safe to expose externally.
	Public, IsPublic = public.WithValue(true), publicFrom.WithZero()

	// Retryable marks the operation as retryable.
	Retryable, IsRetryable = retryable.WithValue(true), retryableFrom.WithZero()

	// RetryAfter recommends a delay to wait before retrying.
	RetryAfter, RetryAfterFrom = DefineField[time.Duration]("retry_after")

	// Unreportable prevents the error from being sent to error tracking.
	Unreportable, IsUnreportable = unreportable.WithValue(true), unreportableFrom.WithZero()

	// ExitCode sets the exit code for a CLI application.
	ExitCode, ExitCodeFrom = DefineField[int]("exit_code")

	// HelpURL provides a URL for documentation or help guides.
	HelpURL, HelpURLFrom = DefineField[string]("help_url")

	// DetailsFrom extracts diagnostic details from an error.
	DetailsFrom FieldExtractor[Details] = detailsFrom
)

Functions

func ContextWithOptions

func ContextWithOptions(ctx context.Context, opts ...Option) context.Context

ContextWithOptions adds error options to a context. These options will be automatically applied when creating errors using Definition.With method.

func DefineField

func DefineField[T any](name string) (FieldConstructor[T], FieldExtractor[T])

DefineField creates a field option constructor and extractor for the given field name. The constructor can be used to set a field value in error options, and the extractor can be used to retrieve the field value from errors.

NOTE: The identity of a field is determined by the returned constructor and extractor instances, not by the provided name string. This ensures that fields created by different calls to DefineField, even with the same name, are distinct and do not collide.

The name string is used as the key when an error's fields are serialized (e.g., to JSON). To avoid ambiguity in logs and other serialized representations, it is strongly recommended to use a unique name for each defined field.

Types

type DebugStacker

type DebugStacker interface {
	DebugStack() string
}

DebugStacker returns a string that resembles the output of debug.Stack(). This is useful for integrating with Google Cloud Error Reporting. See: https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.events/report#ReportedErrorEvent

NOTE: The goroutine ID and state may differ from the actual one.

type Definition

type Definition interface {
	error
	Factory

	// Kind returns the kind of this error definition.
	Kind() Kind
	// Is reports whether this definition matches the given error.
	Is(error) bool
	// Fields returns the fields associated with this definition.
	Fields() Fields
	// With creates a new Factory and applies options from context first (if any),
	// then the given opts. Later options override earlier ones.
	With(context.Context, ...Option) Factory
	// WithOptions creates a new Factory with the given options applied.
	// Later options override earlier ones.
	WithOptions(...Option) Factory
}

Definition represents an error definition with customizable options. It serves as a reusable template for creating structured errors with a specific kind, fields, and behavior (e.g., stack traces, formatting, serialization).

Definition can be used as a sentinel error for identity checks with errors.Is, similar to standard errors like io.EOF. It can also be configured with additional options using With or WithOptions to create a Factory for generating errors with context-specific or request-scoped data.

func Define

func Define(kind Kind, opts ...Option) Definition

Define creates a new error definition with the specified kind and options.

NOTE: The error identity check performed by errors.Is relies on an internal identifier associated with each Definition instance, not on the string value of Kind.

While this means that using the same Kind string for different definitions will not cause incorrect identity checks, it is strongly recommended to use a unique Kind value across your application to prevent confusion in logs and monitoring tools.

type Details added in v0.2.1

type Details map[string]any

Details represents a map of diagnostic details that can be attached to an error.

func (Details) Key added in v0.3.1

func (d Details) Key() FieldKey

Key returns the field key for Details.

type Error

type Error interface {
	error

	// Kind returns the type of this error.
	Kind() Kind
	// Fields returns the structured fields associated with this error.
	Fields() Fields
	// Stack returns the stack trace where this error was created.
	Stack() Stack
	// Unwrap returns the errors that this error wraps.
	Unwrap() []error
	// UnwrapTree returns all causes as a tree structure.
	// This method includes cycle detection: when a circular reference is detected,
	// the node that would create the cycle is excluded, ensuring the result remains acyclic.
	// While circular references are rare in practice, this check serves as a defensive
	// programming measure.
	UnwrapTree() Nodes
}

Error extends the built-in error interface with additional functionality for structured error handling including kinds, fields, and stack traces.

Error instances are created from a Definition and remain immutable after creation. They provide rich context through Kind (error classification), Fields (structured data), and Stack (call stack information), while maintaining compatibility with standard Go error handling via errors.Is and errors.As.

Error chains are supported through Unwrap() for standard error unwrapping, and UnwrapTree() for accessing the full error tree with cycle detection.

type Factory added in v0.6.0

type Factory interface {
	// New creates a new error with the given message using this definition.
	New(msg string) error
	// Errorf creates a new error with a formatted message using this definition.
	Errorf(format string, args ...any) error
	// Wrap wraps an existing error using this definition.
	// Returns nil if cause is nil.
	Wrap(cause error) error
	// Wrapf wraps an existing error with a formatted message using this definition.
	// Returns nil if cause is nil.
	Wrapf(cause error, format string, args ...any) error
	// Join creates a new error by joining multiple errors using this definition.
	// Returns nil if all causes are nil.
	Join(causes ...error) error
	// Recover executes the given function and recovers from any panic that occurs within it.
	// If a panic occurs, it wraps the panic as an error using this definition and returns it.
	// If no panic occurs, it returns the function's return value as is.
	// The resulting error implements PanicError interface to preserve the original panic value.
	Recover(fn func() error) error
}

Factory is an interface for creating errors from a configured Definition. It provides only error creation methods, preventing misuse such as identity comparison (errors.Is) or further configuration (With/WithOptions).

Factory instances are typically created by Definition.With or Definition.WithOptions methods, and are intended to be used immediately for error creation rather than stored as sentinel values.

type FieldConstructor added in v0.2.5

type FieldConstructor[T any] func(value T) Option

FieldConstructor creates an Option that sets a field value.

func (FieldConstructor[T]) Key added in v0.3.1

func (f FieldConstructor[T]) Key() FieldKey

Key returns the key associated with this constructor.

func (FieldConstructor[T]) WithContextFunc added in v0.2.5

func (f FieldConstructor[T]) WithContextFunc(fn func(ctx context.Context) T) FieldConstructor[context.Context]

WithContextFunc creates a field option constructor that sets a value using a function that takes a context.

func (FieldConstructor[T]) WithErrorFunc added in v0.2.5

func (f FieldConstructor[T]) WithErrorFunc(fn func(err error) T) FieldConstructor[error]

WithErrorFunc creates a field option constructor that sets a value using a function that takes an error.

func (FieldConstructor[T]) WithHTTPRequestFunc added in v0.2.5

func (f FieldConstructor[T]) WithHTTPRequestFunc(fn func(r *http.Request) T) FieldConstructor[*http.Request]

WithHTTPRequestFunc creates a field option constructor that sets a value using a function that takes an HTTP request.

func (FieldConstructor[T]) WithValue added in v0.2.5

func (f FieldConstructor[T]) WithValue(value T) FieldConstructorNoArgs[T]

WithValue creates a field option constructor that sets a specific value.

func (FieldConstructor[T]) WithValueFunc added in v0.2.5

func (f FieldConstructor[T]) WithValueFunc(fn func() T) FieldConstructorNoArgs[T]

WithValueFunc creates a field option constructor that sets a value using a function.

type FieldConstructorNoArgs added in v0.2.5

type FieldConstructorNoArgs[T any] func() Option

FieldConstructorNoArgs creates an Option with a default value when called with no arguments.

func (FieldConstructorNoArgs[T]) Key added in v0.3.1

func (f FieldConstructorNoArgs[T]) Key() FieldKey

Key returns the key associated with this constructor.

type FieldExtractor added in v0.2.5

type FieldExtractor[T any] func(err error) (T, bool)

FieldExtractor extracts a field value from an error.

func (FieldExtractor[T]) OrDefault added in v0.2.5

func (f FieldExtractor[T]) OrDefault(err error, value T) T

OrDefault extracts the field value from the error, returning a default value if not found.

func (FieldExtractor[T]) OrFallback added in v0.2.5

func (f FieldExtractor[T]) OrFallback(err error, fn func(err error) T) T

OrFallback extracts the field value from the error, calling a function to obtain a value if not found.

func (FieldExtractor[T]) OrZero added in v0.2.5

func (f FieldExtractor[T]) OrZero(err error) T

OrZero extracts the field value from the error, returning the zero value if not found.

func (FieldExtractor[T]) WithDefault added in v0.2.5

func (f FieldExtractor[T]) WithDefault(value T) FieldExtractorSingleReturn[T]

WithDefault creates a field extractor that returns a default value if the field is not found.

func (FieldExtractor[T]) WithFallback added in v0.2.5

func (f FieldExtractor[T]) WithFallback(fn func(err error) T) FieldExtractorSingleReturn[T]

WithFallback creates a field extractor that calls a function to obtain a value if the field is not found.

func (FieldExtractor[T]) WithZero added in v0.2.5

func (f FieldExtractor[T]) WithZero() FieldExtractorSingleReturn[T]

WithZero creates a field extractor that returns only the value, ignoring the boolean.

type FieldExtractorSingleReturn added in v0.2.5

type FieldExtractorSingleReturn[T any] func(err error) T

FieldExtractorSingleReturn extracts a field value from an error, returning only the value.

type FieldKey

type FieldKey interface {
	fmt.Stringer
	// From creates a new FieldValue with the same type from the given value.
	NewValue(value any) (FieldValue, bool)
	// ZeroValue returns a FieldValue representing the zero value for the key's type.
	ZeroValue() FieldValue
}

FieldKey represents a key for structured error fields.

type FieldValue added in v0.2.0

type FieldValue interface {
	// Value returns the underlying value.
	Value() any
	// Equal checks if the value is equal to another value.
	Equal(other any) bool
}

FieldValue represents a value for structured error fields.

type Fields

type Fields interface {
	// Get retrieves the value associated with the given key.
	Get(key FieldKey) (FieldValue, bool)
	// FindKeys finds all keys that match the given name.
	FindKeys(name string) []FieldKey
	// All returns an iterator over all key-value pairs sorted by insertion order.
	All() iter.Seq2[FieldKey, FieldValue]
	// Len returns the number of fields.
	Len() int
	// IsZero checks if there are no fields.
	IsZero() bool
}

Fields represents a collection of structured error fields.

func FieldsFrom added in v0.7.0

func FieldsFrom(err error) (Fields, bool)

FieldsFrom extracts the Fields from an error. It returns the Fields and true if the error implements the Fields() method and the Fields are non-empty. Otherwise, it returns nil and false.

type Frame

type Frame struct {
	Func string `json:"func"`
	File string `json:"file"`
	Line int    `json:"line"`
}

Frame represents a single frame in a stack trace.

func (Frame) LogValue added in v0.2.5

func (f Frame) LogValue() slog.Value

type Kind

type Kind string

Kind is a human-readable string that represents the type of an error. It is primarily used for classification and identification in structured logs, metrics, and API responses.

func KindFrom added in v0.7.0

func KindFrom(err error) (Kind, bool)

KindFrom extracts the Kind from an error. It returns the Kind and true if the error implements the Kind() method. Otherwise, it returns an empty Kind and false.

type Node added in v0.5.5

type Node struct {
	// Error is the error at this node.
	Error error
	// Causes are the nested causes of this error.
	Causes Nodes
	// IsCyclic indicates whether this node is part of a cycle in the tree.
	IsCyclic bool
}

Node represents a node in the cause tree.

func (*Node) LogValue added in v0.5.5

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

LogValue implements slog.LogValuer for Node.

func (*Node) MarshalJSON added in v0.5.5

func (n *Node) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for Node.

type Nodes added in v0.5.5

type Nodes []*Node

Nodes is a slice of error nodes representing an error tree structure.

func UnwrapTreeFrom added in v0.7.0

func UnwrapTreeFrom(err error) (Nodes, bool)

UnwrapTreeFrom extracts the error cause tree from an error. It returns the Nodes and true if the error implements the UnwrapTree() method and the returned Nodes are non-empty. Otherwise, it returns nil and false.

func (Nodes) HasCycle added in v0.5.5

func (ns Nodes) HasCycle() bool

HasCycle returns true if any node in the error tree contains a cycle.

func (Nodes) Walk added in v0.5.5

func (ns Nodes) Walk() iter.Seq2[int, *Node]

Walk returns an iterator that traverses the error tree in depth-first order. The iterator yields pairs of depth (int) and node (*Node) for each error in the tree.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option represents a configuration option that can be applied to error definitions.

func Formatter

func Formatter(f func(err Error, s fmt.State, verb rune)) Option

Formatter overrides the default `fmt.Formatter` behavior.

func JSONMarshaler

func JSONMarshaler(f func(err Error) ([]byte, error)) Option

JSONMarshaler overrides the default `json.Marshaler` behavior.

func LogValuer added in v0.1.8

func LogValuer(f func(err Error) slog.Value) Option

LogValuer overrides the default `slog.LogValuer` behavior.

func NoTrace

func NoTrace() Option

NoTrace disables stack trace collection for the error.

func StackDepth added in v0.1.8

func StackDepth(depth int) Option

StackDepth sets the depth of the stack trace capture (default: 32).

func StackSkip

func StackSkip(skip int) Option

StackSkip skips a specified number of frames during stack capture.

func StackSource added in v0.8.0

func StackSource(around, depth int) Option

StackSource enables source code display for stack traces. It shows around lines before and after each stack frame in %+v output. The depth parameter controls how many frames to show source for:

  • depth > 0: show source for the first depth frames
  • depth == -1: show source for all frames
  • depth == 0: no source display (no-op)

type PanicError added in v0.1.4

type PanicError interface {
	error

	// PanicValue returns the value recovered from the panic.
	PanicValue() any
	// Unwrap returns the underlying error if the panic value is an error.
	Unwrap() error
}

type Presenter added in v0.6.0

type Presenter interface {
	// FormatError formats the error using this definition's custom formatter if set,
	// otherwise uses the default format implementation.
	FormatError(err Error, s fmt.State, verb rune)
	// MarshalErrorJSON marshals the error to JSON using this definition's custom marshaler if set,
	// otherwise uses the default JSON structure.
	MarshalErrorJSON(err Error) ([]byte, error)
	// MakeErrorLogValue returns a slog.Value representing the error using this definition's custom log valuer if set,
	// otherwise uses the default log structure.
	MakeErrorLogValue(err Error) slog.Value
	// BuildCauseTree returns all causes as a tree structure.
	// This method includes cycle detection: when a circular reference is detected,
	// the node that would create the cycle is excluded, ensuring the result remains acyclic.
	// While circular references are rare in practice, this check serves as a defensive
	// programming measure.
	BuildCauseTree(err Error) Nodes
}

Presenter provides error presentation methods for formatting, serialization, and logging. These methods are primarily used internally by Error implementations and are not typically called directly by application code.

type Redacted added in v0.2.4

type Redacted[T any] struct {
	// contains filtered or unexported fields
}

Redacted[T] wraps a value so it always renders as "[REDACTED]" when printed, marshaled, or logged (fmt, json, slog), while still allowing access to the original value via Value(). Use this to prevent accidental exposure of sensitive data in logs/output.

func Redact added in v0.2.4

func Redact[T any](value T) Redacted[T]

Redact wraps value in a Redacted[T], which always renders as "[REDACTED]" when printed, marshaled, or logged (fmt, json, slog), while still allowing access to the original value via Value(). Use this to prevent accidental exposure of sensitive data in logs/output.

func (Redacted[T]) Format added in v0.2.4

func (r Redacted[T]) Format(s fmt.State, verb rune)

Format implements fmt.Formatter, always rendering as "[REDACTED]" regardless of the format verb.

func (Redacted[T]) GoString added in v0.4.0

func (r Redacted[T]) GoString() string

GoString implements fmt.GoStringer, always returning "[REDACTED]" for %#v format.

func (Redacted[T]) IsRedacted added in v0.7.1

func (r Redacted[T]) IsRedacted() bool

IsRedacted returns true. This method allows identifying Redacted[T] values via interface without knowing the type parameter T.

func (Redacted[T]) LogValue added in v0.2.4

func (r Redacted[T]) LogValue() slog.Value

LogValue implements slog.LogValuer, always logging as "[REDACTED]".

func (Redacted[T]) MarshalBinary added in v0.4.0

func (r Redacted[T]) MarshalBinary() ([]byte, error)

MarshalBinary implements encoding.BinaryMarshaler, always returning "[REDACTED]" as bytes.

func (Redacted[T]) MarshalJSON added in v0.2.4

func (r Redacted[T]) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler, always marshaling as "[REDACTED]".

func (Redacted[T]) MarshalText added in v0.2.4

func (r Redacted[T]) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler, always returning "[REDACTED]" as text.

func (Redacted[T]) String added in v0.2.4

func (r Redacted[T]) String() string

String implements fmt.Stringer, always returning "[REDACTED]".

func (Redacted[T]) Value added in v0.2.4

func (r Redacted[T]) Value() T

Value returns the original wrapped value.

type Stack

type Stack interface {
	// Frames returns the stack trace as structured frame information.
	Frames() []Frame
	// HeadFrame returns the top frame of the stack trace.
	HeadFrame() (Frame, bool)
	// FramesAndSource returns an iterator that yields frames and their source code snippets.
	// Source code will be empty string if not available or if the frame exceeds the configured depth.
	FramesAndSource() iter.Seq2[Frame, string]
	// Len returns the number of frames in the stack trace.
	Len() int
	// IsZero returns true if the stack trace is empty.
	IsZero() bool
}

Stack represents a stack trace captured when an error was created.

func StackFrom added in v0.7.0

func StackFrom(err error) (Stack, bool)

StackFrom extracts the Stack from an error. It returns the Stack and true if the error implements the Stack() method and the Stack is non-empty. Otherwise, it returns nil and false.

type StackTracer added in v0.7.2

type StackTracer interface {
	StackTrace() []uintptr
}

StackTracer provides access to the low-level stack trace representation as program counters. This interface is intended for specialized use cases such as error reporting tools, tracing systems, or custom stack trace processing.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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