errs

package module
v0.0.0-...-222f906 Latest Latest
Warning

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

Go to latest
Published: Jan 13, 2026 License: MIT Imports: 13 Imported by: 18

README

go-errs

Go error wrapping with call-stack and function parameter capture.

Go Reference Go Report Card

Features

  • Automatic call stack capture - Every error wrapped with this package includes the full call stack at the point where the error was created or wrapped
  • Function parameter tracking - Capture and display function parameters in error messages for detailed debugging
  • Error wrapping compatible - Works seamlessly with errors.Is, errors.As, and errors.Unwrap
  • Helper utilities - Common patterns for NotFound errors, context errors, and panic recovery
  • Customizable formatting - Control how sensitive data appears in error messages
  • Go 1.23+ iterator support - Convert errors to iterators for functional programming patterns

Installation

go get github.com/domonda/go-errs

Quick Start

Basic Error Creation
import "github.com/domonda/go-errs"

func DoSomething() error {
    return errs.New("something went wrong")
}

// Error output includes call stack:
// something went wrong
// main.DoSomething
//     /path/to/file.go:123
Wrapping Errors with Function Parameters

The most powerful feature - automatically capture function parameters when errors occur:

func ProcessUser(userID string, age int) (err error) {
    defer errs.WrapWithFuncParams(&err, userID, age)

    if age < 0 {
        return errors.New("invalid age")
    }

    return database.UpdateUser(userID, age)
}

// When an error occurs, output includes:
// invalid age
// main.ProcessUser("user-123", -5)
//     /path/to/file.go:45
Error Wrapping
func LoadConfig(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        return errs.Errorf("failed to read config: %w", err)
    }
    // ... parse data
    return nil
}

Core Functions

Error Creation
  • errs.New(text) - Create a new error with call stack
  • errs.Errorf(format, ...args) - Format an error with call stack (supports %w for wrapping)
  • errs.Sentinel(text) - Create a const-able sentinel error
Error Wrapping with Parameters
  • errs.WrapWithFuncParams(&err, params...) - Most common: wrap error with function parameters
  • errs.WrapWith0FuncParams(&err) through errs.WrapWith10FuncParams(&err, p0, ...) - Optimized variants for specific parameter counts
  • errs.WrapWithFuncParamsSkip(skip, &err, params...) - Advanced: control stack frame skipping
Error Wrapping without Parameters
  • errs.WrapWithCallStack(err) - Wrap error with call stack only
  • errs.WrapWithCallStackSkip(skip, err) - Advanced: control stack frame skipping

Advanced Features

NotFound Errors

Standardized "not found" error handling compatible with sql.ErrNoRows and os.ErrNotExist:

var ErrUserNotFound = fmt.Errorf("user %w", errs.ErrNotFound)

func GetUser(id string) (*User, error) {
    user, err := db.Query("SELECT * FROM users WHERE id = ?", id)
    if errs.IsErrNotFound(err) {
        return nil, ErrUserNotFound
    }
    return user, err
}

// Check for any "not found" variant
if errs.IsErrNotFound(err) {
    // Handle not found case
}
Context Error Helpers
// Check if context is done
if errs.IsContextDone(ctx) {
    // Handle context done
}

// Check specific context errors
if errs.IsContextCanceled(ctx) {
    // Handle cancellation
}

if errs.IsContextDeadlineExceeded(ctx) {
    // Handle timeout
}

// Check if an error is context-related
if errs.IsContextError(err) {
    // Don't retry context errors
}
Panic Recovery
func RiskyOperation() (err error) {
    defer errs.RecoverPanicAsError(&err)

    // If this panics, it will be converted to an error
    return doSomethingRisky()
}

// With function parameters
func ProcessItem(id string) (err error) {
    defer errs.RecoverPanicAsErrorWithFuncParams(&err, id)

    return processItem(id) // May panic
}
Logging Control
type CustomError struct {
    error
}

func (e CustomError) ShouldLog() bool {
    return false // Don't log this error
}

// Check if error should be logged
if errs.ShouldLog(err) {
    logger.Error(err)
}

// Wrap error to prevent logging
err = errs.DontLog(err)
Protecting Sensitive Data
Using KeepSecret for Quick Protection

For simple cases where you want to prevent a parameter from appearing in logs, use errs.KeepSecret(param):

func Login(username string, password string) (err error) {
    defer errs.WrapWithFuncParams(&err, username, errs.KeepSecret(password))
    // Error messages will show: Login("admin", ***REDACTED***)
    return authenticate(username, password)
}

The Secret interface wraps a value and ensures it's never logged or printed:

  • String() returns "***REDACTED***"
  • Implements pretty.Printable to ensure redaction in error call stacks and pretty-printed output
  • Use secret.Secrect() to retrieve the actual value when needed

