goerr

package module
v2.0.1 Latest Latest
Warning

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

Go to latest
Published: Mar 7, 2026 License: BSD-2-Clause Imports: 10 Imported by: 8

README

goerr test gosec package scan Go Reference

Enhanced error handling for Go with stack traces, contextual values, and structured logging

Overview

goerr is a powerful error handling library for Go that enhances errors with rich contextual information. It provides stack traces, contextual variables, error categorization, and seamless integration with structured logging - all while maintaining full compatibility with Go's standard error handling patterns.

Key Features

  • Stack Traces: Automatic capture with github.com/pkg/errors compatibility
  • Contextual Data: Attach key-value pairs and tags to errors
  • Type Safety: Compile-time type checking for error context
  • Multiple Errors: Aggregate errors with goerr.Errors
  • Structured Logging: Native slog integration

Installation

go get github.com/gaebalai/goerr/v2

Quick Start

package main

import (
    "log"
    "github.com/gaebalai/goerr/v2"
)

func main() {
    if err := processFile("data.txt"); err != nil {
        // Print error with stack trace
        log.Fatalf("%+v", err)
    }
}

func processFile(filename string) error {
    _, err := readFile(filename)
    if err != nil {
        return goerr.Wrap(err, "failed to process file",
            goerr.Value("filename", filename))
    }
    return nil
}

func readFile(filename string) error {
    // Simulate error
    return goerr.New("file not found")
}

Core Features

Error Creation and Wrapping

Create new errors or wrap existing ones with additional context:

// Create a new error
err := goerr.New("validation failed")

// Wrap an existing error
if err := someFunc(); err != nil {
    return goerr.Wrap(err, "operation failed")
}

// Add contextual information without changing the original error
err = goerr.With(err,
    goerr.Value("user_id", userID),
    goerr.Value("timestamp", time.Now()))

// With preserves stacktrace for goerr.Error, wraps standard errors
originalErr := goerr.New("original error")
enhanced := goerr.With(originalErr, goerr.Value("context", "added"))
// enhanced has same stacktrace as originalErr, originalErr unchanged

// Key precedence: later values override earlier ones
err := goerr.New("error", goerr.Value("key", "first"))
enhanced := goerr.With(err,
    goerr.Value("key", "second"),  // Overrides "first"
    goerr.Value("key", "final"))   // Overrides "second"
// enhanced.Values()["key"] == "final"

// Extract goerr.Error from any error
if goErr := goerr.Unwrap(err); goErr != nil {
    values := goErr.Values() // Get all contextual values
}
Multiple Error Handling

Aggregate multiple errors with goerr.Errors:

// Collect errors during processing
var errs *goerr.Errors
for _, item := range items {
    if err := processItem(item); err != nil {
        errs = goerr.Append(errs, err)  // nil-safe
    }
}

// Return only if errors occurred
return errs.ErrorOrNil()  // nil if no errors

// Join errors directly
combined := goerr.Join(err1, err2, err3)

// All errors displayed together
fmt.Printf("%v", combined)
// Output: error1\nerror2\nerror3

// Works with standard library
if errors.Is(combined, err1) { /* true */ }
Contextual Data

String-based Values

Attach arbitrary key-value pairs to errors:

func validateUser(userID string, age int) error {
    if age < 18 {
        return goerr.New("user too young",
            goerr.V("user_id", userID),  // V is alias for Value
            goerr.V("age", age),
            goerr.V("required_age", 18))
    }
    return nil
}

// Extract values from error
if err := validateUser("user123", 16); err != nil {
    if goErr := goerr.Unwrap(err); goErr != nil {
        for key, value := range goErr.Values() {
            log.Printf("%s: %v", key, value)
        }
    }
}

Type-safe Values

Use compile-time type checking for error context:

// Define typed keys (typically at package level)
var (
    UserIDKey    = goerr.NewTypedKey[string]("user_id")
    RequestIDKey = goerr.NewTypedKey[int64]("request_id")
    ConfigKey    = goerr.NewTypedKey[*Config]("config")
)

// Use typed values - compile-time type checking
err := goerr.New("validation failed",
    goerr.TV(UserIDKey, "user123"),      // Must be string
    goerr.TV(RequestIDKey, int64(42)),   // Must be int64
    goerr.TV(ConfigKey, currentConfig))  // Must be *Config