Important: Wrapping a parameter with errs.KeepSecret() is preferable to omitting it entirely from a defer errs.WrapWith* statement. When you run go-errs-wrap replace, omitted parameters will be added back, but KeepSecret-wrapped parameters are preserved in their wrapped form.

Custom Types with pretty.Printable

For custom types, implement pretty.Printable from github.com/domonda/go-pretty to control how they appear in error messages:

import "github.com/domonda/go-pretty"

type Password struct {
    value string
}

func (p Password) PrettyPrint(w io.Writer) {
    io.WriteString(w, "***REDACTED***")
}

func Login(username string, pwd Password) (err error) {
    defer errs.WrapWithFuncParams(&err, username, pwd)
    // Error messages will show: Login("admin", ***REDACTED***)
    return authenticate(username, pwd)
}

The go-pretty library automatically handles recursive formatting, so types implementing pretty.Printable will be properly formatted even when nested in other structs.

Customizing Call Stack Printing with PrintFuncFor

Default behavior: By default, the Printer checks if a type implements the pretty.Printable interface. If it does, the PrettyPrint(io.Writer) method is called to format the value. This is the recommended approach for types you own and control.

For advanced use cases where you need to customize formatting beyond implementing pretty.Printable, use Printer.WithPrintFuncFor(). This is useful when:

  • You want to format types you don't control (third-party types, stdlib types)
  • You need runtime-conditional formatting based on values or context
  • You want to adapt types that implement other interfaces (e.g., fmt.Stringer)
  • You need to apply global formatting rules based on struct tags or type patterns
  • You want to override or wrap the default Printable behavior
import (
    "fmt"
    "io"
    "reflect"
    "strings"
    "github.com/domonda/go-errs"
    "github.com/domonda/go-pretty"
)

func init() {
    // Create a custom printer with PrintFuncFor hook
    errs.Printer = errs.Printer.WithPrintFuncFor(func(v reflect.Value) pretty.PrintFunc {
        // Example 1: Mask strings containing sensitive keywords
        if v.Kind() == reflect.String {
            str := v.String()
            if strings.Contains(strings.ToLower(str), "password") ||
               strings.Contains(strings.ToLower(str), "token") ||
               strings.Contains(strings.ToLower(str), "apikey") {
                return func(w io.Writer) {
                    io.WriteString(w, "`***REDACTED***`")
                }
            }
        }

        // Example 2: Hide struct fields tagged with `secret:"true"`
        if v.Kind() == reflect.Struct {
            t := v.Type()
            for i := 0; i < t.NumField(); i++ {
                field := t.Field(i)
                if field.Tag.Get("secret") == "true" {
                    // Create a custom formatter that masks tagged fields
                    return func(w io.Writer) {
                        // Custom struct formatting logic here
                        io.WriteString(w, t.Name())
                        io.WriteString(w, "{***FIELDS_REDACTED***}")
                    }
                }
            }
        }

        // Example 3: Adapt types implementing fmt.Stringer
        stringer, ok := v.Interface().(fmt.Stringer)
        if !ok && v.CanAddr() {
            stringer, ok = v.Addr().Interface().(fmt.Stringer)
        }
        if ok {
            return func(w io.Writer) {
                io.WriteString(w, stringer.String())
            }
        }

        // Fall back to default Printable interface handling
        return pretty.PrintFuncForPrintable(v)
    })
}

// Now all error call stacks will use your custom formatting
func ProcessPayment(amount int, cardNumber string) (err error) {
    defer errs.WrapWithFuncParams(&err, amount, cardNumber)
    // If cardNumber contains "4111-1111-1111-1111", it will be shown as:
    // ProcessPayment(1000, `***REDACTED***`)
    return chargeCard(amount, cardNumber)
}

Key points:

  • Default behavior: If no PrintFuncFor is set, the Printer automatically checks if types implement pretty.Printable and calls their PrettyPrint() method
  • WithPrintFuncFor() returns a new Printer with the custom hook installed
  • The hook receives a reflect.Value and returns a pretty.PrintFunc (or nil to use default)
  • Important: In a PrintFuncFor function, always return pretty.PrintFuncForPrintable(v) as the fallback to preserve the default Printable interface checking behavior if you want to honor the Printable interface.
  • The hook applies to all values printed in error call stacks, including nested struct fields
  • This approach allows centralized control over sensitive data redaction without modifying type definitions
  • PrintFuncFor is evaluated for every value, allowing you to intercept and customize formatting before the Printable interface is checked

Comparison: Printable vs PrintFuncFor

// Approach 1: Implement pretty.Printable (recommended for types you own)
type APIKey string

func (k APIKey) PrettyPrint(w io.Writer) {
    io.WriteString(w, "***REDACTED***")
}