// Retrieve typed values - no type assertion needed
if userID, ok := goerr.GetTypedValue(err, UserIDKey); ok {
    // userID is string type, guaranteed
    fmt.Printf("User: %s\n", userID)
}

Error Tags

Categorize errors for different handling strategies:

// Define tags
var (
    ErrTagNotFound   = goerr.NewTag("not_found")
    ErrTagValidation = goerr.NewTag("validation")
    ErrTagExternal   = goerr.NewTag("external")
)

// Tag errors
if user == nil {
    return goerr.New("user not found",
        goerr.T(ErrTagNotFound))  // T is alias for Tag
}

// Handle errors based on tags
if goerr.HasTag(err, ErrTagNotFound) {
    w.WriteHeader(http.StatusNotFound)
} else if goerr.HasTag(err, ErrTagValidation) {
    w.WriteHeader(http.StatusBadRequest)
} else {
    w.WriteHeader(http.StatusInternalServerError)
}
Stack Traces

Stack traces are automatically captured and compatible with github.com/pkg/errors:

func doWork() error {
    return goerr.New("something went wrong")
}

func main() {
    if err := doWork(); err != nil {
        // Print with stack trace using %+v
        log.Printf("%+v", err)
        
        // Extract stack programmatically
        if goErr := goerr.Unwrap(err); goErr != nil {
            for _, frame := range goErr.Stacks() {
                log.Printf("  at %s:%d in %s", 
                    frame.File, frame.Line, frame.Func)
            }
        }
    }
}

// Remove current frame from stack (useful for helper functions)
func helperFunc() error {
    return goerr.New("error from helper").Unstack()
}

Advanced Features

Enhancing Errors with Context

The With function adds contextual information to errors without modifying the original:

// For goerr.Error: preserves existing stacktrace
originalErr := goerr.New("database connection failed")
enhanced := goerr.With(originalErr,
    goerr.Value("host", "db.example.com"),
    goerr.Value("port", 5432),
    goerr.Tag(ErrTagExternal))

// originalErr remains unchanged, enhanced has same stacktrace
fmt.Printf("Original unchanged: %v\n", originalErr.Values()) // empty
fmt.Printf("Enhanced: %v\n", enhanced.Values())              // has host, port

// For standard errors: wraps with new stacktrace
stdErr := errors.New("file not found")
enhanced2 := goerr.With(stdErr, goerr.Value("path", "/tmp/file.txt"))
// enhanced2 wraps stdErr with new stacktrace and context
Error Identification

Use IDs for flexible error comparison:

var (
    ErrInvalidInput = goerr.New("invalid input", goerr.ID("ERR_INVALID_INPUT"))
    ErrTimeout      = goerr.New("operation timeout", goerr.ID("ERR_TIMEOUT"))
)

func process() error {
    return goerr.Wrap(ErrInvalidInput, "validation failed",
        goerr.Value("field", "email"))
}

// Check error identity
if err := process(); err != nil {
    if errors.Is(err, ErrInvalidInput) {
        // Matches by ID, not pointer
        handleValidationError(err)
    }
}
Builder Pattern

Create multiple errors with shared context:

type Service struct {
    userID string
    reqID  string
}

func (s *Service) process() error {
    // Create builder with common context
    eb := goerr.NewBuilder(
        goerr.Value("user_id", s.userID),
        goerr.Value("request_id", s.reqID))
    
    // Use builder for multiple errors
    if err := s.validate(); err != nil {
        return eb.Wrap(err, "validation failed")
    }
    
    if err := s.save(); err != nil {
        return eb.Wrap(err, "save failed")
    }
    
    return nil
}
Structured Logging

Native integration with Go's slog package:

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

err := goerr.New("database error",
    goerr.Value("table", "users"),
    goerr.Value("operation", "insert"))

// Error implements slog.LogValuer
logger.Error("operation failed", slog.Any("error", err))

// Output (formatted):
// {
//   "level": "ERROR",
//   "msg": "operation failed",
//   "error": {
//     "message": "database error",
//     "values": {"table": "users", "operation": "insert"},
//     "stacktrace": [...]
//   }
// }
JSON Serialization

Export full error details as JSON:

err := goerr.New("validation error",
    goerr.Value("field", "email"),
    goerr.Tag(ValidationTag))

// Get JSON-serializable struct
printable := goerr.Unwrap(err).Printable()

// Or marshal directly
jsonData, _ := json.Marshal(err)

// Output includes message, stack trace, values, tags, and cause chain

Examples

See the examples directory for complete working examples:

  • Stack trace handling
  • Contextual variables
  • Multiple error aggregation
  • HTTP error responses
  • Sentry integration
  • Structured logging with slog
  • And more...

Migration Guide

See Migration Guide for migrating from:

  • github.com/pkg/errors
  • Standard library errors package
  • goerr v1 to v2

License

The 2-Clause BSD License. See LICENSE for more detail.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetTypedValue

func GetTypedValue[T any](err error, key TypedKey[T]) (T, bool)

GetTypedValue returns value associated with the typed key from the error. It searches through the error chain.

Usage:

key := goerr.NewTypedKey[string]("user_id")
if userID, ok := goerr.GetTypedValue(err, key); ok {
    fmt.Printf("User ID: %s", userID) // userID is guaranteed to be string
}

func HasTag

func HasTag(err error, tag tag) bool

HasTag returns true if the error has the tag.

func NewTag

func NewTag(value string) tag

NewTag creates a new Tag. The key will be empty.

Example
package main

import (
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	t1 := goerr.NewTag("DB error")
	err := goerr.New("error message", goerr.Tag(t1))

	if goErr := goerr.Unwrap(err); goErr != nil {
		if goErr.HasTag(t1) {
			fmt.Println("DB error")
		}
	}
}
Output:
DB error

func Tags

func Tags(err error) []string

Tags returns list of tags that is set by WithTags. All wrapped goerr.Error tags will be merged. Tags of wrapped error is overwritten by upper goerr.Error.

func TypedValues

func TypedValues(err error) map[string]any

TypedValues returns map of key and value that is set by TypedValue. All wrapped goerr.Error typed key and values will be merged. Key and values of wrapped error is overwritten by upper goerr.Error.

func Values

func Values(err error) map[string]any

Values returns map of key and value that is set by With. All wrapped goerr.Error key and values will be merged. Key and values of wrapped error is overwritten by upper goerr.Error.

Types

type Builder

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

Builder keeps a set of key-value pairs and can create a new error and wrap error with the key-value pairs.

Example

ExampleBuilder demonstrates using Builder pattern for creating errors with shared context. goerr.NewBuilder(options...) creates a builder, .With(options...) extends it, .New(msg) / .Wrap(err, msg) creates errors

package main

import (
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Create a builder with common context for a request
	builder := goerr.NewBuilder(
		goerr.V("service", "user-service"),
		goerr.V("request_id", "req-12345"),
	)

	// Create errors using the builder - shared context is automatically included
	err1 := builder.New("user not found")
	err2 := builder.Wrap(fmt.Errorf("connection refused"), "database unavailable")

	// All errors created by the builder include the shared context
	fmt.Println("Error 1:", err1.Error())
	fmt.Printf("Service: %s\n", goerr.Values(err1)["service"])

	fmt.Println("Error 2:", err2.Error())
	fmt.Printf("Request ID: %s\n", goerr.Values(err2)["request_id"])

	// Extend the builder with additional context
	extendedBuilder := builder.With(goerr.V("user_id", "user789"))
	err3 := extendedBuilder.New("permission denied")

	fmt.Println("Error 3:", err3.Error())
	fmt.Printf("User ID: %s\n", goerr.Values(err3)["user_id"])

}
Output:
Error 1: user not found
Service: user-service
Error 2: database unavailable: connection refused
Request ID: req-12345
Error 3: permission denied
User ID: user789

func NewBuilder

func NewBuilder(options ...Option) *Builder

NewBuilder creates a new Builder. A Builder is useful for creating multiple errors that share a common context, such as a request ID or service name, without repeatedly specifying the same options.

Usage:

builder := goerr.NewBuilder(goerr.V("service", "auth"), goerr.V("request_id", "req123"))
err1 := builder.New("user not found")
err2 := builder.Wrap(dbErr, "query failed")
// Both errors will include service and request_id context
Example
package main

import (
	"fmt"
	"io"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Create a builder with common context for a request.
	builder := goerr.NewBuilder(
		goerr.Value("service", "auth-service"),
		goerr.Value("request_id", "req-9876"),
	)

	// Use the builder to create errors.
	err1 := builder.New("user not found")
	err2 := builder.Wrap(io.EOF, "failed to read body")

	// The context from the builder is automatically included.
	fmt.Println(goerr.Values(err1)["service"])
	fmt.Println(goerr.Values(err2)["request_id"])

}
Output:
auth-service
req-9876