// Approach 2: Use PrintFuncFor (for types you don't control or global rules)
func init() {
    errs.Printer = errs.Printer.WithPrintFuncFor(func(v reflect.Value) pretty.PrintFunc {
        // Check for APIKey type from a third-party package
        if v.Type().String() == "thirdparty.APIKey" {
            return func(w io.Writer) {
                io.WriteString(w, "***REDACTED***")
            }
        }
        // Fall back to checking for Printable interface
        return pretty.PrintFuncForPrintable(v)
    })
}

Example with struct tags:

type User struct {
    ID       string
    Email    string
    Password string `secret:"true"`
    APIKey   string `secret:"true"`
}

// With the PrintFuncFor hook configured above,
// error messages will automatically redact fields tagged with secret:"true"

Unwrapping and Inspection

Finding Errors by Type
// Check if error chain contains a specific type
if errs.Has[*DatabaseError](err) {
    // Handle database error
}

// Get all errors of a specific type from the chain
dbErrors := errs.As[*DatabaseError](err)
for _, dbErr := range dbErrors {
    // Handle each database error
}

// Check error type without custom Is/As methods
if errs.Type[*DatabaseError](err) {
    // Error is or wraps a DatabaseError
}
Navigating Error Chains
// Get the root cause error
rootErr := errs.Root(err)

// Unwrap call stack information only
plainErr := errs.UnwrapCallStack(err)
Go 1.23+ Iterator Support
// Convert error to single-value iterator
for err := range errs.IterSeq(myErr) {
    // Process error
}

// Convert error to two-value iterator (value, error) pattern
for val, err := range errs.IterSeq2[MyType](myErr) {
    if err != nil {
        // Handle error
    }
}

Configuration

Customize Call Stack Display
// Change path prefix trimming
errs.TrimFilePathPrefix = "/go/src/"

// Adjust maximum stack depth
errs.MaxCallStackFrames = 64 // Default is 32
Limit Parameter Value Length

Control how long parameter values can be in error messages to prevent huge values from making errors unreadable:

// Default is 5000 bytes
errs.FormatParamMaxLen = 500

// Now long parameter values will be truncated
func ProcessDocument(content string) (err error) {
    defer errs.WrapWithFuncParams(&err, content)
    // If content is 2KB, error will show:
    // ProcessDocument("first 500 bytes…(TRUNCATED)")
    return parseDocument(content)
}

This is particularly useful when dealing with:

  • Large JSON or XML payloads
  • Binary data encoded as strings
  • Long database query results
  • Large data structures
Customize Function Call Formatting
// Replace the global formatter
errs.FormatFunctionCall = func(function string, params ...any) string {
    // Your custom formatting logic
    return fmt.Sprintf("%s(%v)", function, params)
}

Best Practices

1. Always use defer for WrapWithFuncParams
func MyFunc(id string) (err error) {
    defer errs.WrapWithFuncParams(&err, id)
    // Function body
}
2. Use optimized variants for better performance
// Instead of:
defer errs.WrapWithFuncParams(&err, p0, p1, p2)

// Use:
defer errs.WrapWith3FuncParams(&err, p0, p1, p2)
3. Protect sensitive data
type APIKey string

func (k APIKey) PrintForCallStack(w io.Writer) {
    io.WriteString(w, "***")
}
4. Use errs.Errorf for wrapping
// Good - preserves error chain
return errs.Errorf("failed to process user %s: %w", userID, err)

// Avoid - loses error chain
return errs.New(fmt.Sprintf("failed: %s", err))
5. Prefer errs package for all error creation
// Use errs.New instead of errors.New
return errs.New("something failed")

// Use errs.Errorf instead of fmt.Errorf
return errs.Errorf("failed: %w", err)

go-errs-wrap CLI Tool

The go-errs-wrap command-line tool helps manage defer errs.WrapWithFuncParams statements in your Go code.

Installation
go install github.com/domonda/go-errs/cmd/go-errs-wrap@latest
Commands
Command Description
remove Remove all defer errs.Wrap* or //#wrap-result-err lines
replace Replace existing defer errs.Wrap* or //#wrap-result-err with properly generated code
insert Insert defer errs.Wrap* at the first line of functions with named error results that don't already have one
Usage Examples

Insert wrap statements into all functions missing them:

# Process a single file
go-errs-wrap insert ./pkg/mypackage/file.go

# Process all Go files in a directory recursively
go-errs-wrap insert ./pkg/...

Replace outdated wrap statements with correct ones:

# Update parameters in existing wrap statements
go-errs-wrap replace ./pkg/mypackage/file.go

Remove all wrap statements:

go-errs-wrap remove ./pkg/...