func (*Builder) New

func (x *Builder) New(msg string, options ...Option) *Error

New creates a new error with message

Usage:

builder := goerr.NewBuilder(goerr.V("service", "auth"))
err := builder.New("authentication failed") // includes service context

func (*Builder) With

func (x *Builder) With(options ...Option) *Builder

With copies the current Builder and adds a new key-value pair.

Usage:

baseBuilder := goerr.NewBuilder(goerr.V("service", "auth"))
userBuilder := baseBuilder.With(goerr.V("user_id", "user123"))
err := userBuilder.New("access denied") // includes both service and user_id

func (*Builder) Wrap

func (x *Builder) Wrap(cause error, msg string, options ...Option) *Error

Wrap creates a new Error with caused error and add message.

Usage:

builder := goerr.NewBuilder(goerr.V("service", "auth"))
err := builder.Wrap(dbErr, "database query failed") // wraps dbErr with service context

type Error

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

Error is error interface for deepalert to handle related variables

func New

func New(msg string, options ...Option) *Error

New creates a new error with message

func Unwrap

func Unwrap(err error) *Error

Unwrap returns unwrapped goerr.Error from err by errors.As. If no goerr.Error, returns nil NOTE: Do not receive error interface. It causes typed-nil problem.

var err error = goerr.New("error")
if err != nil { // always true

func With

func With(err error, options ...Option) *Error

With adds contextual information to an error without modifying the original. It is useful when you want to enrich an error with more context in a middleware or a higher-level function without altering the original error value.

If err is a *goerr.Error, it creates a new *Error that preserves the original stacktrace and adds the new options. If err is a standard error, it wraps the error in a new *goerr.Error with a new stacktrace and adds the options.

Usage:

originalErr := goerr.New("validation failed")
enrichedErr := goerr.With(originalErr, goerr.V("user_id", "user123"))
// originalErr is unchanged, enrichedErr has additional context
Example

ExampleWith demonstrates how to add context to errors without modifying the original error. goerr.With(err, options...) adds context without modifying the original error

package main

import (
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Create an original error
	originalErr := goerr.New("database connection failed")

	// Add context without modifying the original error
	// Original error remains unchanged, new error with additional context is returned
	enrichedErr := goerr.With(originalErr,
		goerr.V("user_id", "user123"),
		goerr.V("component", "auth-service"),
	)

	fmt.Println("Original:", originalErr.Error())
	fmt.Println("Enriched:", enrichedErr.Error())

	// Verify original error has no additional context
	originalValues := goerr.Values(originalErr)
	fmt.Printf("Original has %d values\n", len(originalValues))

	// Verify enriched error has the context
	enrichedValues := goerr.Values(enrichedErr)
	fmt.Printf("Enriched has %d values\n", len(enrichedValues))
	fmt.Printf("User ID: %s\n", enrichedValues["user_id"])

}
Output:
Original: database connection failed
Enriched: database connection failed
Original has 0 values
Enriched has 2 values
User ID: user123

func Wrap

func Wrap(cause error, msg string, options ...Option) *Error

Wrap creates a new Error and add message.

Usage:

baseErr := fmt.Errorf("connection failed")
err := goerr.Wrap(baseErr, "database operation failed",
    goerr.V("host", "localhost"), goerr.V("port", 5432))
// Result: "database operation failed: connection failed" with context
Example

ExampleWrap demonstrates wrapping errors with additional context. goerr.Wrap(cause, message, options...) wraps an existing error and adds additional information

package main

import (
	"errors"
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Create a base error
	baseErr := fmt.Errorf("connection timeout")

	// Wrap the error with additional context
	wrappedErr := goerr.Wrap(baseErr, "failed to connect to database",
		goerr.V("host", "localhost"),
		goerr.V("port", 5432),
		goerr.V("timeout", "30s"),
	)

	fmt.Println("Error:", wrappedErr.Error())

	// Access the wrapped values
	values := goerr.Values(wrappedErr)
	fmt.Printf("Host: %s\n", values["host"])
	fmt.Printf("Port: %v\n", values["port"])

	// Check if the original error is preserved
	if errors.Is(wrappedErr, baseErr) {
		fmt.Println("Original error is preserved")
	}

}
Output:
Error: failed to connect to database: connection timeout
Host: localhost
Port: 5432
Original error is preserved

func (*Error) Error

func (x *Error) Error() string

Error returns error message for error interface

func (*Error) Format

func (x *Error) Format(s fmt.State, verb rune)

Format returns: - %v, %s, %q: formatted message - %+v: formatted message with stack trace

func (*Error) HasTag

func (x *Error) HasTag(tag tag) bool

HasTag returns true if the error has the tag.

func (*Error) Is

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

Is returns true if the target error matches this error. It's for errors.Is. If both errors have IDs set via goerr.ID() option (non-empty), they are compared by ID. Otherwise, pointer equality is used for comparison. Empty ID values (default) are not used for comparison.

func (*Error) LogValue

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

LogValue returns slog.Value for structured logging. It's implementation of slog.LogValuer. https://pkg.go.dev/log/slog#LogValuer

Usage:

err := goerr.New("operation failed", goerr.V("user_id", "user123"))
logger.Error("request failed", slog.Any("error", err))
// Automatically outputs structured log with error details, stack trace, and values
Example

ExampleError_LogValue demonstrates structured logging with slog.LogValuer interface. goerr.Error automatically implements slog.LogValuer for structured logging with slog.Any("error", err)

package main

import (
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Create an error with context
	err := goerr.New("operation failed",
		goerr.V("user_id", "user123"),
		goerr.V("operation", "delete_account"),
	)

	// The error implements slog.LogValuer interface automatically
	// When err is passed to a slog logger (e.g. via slog.Any("error", err)), its LogValue() method is called automatically.
	// We can inspect the returned slog.Value for demonstration.
	logValue := err.LogValue()

	// Extract some information from the log value for demonstration
	attrs := logValue.Group()
	var message, operation string
	for _, attr := range attrs {
		switch attr.Key {
		case "message":
			message = attr.Value.String()
		case "values":
			valueGroup := attr.Value.Group()
			for _, valueAttr := range valueGroup {
				if valueAttr.Key == "operation" {
					operation = valueAttr.Value.String()
				}
			}
		}
	}

	fmt.Printf("Message: %s\n", message)
	fmt.Printf("Operation: %s\n", operation)
	fmt.Println("Structured logging ready")

}
Output:
Message: operation failed
Operation: delete_account
Structured logging ready

func (*Error) MarshalJSON

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

MarshalJSON implements json.Marshaler interface for Error type. It provides comprehensive JSON serialization including message, ID, stack trace, values, tags, and cause information.

func (*Error) Printable

func (x *Error) Printable() *Printable

Printable returns printable object

func (*Error) StackTrace

func (x *Error) StackTrace() StackTrace

StackTrace returns stack trace that is compatible with pkg/errors

func (*Error) Stacks

func (x *Error) Stacks() []*Stack

Stacks returns stack trace array generated by pkg/errors

func (*Error) Tags

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

Tags returns list of tags that is set by WithTags. All wrapped goerr.Error tags will be merged. Tags of wrapped error is overwritten by upper goerr.Error.

func (*Error) TypedValues

func (x *Error) TypedValues() map[string]any

TypedValues returns map of key and value that is set by TypedValue. All wrapped goerr.Error typed key and values will be merged. Key and values of wrapped error is overwritten by upper goerr.Error.

func (*Error) Unstack

func (x *Error) Unstack() *Error

Unstack trims stack trace by 1. It can be used for internal helper or utility functions.

func (*Error) UnstackN

func (x *Error) UnstackN(n int) *Error

UnstackN trims stack trace by n. It can be used for internal helper or utility functions.

func (*Error) Unwrap

func (x *Error) Unwrap() error

Unwrap returns *fundamental of github.com/pkg/errors

func (*Error) Values

func (x *Error) Values() map[string]any

Values returns map of key and value that is set by With. All wrapped goerr.Error key and values will be merged. Key and values of wrapped error is overwritten by upper goerr.Error.

func (*Error) WithTags deprecated

func (x *Error) WithTags(tags ...tag) *Error

WithTags adds tags to the error. The tags are used to categorize errors.

Deprecated: Use the goerr.Tag option with goerr.New() or goerr.Wrap() instead.

func (*Error) Wrap

func (x *Error) Wrap(cause error, options ...Option) *Error

Wrap creates a new Error and copy message and id to new one.

type Errors

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

Errors represents multiple errors as a single error

func Append

func Append(base *Errors, errs ...error) *Errors

Append adds errors to existing Errors (inspired by go-multierror) If base is nil, creates a new Errors. Flattens nested Errors.

func AsErrors

func AsErrors(err error) *Errors

AsErrors extracts goerr.Errors from err by errors.As. If no goerr.Errors, returns nil Complementary to goerr.Unwrap() which only extracts goerr.Error

func Join

func Join(errs ...error) *Errors

Join creates a new Errors by combining multiple errors

func (*Errors) As

func (x *Errors) As(target any) bool

As finds the first error that matches target type

func (*Errors) Error

func (x *Errors) Error() string

Error implements error interface

func (*Errors) ErrorOrNil

func (x *Errors) ErrorOrNil() error

ErrorOrNil returns the error if non-empty, nil otherwise (inspired by go-multierror) Nil-safe: (*goerr.Errors)(nil).ErrorOrNil() returns nil

func (*Errors) Errors

func (x *Errors) Errors() []error

Errors returns the slice of wrapped errors (inspired by go-multierror.WrappedErrors)

func (*Errors) Format

func (x *Errors) Format(s fmt.State, verb rune)

Format implements fmt.Formatter interface

func (*Errors) HasTag

func (x *Errors) HasTag(tag tag) bool

HasTag checks if any wrapped error has the specified tag

func (*Errors) Is

func (x *Errors) Is(target error) bool

Is checks if any wrapped error matches target

func (*Errors) IsEmpty

func (x *Errors) IsEmpty() bool

IsEmpty returns true if no errors are contained

func (*Errors) Len

func (x *Errors) Len() int

Len returns the number of wrapped errors

func (*Errors) LogValue

func (x *Errors) LogValue() slog.Value

LogValue implements slog.LogValuer interface for structured logging

func (*Errors) MarshalJSON

func (x *Errors) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler interface for Errors type

func (*Errors) Unwrap

func (x *Errors) Unwrap() []error

Unwrap returns all wrapped errors for Go 1.20+ multiple errors support

type ErrorsJSON

type ErrorsJSON struct {
	Errors []any `json:"errors"`
}

ErrorsJSON represents JSON structure for Errors

type Option

type Option func(*Error)

func ID

func ID(id string) Option

ID sets an error ID for Is() comparison. When an ID is set, errors.Is() will compare errors by their ID string instead of by pointer equality. This allows for creating sentinel-like errors that can be matched even if they are wrapped. An empty string ("") is treated as an invalid ID and will not be used for comparison.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	var ErrPermission = goerr.New("permission denied", goerr.ID("permission"))

	err := goerr.Wrap(ErrPermission, "failed to open file")

	if errors.Is(err, ErrPermission) {
		fmt.Println("Error is a permission error")
	}
}
Output:
Error is a permission error

func T

func T(t tag) Option

T is alias of Tag

func TV

func TV[T any](key TypedKey[T], value T) Option

TV is alias of TypedValue

func Tag

func Tag(t tag) Option

Tag sets tag to the error

func TypedValue

func TypedValue[T any](key TypedKey[T], value T) Option

TypedValue sets typed key and value to the error

Usage:

key := goerr.NewTypedKey[string]("user_id")
err := goerr.New("error", goerr.TypedValue(key, "user123"))
// or using alias: goerr.TV(key, "user123")
Example

ExampleTypedValue demonstrates type-safe error value handling. goerr.NewTypedKey[T](name) creates typed keys, goerr.TV(key, value) sets values, goerr.GetTypedValue(err, key) retrieves values

package main

import (
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Create typed keys for different types - keys provide compile-time type safety
	userIDKey := goerr.NewTypedKey[string]("user_id")
	retryCountKey := goerr.NewTypedKey[int]("retry_count")
	enabledKey := goerr.NewTypedKey[bool]("enabled")

	// Create error with typed values - compiler ensures type matching
	err := goerr.New("operation failed",
		goerr.TV(userIDKey, "user123"), // string value
		goerr.TV(retryCountKey, 3),     // int value
		goerr.TV(enabledKey, true),     // bool value
	)

	// Retrieve values with type safety - no type assertions needed
	if userID, ok := goerr.GetTypedValue(err, userIDKey); ok {
		fmt.Printf("User ID: %s\n", userID) // userID is guaranteed to be string
	}

	if retryCount, ok := goerr.GetTypedValue(err, retryCountKey); ok {
		fmt.Printf("Retry count: %d\n", retryCount) // retryCount is guaranteed to be int
	}

	if enabled, ok := goerr.GetTypedValue(err, enabledKey); ok {
		fmt.Printf("Enabled: %t\n", enabled) // enabled is guaranteed to be bool
	}

	// TypedValues() returns all typed values as map[string]any
	typedValues := goerr.TypedValues(err)
	fmt.Printf("Total typed values: %d\n", len(typedValues))

}
Output:
User ID: user123
Retry count: 3
Enabled: true
Total typed values: 3
Example (ErrorChain)