Write changes to another output location:

# Output to a different location
go-errs-wrap insert -out ./output ./pkg/mypackage

# Show verbose progress
go-errs-wrap insert -verbose ./pkg/...
Options
Option Description
-out <path> Output to different location instead of modifying source
-minvariadic Use specialized WrapWithNFuncParams functions instead of variadic
-verbose Print progress information
-help Show help message
Example Transformation

Given this input file:

package example

func ProcessData(ctx context.Context, id string) (err error) {
    return doWork(ctx, id)
}

Running go-errs-wrap insert example.go produces:

package example

import "github.com/domonda/go-errs"

func ProcessData(ctx context.Context, id string) (err error) {
    defer errs.WrapWith2FuncParams(&err, ctx, id)

    return doWork(ctx, id)
}

The tool:

  • Inserts the defer errs.Wrap* statement at the first line of the function body
  • Adds an empty line after the defer statement
  • Automatically adds the required import
  • Uses the optimized function variant based on parameter count
  • Skips functions without named error results
  • Skips functions that already have a defer errs.Wrap* statement
  • Preserves errs.KeepSecret(param) wrapped parameters during replacement
Preserving Secrets During Replacement

When using go-errs-wrap replace, parameters wrapped with errs.KeepSecret() are preserved. This allows developers to mark sensitive parameters once and have that protection maintained across replacements:

// Before: developer manually wrapped password with KeepSecret
func Login(username, password string) (err error) {
    defer errs.WrapWithFuncParams(&err, username, errs.KeepSecret(password))
    return authenticate(username, password)
}

// After running: go-errs-wrap replace -minvariadic file.go
// The KeepSecret wrapping is preserved:
func Login(username, password string) (err error) {
    defer errs.WrapWith2FuncParams(&err, username, errs.KeepSecret(password))
    return authenticate(username, password)
}

This is preferable to omitting sensitive parameters entirely, as omitted parameters would be re-added by the tool.

Compatibility

  • Go version: Requires Go 1.13+ for error wrapping, Go 1.23+ for iterator support
  • Error handling: Fully compatible with errors.Is, errors.As, errors.Unwrap, and errors.Join
  • Testing: Use with testify or any testing framework

Performance

  • Zero-allocation error wrapping for functions with 0-10 parameters (using specialized functions)
  • Efficient call stack capture using runtime.Callers
  • Lazy error message formatting - only formats when Error() is called
  • Configurable stack depth to balance detail vs memory usage

Examples

See the examples directory and godoc for more examples.

License

MIT License - see LICENSE file for details.

Contributing

Contributions welcome! Please open an issue or submit a pull request.

  • go-pretty - Pretty printing used for error parameter formatting

Documentation

Overview

Package errs provides Go 1.13+ compatible error wrapping with call stacks and function parameters.

This package extends the standard library's error handling with:

  • Automatic call stack capture for error context
  • Function parameter tracking for detailed debugging
  • Helper functions for common error patterns (NotFound, context errors)
  • Panic recovery and conversion to errors
  • Iterator support for Go 1.23+

Basic usage:

func DoSomething(id string) (err error) {
    defer errs.WrapWithFuncParams(&err, id)
    // Your code here
    return someOperation(id)
}

See the documentation of individual functions for more examples.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// TrimFilePathPrefix will be trimmed from the
	// beginning of every call-stack file-path.
	// Defaults to $GOPATH/src/ of the build environment
	// or will be empty if go build gets called with -trimpath.
	TrimFilePathPrefix = filePathPrefix()

	// MaxCallStackFrames is the maximum number of frames to include in the call stack.
	MaxCallStackFrames = 32

	// Printer is the pretty.Printer used to format function parameters
	// in error call stacks. It can be configured to customize formatting,
	// mask secrets, or adapt types that don't implement pretty.Printable.
	//
	// Example - Masking sensitive data:
	//
	//	func init() {
	//	    errs.Printer.AsPrintable = func(v reflect.Value) (pretty.Printable, bool) {
	//	        if v.Kind() == reflect.String && strings.Contains(v.String(), "secret") {
	//	            return printableAdapter{
	//	                format: func(w io.Writer) {
	//	                    fmt.Fprint(w, "`***REDACTED***`")
	//	                },
	//	            }, true
	//	        }
	//	        return pretty.AsPrintable(v) // Use default
	//	    }
	//	}
	Printer = &pretty.DefaultPrinter

	// FormatParamMaxLen is the maximum length in bytes for a single formatted
	// parameter value in error call stacks. When a parameter's formatted
	// representation exceeds this length, it will be truncated and suffixed
	// with "…(TRUNCATED)".
	//
	// This prevents extremely large values (like long strings, big JSON blobs,
	// or large data structures) from making error messages unreadable.
	//
	// Default: 5000 bytes
	//
	// Example:
	//
	//	errs.FormatParamMaxLen = 100 // Limit to 100 bytes
	//
	//	func ProcessData(data string) (err error) {
	//	    defer errs.WrapWithFuncParams(&err, data)
	//	    // If data is 200 bytes, error will show:
	//	    // ProcessData("first 100 bytes of data…(TRUNCATED)")
	//	    return validateData(data)
	//	}
	FormatParamMaxLen = 5000
)