ExampleTypedValue_errorChain demonstrates typed value propagation through error chains.

package main

import (
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Create typed keys
	userIDKey := goerr.NewTypedKey[string]("user_id")
	requestIDKey := goerr.NewTypedKey[int64]("request_id")

	// Base error with user ID
	baseErr := goerr.New("validation failed",
		goerr.TV(userIDKey, "user456"),
	)

	// Wrapped error adds request ID - values propagate through the chain
	wrappedErr := goerr.Wrap(baseErr, "request processing failed",
		goerr.TV(requestIDKey, int64(789)),
	)

	// Both values are accessible from the wrapped error
	if userID, ok := goerr.GetTypedValue(wrappedErr, userIDKey); ok {
		fmt.Printf("User ID: %s\n", userID)
	}

	if requestID, ok := goerr.GetTypedValue(wrappedErr, requestIDKey); ok {
		fmt.Printf("Request ID: %d\n", requestID)
	}

}
Output:
User ID: user456
Request ID: 789

func V

func V(key string, value any) Option

V is alias of Value

func Value

func Value(key string, value any) Option

Value sets key and value to the error

type Printable

type Printable struct {
	Message     string         `json:"message"`
	ID          string         `json:"id"`
	StackTrace  []*Stack       `json:"stacktrace"`
	Cause       any            `json:"cause"`
	Values      map[string]any `json:"values"`
	TypedValues map[string]any `json:"typed_values"`
	Tags        []string       `json:"tags"`
}