Configuration variables

View Source
var FormatFunctionCall = func(function string, params ...any) string {
	var b strings.Builder
	b.WriteString(function)
	b.WriteByte('(')
	for i, param := range params {
		if i > 0 {
			b.WriteString(", ")
		}
		var paramBuf bytes.Buffer
		Printer.Fprint(&paramBuf, param)
		if paramBuf.Len() > FormatParamMaxLen {
			bStr := paramBuf.Bytes()[:FormatParamMaxLen]

			b.Write(bytes.ToValidUTF8(bStr, nil))
			b.WriteString("…(TRUNCATED)")
		} else {
			b.Write(paramBuf.Bytes())
		}
	}
	b.WriteByte(')')
	return b.String()
}

FormatFunctionCall formats a function call in pseudo syntax using the Printer variable to format parameters. Types that implement pretty.Printable will use their PrettyPrint method, and this works recursively for nested structs.

FormatFunctionCall is a function variable that can be changed to globally configure the formatting of function calls.

Default Implementation:

The default implementation formats function calls as:

functionName(param1, param2, ...)

Each parameter is formatted using the Printer variable, which respects types implementing pretty.Printable. If a formatted parameter exceeds FormatParamMaxLen bytes, it will be truncated to ensure valid UTF-8 and suffixed with "…(TRUNCATED)".

Functions

func As

func As[T error](err error) []T

As returns all errors of type T in the wrapping tree of err.

This function is similar to errors.As but traverses the full tree using the interface methods:

Unwrap() error
Unwrap() []error

An error err matches the type T if the type assertion err.(T) holds, or if the error has a method As(any) bool such that err.As(target) returns true when target is a non-nil *T. In the latter case, the As method is responsible for setting target.

func AsError

func AsError(val any) error

AsError converts any type to an error without wrapping it. Nil values will be converted to a nil error.

func AsErrorWithDebugStack

func AsErrorWithDebugStack(val any) error

AsErrorWithDebugStack converts any type to an error and if not nil wraps it with debug.Stack() after a newline. Nil values will be converted to a nil error.

func DontLog

func DontLog(err error) error

DontLog wraps the passed error as LogDecisionMaker so that ShouldLog returns false. A nil error won't be wrapped but returned as nil.

func Errorf

func Errorf(format string, a ...any) error

Errorf wraps the result of fmt.Errorf with the current call stack.

If the format specifier includes a %w verb with an error operand, the returned error will implement an Unwrap method returning the operand. It is invalid to include more than one %w verb or to supply it with an operand that does not implement the error interface. The %w verb is otherwise a synonym for %v.

func Has

func Has[T error](err error) bool

Has is a shortcut for errors.As when the target error value is not needed.

func IsContextCanceled

func IsContextCanceled(ctx context.Context) bool

IsContextCanceled checks if the context Done channel is closed and if the context error unwraps to context.Canceled.

func IsContextDeadlineExceeded

func IsContextDeadlineExceeded(ctx context.Context) bool

IsContextDeadlineExceeded checks if the context Done channel is closed and if the context error unwraps to context.DeadlineExceeded.

func IsContextDone

func IsContextDone(ctx context.Context) bool

IsContextDone checks if the context Done channel is closed.

func IsContextError

func IsContextError(err error) bool

IsContextError returns true if err unwraps to context.Canceled or context.DeadlineExceeded.

func IsErrNotFound

func IsErrNotFound(err error) bool

IsErrNotFound returns true if the passed error unwraps to, or is ErrNotFound, sql.ErrNoRows, or os.ErrNotExist.

func IsOtherThanErrNotFound

func IsOtherThanErrNotFound(err error) bool

IsOtherThanErrNotFound returns true if the passed error is not nil and does not unwrap to, or is ErrNotFound, sql.ErrNoRows, or os.ErrNotExist.

func IsType

func IsType(err, ref error) bool

IsType returns if err or any unwrapped error is of the type of the passed ref error. It works similar than errors.As but without assigning to the ref error and without checking for Is or As methods.

func IterSeq

func IterSeq(err error) iter.Seq[error]

IterSeq returns an iter.Seq[error] iterator that yields the passed error once.

See the iter package documentation for more details.

func IterSeq2

func IterSeq2[T any](err error) iter.Seq2[T, error]

IterSeq2 returns an iter.Seq2[T, error] iterator that yields the default value of T and the passed error once.

This is useful for the design-pattern of using first value of a two value iterator as actual value and the second as error.

See the iter package documentation for more details.

func LogFunctionCall

func LogFunctionCall(logger Logger, function string, params ...any)

LogFunctionCall logs a formatted function call using FormatFunctionCall if logger is not nil. This is useful for logging function calls with their parameters for debugging.

func LogPanicWithFuncParams

func LogPanicWithFuncParams(log Logger, params ...any)

LogPanicWithFuncParams recovers any panic, converts it to an error wrapped with the callstack of the panic and the passed function parameter values and prints it with the prefix "LogPanicWithFuncParams: " to the passed Logger. After logging, the original panic is re-paniced.

func New

func New(text string) error

New returns a new error with the passed text wrapped with the current call stack.

func RecoverAndLogPanicWithFuncParams

func RecoverAndLogPanicWithFuncParams(log Logger, params ...any)

RecoverAndLogPanicWithFuncParams recovers any panic, converts it to an error wrapped with the callstack of the panic and the passed function parameter values and prints it with the prefix "RecoverAndLogPanicWithFuncParams: " to the passed Logger.

func RecoverPanicAsError

func RecoverPanicAsError(result *error)

RecoverPanicAsError recovers any panic, converts it to an error wrapped with the callstack of the panic and assigns it to the result error.

func RecoverPanicAsErrorWithFuncParams

func RecoverPanicAsErrorWithFuncParams(result *error, params ...any)

RecoverPanicAsErrorWithFuncParams recovers any panic, converts it to an error wrapped with the callstack of the panic and the passed function parameter values and assigns it to the result error.

func ReplaceErrNotFound

func ReplaceErrNotFound(err, replacement error) error

ReplaceErrNotFound returns the passed replacement error if IsErrNotFound(err) returns true, meaning that all (optionally wrapped) ErrNotFound, sql.ErrNoRows, os.ErrNotExist errors get replaced.

func Root

func Root(err error) error

Root unwraps err recursively and returns the root error.

func ShouldLog

func ShouldLog(err error) bool

ShouldLog checks if the passed error unwraps as a LogDecisionMaker and returns the result of its ShouldLog method. If error does not unwrap to LogDecisionMaker and is not nil then ShouldLog returns true. A nil error results in false.

func Type

func Type[T error](err error) bool

Type indicates if err is not nil and it or any unwrapped error is of the type T. It works similar than errors.As but without assigning to the ref error and without checking for Is or As methods.

func UnwrapCallStack

func UnwrapCallStack(err error) error

UnwrapCallStack removes all top-level callstack wrapping from err and returns the underlying error without the callstack information.

Unlike Root which unwraps to the root cause, this function only removes callstack wrappers (including those with function parameters) but preserves the error chain.

This is useful when you want to compare or match errors without the callstack information affecting the comparison, or when you need to access the wrapped error while discarding debug information.

Examples:

// Remove callstack wrapper from error
err := errs.New("something failed")
cleaned := errs.UnwrapCallStack(err)
// cleaned is the underlying Sentinel without callstack

// Compare errors without callstack
err1 := errs.WrapWithCallStack(sentinel)
err2 := errs.WrapWithCallStack(sentinel)
// err1 != err2 (different callstacks)
// errs.UnwrapCallStack(err1) == errs.UnwrapCallStack(err2) == sentinel

// Preserve error chain while removing top-level callstack
wrapped := fmt.Errorf("context: %w", sentinel)
withStack := errs.WrapWithCallStack(wrapped)
result := errs.UnwrapCallStack(withStack)
// result == wrapped (still wraps sentinel)

Note: This only removes top-level callstack wrapping. If there are callstack wrappers further down the error chain, they are preserved.

func WrapWith0FuncParams

func WrapWith0FuncParams(resultVar *error)

WrapWith0FuncParams wraps an error with the call stack for functions with no parameters. This is more efficient than WrapWithFuncParams for zero-parameter functions.

func WrapWith10FuncParams

func WrapWith10FuncParams(resultVar *error, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 any)

WrapWith10FuncParams wraps an error with the call stack and 10 function parameters. This is more efficient than WrapWithFuncParams for ten-parameter functions.

func WrapWith1FuncParam

func WrapWith1FuncParam(resultVar *error, p0 any)

WrapWith1FuncParam wraps an error with the call stack and 1 function parameter. This is more efficient than WrapWithFuncParams for single-parameter functions.

func WrapWith2FuncParams

func WrapWith2FuncParams(resultVar *error, p0, p1 any)