type Stack

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

Stack represents function, file and line No of stack trace

type StackTrace

type StackTrace []frame

StackTrace is array of frame. It's exported for compatibility with github.com/pkg/errors

func (StackTrace) Format

func (st StackTrace) Format(s fmt.State, verb rune)

Format implements fmt.Formatter interface for StackTrace

type TypedKey

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

TypedKey represents a type-safe key for error values

func NewTypedKey

func NewTypedKey[T any](name string) TypedKey[T]

NewTypedKey creates a new type-safe key with the given name. This key can then be used with TV() and GetTypedValue() to attach and retrieve strongly-typed values from an error, providing compile-time safety.

Usage:

var UserIDKey = goerr.NewTypedKey[string]("user_id")
var CountKey = goerr.NewTypedKey[int]("count")
err := goerr.New("error", goerr.TV(UserIDKey, "user123"))
if userID, ok := goerr.GetTypedValue(err, UserIDKey); ok { ... }
Example
package main

import (
	"fmt"

	"github.com/gaebalai/goerr/v2"
)

func main() {
	// Define typed keys at the package level for reuse.
	var UserIDKey = goerr.NewTypedKey[string]("user_id")
	var RequestIDKey = goerr.NewTypedKey[int]("request_id")

	// Attach typed values when creating an error.
	err := goerr.New("request failed",
		goerr.TV(UserIDKey, "blue"),
		goerr.TV(RequestIDKey, 12345),
	)

	// Retrieve the typed value later.
	if userID, ok := goerr.GetTypedValue(err, UserIDKey); ok {
		// The retrieved value has the correct type (string), no assertion needed.
		fmt.Printf("User ID: %s\n", userID)
	}

	if reqID, ok := goerr.GetTypedValue(err, RequestIDKey); ok {
		fmt.Printf("Request ID: %d\n", reqID)
	}

}
Output:
User ID: blue
Request ID: 12345

func (TypedKey[T]) Name

func (k TypedKey[T]) Name() string

Name returns the name of the key

func (TypedKey[T]) String

func (k TypedKey[T]) String() string

String returns the string representation of the key for debugging

Directories

Path Synopsis
examples
basic command
builder command
errors_is command
logging command
tag command
typed_values command
variables command
with command

Jump to

Keyboard shortcuts

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