WrapWith2FuncParams wraps an error with the call stack and 2 function parameters. This is more efficient than WrapWithFuncParams for two-parameter functions.

func WrapWith3FuncParams

func WrapWith3FuncParams(resultVar *error, p0, p1, p2 any)

WrapWith3FuncParams wraps an error with the call stack and 3 function parameters. This is more efficient than WrapWithFuncParams for three-parameter functions.

func WrapWith4FuncParams

func WrapWith4FuncParams(resultVar *error, p0, p1, p2, p3 any)

WrapWith4FuncParams wraps an error with the call stack and 4 function parameters. This is more efficient than WrapWithFuncParams for four-parameter functions.

func WrapWith5FuncParams

func WrapWith5FuncParams(resultVar *error, p0, p1, p2, p3, p4 any)

WrapWith5FuncParams wraps an error with the call stack and 5 function parameters. This is more efficient than WrapWithFuncParams for five-parameter functions.

func WrapWith6FuncParams

func WrapWith6FuncParams(resultVar *error, p0, p1, p2, p3, p4, p5 any)

WrapWith6FuncParams wraps an error with the call stack and 6 function parameters. This is more efficient than WrapWithFuncParams for six-parameter functions.

func WrapWith7FuncParams

func WrapWith7FuncParams(resultVar *error, p0, p1, p2, p3, p4, p5, p6 any)

WrapWith7FuncParams wraps an error with the call stack and 7 function parameters. This is more efficient than WrapWithFuncParams for seven-parameter functions.

func WrapWith8FuncParams

func WrapWith8FuncParams(resultVar *error, p0, p1, p2, p3, p4, p5, p6, p7 any)

WrapWith8FuncParams wraps an error with the call stack and 8 function parameters. This is more efficient than WrapWithFuncParams for eight-parameter functions.

func WrapWith9FuncParams

func WrapWith9FuncParams(resultVar *error, p0, p1, p2, p3, p4, p5, p6, p7, p8 any)

WrapWith9FuncParams wraps an error with the call stack and 9 function parameters. This is more efficient than WrapWithFuncParams for nine-parameter functions.

func WrapWithCallStack

func WrapWithCallStack(err error) error

WrapWithCallStack wraps an error with the current call stack.

func WrapWithCallStackSkip

func WrapWithCallStackSkip(skip int, err error) error

WrapWithCallStackSkip wraps an error with the current call stack skipping skip stack frames.

The skip parameter specifies how many stack frames to skip before capturing the call stack. Use skip=0 to capture the stack from the immediate caller of WrapWithCallStackSkip. Increase skip by 1 for each additional function wrapper you add.

Examples:

// Direct use - skip=1 to show caller of your function
func DoSomething() error {
    err := someOperation()
    if err != nil {
        return WrapWithCallStackSkip(1, err)
    }
    return nil
}

// Wrapper function - skip=1+skip to pass through skip count
func myWrapper(skip int, err error) error {
    return WrapWithCallStackSkip(1+skip, err)
}

// Helper that wraps - skip=1 so caller of helper appears in stack
func wrapDatabaseError(err error) error {
    return WrapWithCallStackSkip(1, fmt.Errorf("database error: %w", err))
}

func WrapWithFuncParams

func WrapWithFuncParams(resultVar *error, params ...any)

WrapWithFuncParams wraps an error with the current call stack and function parameters.

This is the most commonly used function for wrapping errors with function parameters in defer statements. It automatically captures the correct call stack frame.

Example:

func ProcessUser(userID string, age int) (err error) {
    defer WrapWithFuncParams(&err, userID, age)

    if age < 0 {
        return errors.New("invalid age")
    }
    // When an error is returned, it will be wrapped with:
    // - The call stack showing ProcessUser(userID, age)
    // - The actual parameter values passed to the function
    return someOperation(userID)
}

Note: For functions with 0-10 parameters, use the optimized variants WrapWith0FuncParams through WrapWith10FuncParams for better performance.

Example
package main

import (
	"context"
	"fmt"
)

type strct struct {
	A int
}

func funcA(ctx context.Context, i int, s string, strct *strct) (err error) {
	defer WrapWith4FuncParams(&err, ctx, i, s, strct)

	return funcB(s, "X\nX")
}

func funcB(s ...string) (err error) {
	defer WrapWithFuncParams(&err, s)

	return funcC()
}

func funcC() (err error) {
	defer WrapWithFuncParams(&err)

	return New("error in funcC")
}

func main() {
	err := funcA(context.Background(), 666, "Hello World!", &strct{A: -1})
	fmt.Println(err)

}
Output:

error in funcC
github.com/domonda/go-errs.funcC()
    github.com/domonda/go-errs/wrapwithfuncparams_test.go:27
github.com/domonda/go-errs.funcB([`Hello World!`,`X\nX`])
    github.com/domonda/go-errs/wrapwithfuncparams_test.go:21
github.com/domonda/go-errs.funcA(Context{}, 666, `Hello World!`, strct{A:-1})
    github.com/domonda/go-errs/wrapwithfuncparams_test.go:15

func WrapWithFuncParamsSkip

func WrapWithFuncParamsSkip(skip int, resultVar *error, params ...any)

WrapWithFuncParamsSkip wraps an error with the current call stack and function parameters, skipping skip stack frames.

The skip parameter specifies how many stack frames to skip before capturing the call stack. Use skip=0 to capture the stack from the immediate caller of WrapWithFuncParamsSkip. Increase skip by 1 for each additional function wrapper you add.

This function is typically used in defer statements to automatically wrap errors with the function's parameters when the function returns an error.

Examples:

// Standard usage in defer - skip=0 is correct
func ProcessUser(userID string, age int) (err error) {
    defer WrapWithFuncParamsSkip(0, &err, userID, age)
    // ... function body
    return someError
}

// Wrapper function - skip=1+skip to pass through skip count
func myWrapperSkip(skip int, resultVar *error, params ...any) {
    WrapWithFuncParamsSkip(1+skip, resultVar, params...)
}

// Most common: use WrapWithFuncParams which has skip=0 built-in
func ProcessUser(userID string, age int) (err error) {
    defer WrapWithFuncParams(&err, userID, age)
    // ... function body
    return someError
}

Types

type LogDecisionMaker

type LogDecisionMaker interface {
	error

	// ShouldLog decides if the error should be logged
	ShouldLog() bool
}

LogDecisionMaker can be implemented by errors to decide if they should be logged. Use the package function ShouldLog to check if a wrapped error implements the interface and get the result of its ShouldLog method.

type Logger

type Logger interface {
	Printf(format string, args ...any)
}

Logger is an interface that can be implemented to log errors

type Secret

type Secret interface {
	// Secrect returns the wrapped secret value.
	Secrect() any

	// String returns a redacted string that indicates
	// that the value is a secret without revealing the actual value.
	String() string
}

Secret is an interface that wraps a secret value to prevent it from being logged or printed. It implements CallStackPrintable to ensure secrets are never revealed in error call stacks.

func KeepSecret

func KeepSecret(val any) Secret

KeepSecret wraps the passed value in a Secret to prevent it from being logged or printed.

Example
secret := errs.KeepSecret("My Password!")
// Actual value:
fmt.Println(secret.Secrect())
// Redacted string variants:
fmt.Println(secret)
fmt.Printf("%v\n", secret)
fmt.Printf("%+v\n", secret)
fmt.Printf("%#v\n", secret)
Output:

My Password!
***REDACTED***
***REDACTED***
***REDACTED***
string(***REDACTED***)

type Sentinel

type Sentinel string

Sentinel implements the error interface for a string and is meant to be used to declare const sentinel errors.

Example:

const ErrUserNotFound errs.Sentinel = "user not found"
Example
const ErrUserAlreadyExists Sentinel = "user already exists"

var err error = ErrUserAlreadyExists
fmt.Println("const Sentinel errors.Is:", errors.Is(err, ErrUserAlreadyExists))

err = fmt.Errorf("%w: [email protected]", ErrUserAlreadyExists)
fmt.Println("Wrapped Sentinel errors.Is:", errors.Is(err, ErrUserAlreadyExists))
Output:

const Sentinel errors.Is: true
Wrapped Sentinel errors.Is: true
const ErrNotFound Sentinel = "not found"

ErrNotFound is an universal error returned in case that a requested resource could not be found.

Recommended usage:

This error can be returned directly from a function if that function only requests one kind of resource and no further differentiation is needed about what resource could not be found.

Else create custom "not found" error by wrapping ErrNotFound or implementing a custom error type with an

Is(target error) bool

method that returns true for target == ErrNotFound.

For checking errors it is recommended to use IsErrNotFound(err) instead of errors.Is(err, ErrNotFound) to also catch the standard library "not found" errors sql.ErrNoRows and os.ErrNotExist.

func (Sentinel) Error

func (s Sentinel) Error() string

Directories

Path Synopsis
cmd
go-errs-wrap command
go-errs-wrap is a code transformation tool for managing error wrapping in Go.
go-errs-wrap is a code transformation tool for managing error wrapping in Go.
go-errs-wrap/rewrite
Package rewrite provides functionality to remove or replace defer errs.Wrap statements and //#wrap-result-err marker comments in Go source files.
Package rewrite provides functionality to remove or replace defer errs.Wrap statements and //#wrap-result-err marker comments in Go source files.
examples
wrapping command

Jump to

Keyboard shortcuts

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