fail

package module
v1.1.12 Latest Latest
Warning

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

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

README

🔥 FAIL - Failure Abstraction & Instrumentation Layer

Deterministic, type-safe, compilation-order-independent error handling for Go.

FAIL provides a revolutionary approach to error handling with name-based deterministic IDs, automatic validation, and beautiful ergonomics.

✨ What Makes FAIL Revolutionary

🎯 Name-Based Deterministic IDs

Error IDs are hash-based and compilation-order independent:

// Define with NAMES - numbers are deterministic!
var (
    AuthInvalidCredentials = fail.ID(0, "AUTH", 0, true, "AuthInvalidCredentials")  // 0_AUTH_0000_S
    AuthTokenExpired       = fail.ID(0, "AUTH", 1, true, "AuthTokenExpired")        // 0_AUTH_0001_S
    UserNotFound           = fail.ID(0, "USER", 0, true, "UserNotFound")            // 0_USER_0000_S
)

// Numbers are based on explicit assignment per domain
// They NEVER change unless you change the number
// No more file-order issues! 🎉
🛡️ Built-in Validation

FAIL validates at runtime (via fail.ID or ValidateIDs):

// ✅ Valid - name starts with domain
var Good1 = fail.ID(0, "AUTH", 0, true, "AuthInvalidCredentials")

// ❌ PANIC - name doesn't start with domain  
var Bad1 = fail.ID(0, "AUTH", 1, true, "InvalidCredentials")

// ❌ PANIC - duplicate name
var Bad2 = fail.ID(0, "AUTH", 0, true, "AuthInvalidCredentials")

// ❌ PANIC - too similar (Levenshtein distance < 3)
var Bad3 = fail.ID(0, "AUTH", 2, true, "AuthInvalidCredential")  // Too close!
🎨 One-Line Error Registration
// Form() creates sentinel, registers it, and supports translations/default args!
var ErrUserNotFound = fail.Form(UserNotFound, "user %s not found", false, nil).
    AddLocalizations(map[string]string{
        "pt-BR": "usuário %s não encontrado",
        "es-ES": "usuario %s no encontrado",
    })

// Registration is IDEMPOTENT - first register wins! 🛡️
📋 Auto-Documentation
// Export all your errors as JSON for documentation
fail.ExportIDList()

// Output:
// [
//   {"name": "AuthInvalidCredentials", "domain": "AUTH", "static": true, "id": "0_AUTH_0000_S"},
//   {"name": "UserNotFound", "domain": "USER", "static": true, "id": "0_USER_0000_S"},
//   ...
// ]

🚀 Quick Start

1. Define Error IDs

Create errors.go:

package myapp

import "your-module/fail"

// Auth domain - names MUST start with "Auth"
var (
    AuthInvalidCredentials = fail.ID(0, "AUTH", 0, true, "AuthInvalidCredentials")
    AuthTokenExpired       = fail.ID(0, "AUTH", 1, true, "AuthTokenExpired")
    AuthValidationFailed   = fail.ID(0, "AUTH", 0, false, "AuthValidationFailed") // Dynamic!
)
2. Register Errors with Localization
// Form() - one-liner sentinels with translations! 🎉
var (
    ErrUserNotFound = fail.Form(UserNotFound, "user %s not found", false, nil).
        AddLocalizations(map[string]string{
            "pt-BR": "usuário %s não encontrado",
            "es-ES": "usuario %s no encontrado",
        })
)
3. Use Everywhere
func GetUser(email string) (*User, error) {
    user, err := db.GetUser(email)
    if err != nil {
        // Localization + Rendering
        return nil, fail.New(UserNotFound).
            WithLocale("pt-BR").
            WithArgs(email)
    }
    return user, nil
}
4. Render & Handle
func HandleRequest(w http.ResponseWriter, r *http.Request) {
    _, err := GetUser("[email protected]")
    if err != nil {
        // err.Error() automatically calls Render()!
        fmt.Println(err.Error()) 
        // Output: [0_USER_0000_S] usuário [email protected] não encontrado
    }
}

🌐 Localization & Rendering

FAIL provides first-class support for multi-language applications and dynamic message templates.

Template Rendering

Use standard fmt placeholders in your messages:

err := fail.New(UserNotFound).WithArgs("[email protected]")
msg := err.Render().Message // "user [email protected] not found"
Switching Locales
// Global fallback
fail.SetDefaultLocale("en-US")

// Per-error instance
err.WithLocale("es-ES").Localize()

🎨 Core Features

Fluent Builder API
err := fail.New(UserValidationFailed).
    WithLocale("fr-FR").            // Set target language
    WithArgs("admin").              // Set template arguments
    Localize().                     // Resolve translation
    Render().                       // Format message
    Msg("override message").        // Manual override
    With(cause).
    Internal("debug info").
    Trace("step 1").
    Validation("email", "invalid").
    LogAndRecord()
🔄 Robust Retry Logic

Built-in retry mechanism with configurable backoff strategies and jitter.

// Basic retry (uses default config)
err := fail.Retry(func() error {
    return db.Connect()
})

// Advanced retry with exponential backoff + jitter
cfg := fail.RetryConfig{
    MaxAttempts: 5,
    Delay: fail.WithJitter(
        fail.BackoffExponential(100*time.Millisecond), 
        0.3, // 30% jitter
    ),
    ShouldRetry: func(err error) bool {
        // Custom retry logic
        return fail.IsSystem(err)
    },
}

err := fail.RetryCFG(cfg, func() error {
    return remoteAPI.Call()
})

// Retry with return value
val, err := fail.RetryValue(func() (*User, error) {
    return repo.GetUser(id)
})
🔗 Advanced Error Chaining

Fluent chain API for executing steps and handling errors cleanly.

err := fail.Chain(validateRequest).
    Then(checkPermissions).
    ThenCtx("database", saveData).       // Adds context to error if fails
    ThenIf(shouldNotify, sendEmail).     // Conditional execution
    Catch(func(e *fail.Error) *fail.Error {
        // Transform error if needed
        return e.AddMeta("caught", true)
    }).
    Finally(func() {
        cleanup()
    }).
    Error() // Returns *fail.Error or nil
📦 Error Groups

Collect multiple errors thread-safely (e.g., parallel validation).

group := fail.NewErrorGroup(10)

// Add errors safely from goroutines
group.Add(err1)
group.Addf(ValidationFailed, "field %s invalid", "email")

if group.HasErrors() {
    // Returns a single error containing all others
    return group.ToError() 
}
🪝 Hooks & Lifecycle

Hook into error events for global monitoring, logging, or metrics.

// Register global hooks
fail.OnCreate(func(e *fail.Error) {
    // Called when fail.New() is used
})

fail.OnLog(func(e *fail.Error, data map[string]any) {
    // Called when e.Log() is called
})

fail.OnMatch(func(e *fail.Error, data map[string]any) {
    // Called when fail.Match() succeeds
})
🔍 Observability

Integrate with your favorite tracing and logging libraries.

// 1. Implement fail.Tracer and fail.Logger interfaces
type MyTracer struct { ... }
type MyLogger struct { ... }

// 2. Register them
fail.SetTracer(&MyTracer{})
fail.SetLogger(&MyLogger{})

// 3. Use in code
err := fail.New(AuthTokenExpired).
    Record(). // Traces error
    Log()     // Logs error
🔄 Generic Error Mapping

Map external errors (DB, libraries) to your domain errors.

// Implement Mapper interface
type MyMapper struct{}
func (m *MyMapper) MapToFail(err error) (*fail.Error, bool) {
    if isPostgresDuplicateKey(err) {
        return fail.New(UserEmailExists), true
    }
    return nil, false
}

// Register with priority
fail.RegisterMapper(&MyMapper{})

// Usage:
err := db.Query(...)
return fail.From(err) // Automatically mapped!
🌐 Translation

Convert errors to other formats (HTTP, gRPC, CLI).

// Implement Translator
type HTTPTranslator struct{}
func (t *HTTPTranslator) Translate(e *fail.Error) (any, error) {
    return HTTPResponse{Code: 400, Msg: e.Message}, nil
}

fail.RegisterTranslator(&HTTPTranslator{})

// Usage
resp, _ := fail.Translate(err, "http")

🔍 Pattern Matching

Match errors elegantly without nested if-statements.

fail.Match(err).
    Case(AuthInvalidCredentials, func(e *fail.Error) {
        log.Info("invalid credentials")
    }).
    CaseDomain(func(e *fail.Error) {
        // Handle any domain error
    }).
    CaseSystem(func(e *fail.Error) {
        // Handle system/unexpected errors
        alert.Ops(e)
    }).
    Default(func(err error) {
        // Unknown error
    })

🎁 Helper Functions

// Quick constructors
fail.Fast(AuthTokenExpired, "custom msg")
fail.Wrap(DBQueryFailed, dbErr)
fail.WrapMsg(DBQueryFailed, "query failed", dbErr)

// Panic on error (for init)
fail.Must(err)
fail.MustNew(AuthTokenExpired)

// Checkers
fail.Is(err, AuthTokenExpired)
fail.IsSystem(err)
fail.IsTrusted(err)

🚀 Why FAIL?

  • Deterministic: Hash-based IDs never change unless you rename
  • Type-Safe: ErrorID is a struct, impossible to typo
  • Validated: Name, domain, similarity all checked at creation
  • Documented: Export JSON list for automatic documentation
  • Ergonomic: Beautiful fluent API with Form() helper
  • Framework-Agnostic: Works with any HTTP framework
  • Observable: Built-in logging and tracing hooks
  • Fun: Actually enjoyable to use!

📦 Installation

go get your-module/fail

📄 License

MIT


FAIL - Because deterministic error handling shouldn't be a failure! 🔥

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrMultipleErrors = Form(MultipleErrors, "multiple errors occurred", false, nil)
View Source
var ErrNoMapperRegistered = Form(NoMapperRegistered, "no mapper is registered in the registry", true, nil)
View Source
var ErrNotMatchedInAnyMapper = Form(NotMatchedInAnyMapper, "error wasn't matched/mapped by any mapper", true, nil)
View Source
var ErrTranslateNotFound = Form(TranslateNotFound, "couldn't find translator", true, nil)
View Source
var ErrTranslatePanic = Form(TranslatePanic, "translator panicked during translation", true, nil)
View Source
var ErrTranslateUnsupportedError = Form(TranslateUnsupportedError, "error not supported by %s translator", true, nil, "UNNAMED")
View Source
var ErrTranslateUntrustedError = Form(TranslateUntrustedError, "tried translating unregistered error", true, nil)
View Source
var ErrTranslateWrongType = Form(TranslateWrongType, "translator returned unexpected type", true, nil)
View Source
var ErrTranslatorAlreadyRegistered = Form(TranslatorAlreadyRegistered, "translator already registered", true, nil)
View Source
var ErrTranslatorNameEmpty = Form(TranslatorNameEmpty, "translator must have a non-empty name", true, nil)
View Source
var ErrTranslatorNil = Form(TranslatorNil, "cannot register nil translator", true, nil)
View Source
var ErrUnknownError = Form(UnknownError, "unknown error", true, nil)
View Source
var ErrUnregisteredError = Form(UnregisteredError, "error with ID(%s) is not registered in the registry", true, nil, "ID NOT SET")
View Source
var MultipleErrors = internalID(0, 6, false, "FailMultipleErrors")

MultipleErrors is raised when multiple errors are aggregated in ErrorGroup

View Source
var NoMapperRegistered = internalID(0, 12, false, "FailNoMapperRegistered")
View Source
var NotMatchedInAnyMapper = internalID(0, 11, false, "FailNotMatchedInAnyMapper")
View Source
var RuntimeIDInvalid = internalID(9, 16, true, "FailRuntimeIDInvalid")
View Source
var TranslateNotFound = internalID(0, 2, false, "FailTranslatorNotFound")
View Source
var TranslatePanic = internalID(0, 4, false, "FailTranslatorPanic")
View Source
var TranslateUnsupportedError = internalID(0, 3, false, "FailTranslateUnsupportedError")
View Source
var TranslateUntrustedError = internalID(0, 1, false, "FailTranslateUntrustedError")
View Source
var TranslateWrongType = internalID(0, 5, false, "FailTranslateWrongType")
View Source
var TranslatorAlreadyRegistered = internalID(0, 13, false, "FailTranslatorAlreadyRegistered")
View Source
var TranslatorNameEmpty = internalID(0, 15, true, "FailTranslatorNameEmpty")
View Source
var TranslatorNil = internalID(0, 14, true, "FailTranslatorNil")
View Source
var UnknownError = internalID(0, 7, false, "FailUnknownError")
View Source
var UnregisteredError = internalID(0, 0, false, "FailUnregisteredError")

Functions

func AllowInternalLogs added in v1.1.8

func AllowInternalLogs(allow bool)

AllowInternalLogs enables or disables internal library logging. When enabled, the library will log warnings and debug information about error processing, such as:

  • Double calls to From()
  • Unmapped errors
  • Mapper registration issues

This is useful for debugging integration issues but should be disabled in production to avoid log spam. Default is false.

Example:

fail.AllowInternalLogs(true)  // Enable logging
fail.AllowInternalLogs(false) // Disable logging (default)

func AllowRuntimePanics added in v1.1.10

func AllowRuntimePanics(allow bool)

AllowRuntimePanics controls whether the library may panic at runtime. When true, operations that would normally log warnings or fail silently will instead panic immediately (e.g., modifying static errors). When false (default), the library avoids panics and uses error logging or silent failures where appropriate.

Note: The ID() method will always panic regardless of this setting.

Enabling this is recommended during development and testing to catch programming errors early, but should typically be disabled in production.

func AllowStaticMutations added in v1.1.10

func AllowStaticMutations(allow bool, shouldPanic bool)

AllowStaticMutations controls whether static errors in the global registry can be mutated. When set to false (default), builder methods on static errors will silently return the original error without modifications. When set to true, shoudlPanic is ignored and mutations are allowed but warnings are logged if internal logging is enabled.

If shouldPanic is true and allow is false, attempts to modify static errors will panic instead of failing silently. This overrides the default silent behavior.

This is a convenience wrapper for global.AllowStaticMutations(allow, shouldPanic).

func BackoffConstant

func BackoffConstant(d time.Duration) func(int) time.Duration

func BackoffExponential

func BackoffExponential(base time.Duration) func(int) time.Duration

func BackoffLinear

func BackoffLinear(step time.Duration) func(int) time.Duration

func ExportIDList

func ExportIDList() ([]byte, error)

ExportIDList returns all registered error IDs as JSON bytes

func GetDebug

func GetDebug(err error) ([]string, bool)

GetDebug extracts debug information from an error

func GetInternalMessage

func GetInternalMessage(err error) string

GetInternalMessage extracts the internal message from an error

func GetMessage

func GetMessage(err error) string

GetMessage extracts the user-facing message from any error

func GetMeta

func GetMeta(err error, key string) (any, bool)

GetMeta extracts metadata from an error

func GetTraces

func GetTraces(err error) ([]string, bool)

GetTraces extracts trace information from an error

func Is

func Is(err error, id ErrorID) bool

Is checks if the target error is an Error with the specified ID

func IsDomain

func IsDomain(err error) bool

IsDomain checks if an error is a domain error

func IsRetryableDefault

func IsRetryableDefault(err error) bool

func IsSystem

func IsSystem(err error) bool

IsSystem checks if an error is a system error

func IsTrusted

func IsTrusted(err error) bool

IsTrusted checks if a generic error is an Error and is trusted

func Must

func Must(err error)

Must panics if the error is not nil Useful for initialization code

func MustRegisterTranslator

func MustRegisterTranslator(t Translator)

func On

func On(t HookType, fn any)

On is a global convenience for setting hooks

func OnCreate

func OnCreate(fn func(*Error, map[string]any))

func OnForm

func OnForm(fn func(ErrorID, *Error))

func OnFromFail added in v1.1.3

func OnFromFail(fn func(error))

func OnFromSuccess added in v1.1.3

func OnFromSuccess(fn func(error, *Error))

func OnLog

func OnLog(fn func(*Error, map[string]any))

func OnMap

func OnMap(fn func(*Error, map[string]any))

func OnMatch

func OnMatch(fn func(*Error, map[string]any))

func OnTrace

func OnTrace(fn func(*Error, map[string]any))

func OnTranslate

func OnTranslate(fn func(*Error, map[string]any))

func OnWrap

func OnWrap(fn func(*Error, error))

func OverrideAllowIDRuntimePanics added in v1.1.11

func OverrideAllowIDRuntimePanics(allow bool)

OverrideAllowIDRuntimePanics sets global id registry override

func OverrideAllowIDRuntimeRegistrationForTestingOnly added in v1.1.11

func OverrideAllowIDRuntimeRegistrationForTestingOnly(allow bool)

OverrideAllowIDRuntimeRegistrationForTestingOnly sets global id registry override for tests

func Register

func Register(def ErrorDefinition)

Register adds an error definition to the global registry

func RegisterMany

func RegisterMany(defs ...*ErrorDefinition)

RegisterMany registers multiple error definitions at once

func RegisterMapper

func RegisterMapper(mapper Mapper)

RegisterMapper adds a generic error mapper

func RegisterTranslations

func RegisterTranslations(locale string, msgs map[ErrorID]string)

RegisterTranslations adds translations for a locale on the global registry

func RegisterTranslator

func RegisterTranslator(t Translator) error

RegisterTranslator adds a translator for converting errors to other formats

func Retry

func Retry(fn func() error) error

Retry executes a function with retries

func RetryCFG

func RetryCFG(config RetryConfig, fn func() error) error

RetryCFG executes a function with retries using the passed config

func RetryValue

func RetryValue[T any](fn func() (T, error)) (T, error)

RetryValue retries a function that returns (T, error)

func RetryValueCFG

func RetryValueCFG[T any](config RetryConfig, fn func() (T, error)) (T, error)

func SetDefaultLocale

func SetDefaultLocale(locale string)

SetDefaultLocale sets the fallback locale for the global registry

func SetLogger

func SetLogger(logger Logger)

SetLogger sets the custom logging solution to the registry

func SetRetryConfig

func SetRetryConfig(config *RetryConfig)

func SetTracer

func SetTracer(tracer Tracer)

SetTracer sets the custom tracing solution to the registry

func Translate

func Translate(err *Error, translatorName string) (any, error)

func TranslateAs

func TranslateAs[T any](err *Error, translatorName string) (T, error)

func TranslateAsFrom

func TranslateAsFrom[T any](r *Registry, err *Error, translatorName string) (T, error)

func ValidateIDs

func ValidateIDs()

ValidateIDs checks for gaps and duplicates. Call in main() after all init().

func WithJitter

func WithJitter(delay func(int) time.Duration, jitterFraction float64) func(int) time.Duration

Types

type Error

type Error struct {
	// Required fields
	ID              ErrorID // Unique trusted identifier
	Message         string  // User-facing message
	InternalMessage string  // Internal/debug message (optional but recommended)
	Cause           error   // The underlying error that caused this
	IsSystem        bool    // true = infrastructure/unexpected, false = domain/expected

	Args   []any  // Captured arguments for localization
	Locale string // Target locale for this error instance

	// Optional structured data
	Meta map[string]any // Arbitrary metadata (traces, validation errors, etc.)
	// contains filtered or unexported fields
}

Error is the core error type that all domain errors implement

func As

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

As extracts an Error from any error

func Fast

func Fast(id ErrorID, message string) *Error

Fast creates a simple error with just an ID and custom message

func Form

func Form(id ErrorID, defaultMsg string, isSystem bool, meta map[string]any, defaultArgs ...any) *Error

Form creates, registers, and returns an error in one call This is a convenience function for defining error sentinels

WARNING Only use package level sentinel errors that are created by Form in non-concurrent environments For concurrent environment prefer calling New with the error ID

Example:

var ErrUserNotFound = fail.Form(UserNotFound, "user not found", false, nil)

This is equivalent to:

fail.Register(fail.ErrorDefinition{
    ID:             UserNotFound,
    DefaultMessage: "user not found",
    IsSystem:       false,
})
var ErrUserNotFound = fail.New(UserNotFound)

func From

func From(err error) *Error

From ingests a generic error and transforms it to an Error

func FromWithMsg

func FromWithMsg(err error, message string) *Error

FromWithMsg ingests a generic error and adds a custom message

func MustNew

func MustNew(id ErrorID) *Error

MustNew creates an error and panics if it's not registered

func New

func New(id ErrorID) *Error

New returns a new Error from a registered definition

func Newf

func Newf(id ErrorID, format string, args ...interface{}) *Error

Newf returns a new Error from a registered definition with a new formatted message

func Wrap

func Wrap(id ErrorID, cause error) *Error

Wrap creates an error that wraps another error

func WrapMsg

func WrapMsg(id ErrorID, message string, cause error) *Error

WrapMsg creates an error with a custom message that wraps another error

func (*Error) AddLocalization

func (e *Error) AddLocalization(locale string, msg string) *Error

AddLocalization adds a translation for this error's ID to its registry If localization already exists for this locale+ID, does nothing (idempotent) Returns the original error unmodified for chaining

func (*Error) AddLocalizations

func (e *Error) AddLocalizations(msgs map[string]string) *Error

AddLocalizations adds multiple translations at once

func (*Error) AddMeta

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

AddMeta sets a metadata value

func (*Error) Debug

func (e *Error) Debug(debug string) *Error

Debug adds debug information to metadata

func (*Error) Debugs

func (e *Error) Debugs(debug ...string) *Error

Debugs adds each debug information to metadata

func (*Error) Domain

func (e *Error) Domain() *Error

Domain marks this error as a domain error

func (*Error) Dump

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

func (*Error) Error

func (e *Error) Error() string

Error() uses GetRendered() for the final message

func (*Error) GetLocalized

func (e *Error) GetLocalized() string

GetLocalized returns the localized message template as string (read-only, no modification)

func (*Error) GetRendered

func (e *Error) GetRendered() string

GetRendered returns the fully rendered message as string (read-only, no modification)

func (*Error) Internal

func (e *Error) Internal(msg string) *Error

Internal sets or overrides the internal message

func (*Error) Internalf

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

Internalf sets the internal message using format string

func (*Error) IsTrusted

func (e *Error) IsTrusted() bool

IsTrusted checks if an error is trusted by the registry

func (*Error) Localize

func (e *Error) Localize() *Error

Localize resolves the translated message template for this error's locale Stores result in e.Message, returns *Error for chaining

func (*Error) Log

func (e *Error) Log() *Error

Log automatically logs the error using the configured logger

func (*Error) LogAndRecord

func (e *Error) LogAndRecord() *Error

LogAndRecord logs and traces the error

func (*Error) LogAndRecordCtx

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

func (*Error) LogCtx

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

func (*Error) MergeMeta

func (e *Error) MergeMeta(data map[string]any) *Error

MergeMeta merges a map into the metadata

func (*Error) Msg

func (e *Error) Msg(msg string) *Error

Msg sets or overrides the error message (for Dynamic errors)

func (*Error) Msgf

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

Msgf sets the error message using format string

func (*Error) Record

func (e *Error) Record() *Error

Record automatically traces the error using the configured tracer

func (*Error) RecordCtx

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

func (*Error) Render

func (e *Error) Render() *Error

Render formats the error's message template with its arguments Stores result in e.Message, returns *Error for chaining

func (*Error) System

func (e *Error) System() *Error

System marks this error as a system error

func (*Error) Trace

func (e *Error) Trace(trace string) *Error

Trace adds trace information to metadata

func (*Error) Traces

func (e *Error) Traces(trace ...string) *Error

Traces adds each trace information to metadata

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap implements error unwrapping for errors.Is/As

func (*Error) Validation

func (e *Error) Validation(field, message string) *Error

Validation adds a validation error to metadata

func (*Error) Validations

func (e *Error) Validations(errs []ValidationError) *Error

Validations adds multiple validation errors at once

func (*Error) With

func (e *Error) With(cause error) *Error

With sets the cause of the error

func (*Error) WithArgs

func (e *Error) WithArgs(args ...any) *Error

WithArgs sets the arguments for template formatting

func (*Error) WithLocale

func (e *Error) WithLocale(locale string) *Error

WithLocale sets the target locale for this error

func (*Error) WithMeta

func (e *Error) WithMeta(data map[string]any) *Error

WithMeta sets the metadata to data, it replaces existing metadata to merge use MergeMeta

type ErrorChain

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

ErrorChain enables fluent error handling with *Error as first-class citizen internally Accepts standard Go error functions externally, converts immediately to *Error

func Chain

func Chain(fn func() error) *ErrorChain

Chain starts a new error chain, immediately executing the first step The error is normalized to *Error via From() on entry

Example:

err := fail.Chain(validateRequest).
	Then(checkPermissions).
	ThenCtx("database", saveData).
	Error()

func ChainCtx

func ChainCtx(stepName string, fn func() error) *ErrorChain

ChainCtx starts a chain with immediate named context

func (*ErrorChain) Catch

func (c *ErrorChain) Catch(fn func(*Error) *Error) *ErrorChain

Catch transforms the error, enabling recovery or enrichment fn receives the current *Error and returns modified *Error

func (*ErrorChain) Error

func (c *ErrorChain) Error() *Error

Error returns the final *Error or nil Returns concrete type for rich error handling (ID, Meta, Cause, etc.)

func (*ErrorChain) Finally

func (c *ErrorChain) Finally(fn func()) *ErrorChain

Finally executes cleanup regardless of error state

func (*ErrorChain) OnError

func (c *ErrorChain) OnError(fn func(*Error)) *ErrorChain

OnError executes callback if chain has failed (for logging/metrics/cleanup)

func (*ErrorChain) Step

func (c *ErrorChain) Step() int

Step returns count of successfully completed steps

func (*ErrorChain) Then

func (c *ErrorChain) Then(fn func() error) *ErrorChain

Then executes the next step if no error has occurred Automatically converts returned error to *Error via From()

func (*ErrorChain) ThenCtx

func (c *ErrorChain) ThenCtx(stepName string, fn func() error) *ErrorChain

ThenCtx executes with named step context for observability

func (*ErrorChain) ThenCtxIf

func (c *ErrorChain) ThenCtxIf(condition bool, stepName string, fn func() error) *ErrorChain

ThenCtxIf executes named step conditionally

func (*ErrorChain) ThenIf

func (c *ErrorChain) ThenIf(condition bool, fn func() error) *ErrorChain

ThenIf executes conditionally only if condition is true and no prior error

func (*ErrorChain) Unwrap

func (c *ErrorChain) Unwrap() error

Unwrap implements error interface for compatibility

func (*ErrorChain) Valid

func (c *ErrorChain) Valid() bool

Valid returns true if no errors occurred (all steps succeeded)

type ErrorDefinition

type ErrorDefinition struct {
	ID             ErrorID
	DefaultMessage string // Used for Static errors or as fallback
	IsSystem       bool
	Meta           map[string]any // Default metadata to include
	DefaultArgs    []any
}

ErrorDefinition is the blueprint for creating errors

type ErrorGroup

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

ErrorGroup collects multiple errors thread-safely

func NewErrorGroup

func NewErrorGroup(capacity int) *ErrorGroup

NewErrorGroup creates a new error group

func (*ErrorGroup) Add

func (g *ErrorGroup) Add(err error) *ErrorGroup

Add adds an error to the group (nil-safe, concurrency-safe)

func (*ErrorGroup) Addf

func (g *ErrorGroup) Addf(id ErrorID, format string, args ...interface{}) *ErrorGroup

Addf adds a formatted error string as a dynamic error (convenience method)

func (*ErrorGroup) Any

func (g *ErrorGroup) Any(match func(*Error) bool) bool

func (*ErrorGroup) Error

func (g *ErrorGroup) Error() string

Error implements error interface

func (*ErrorGroup) Errors

func (g *ErrorGroup) Errors() []*Error

Errors returns a copy of all collected errors (concurrency-safe)

func (*ErrorGroup) First

func (g *ErrorGroup) First() *Error

First returns the first error or nil (concurrency-safe)

func (*ErrorGroup) HasErrors

func (g *ErrorGroup) HasErrors() bool

HasErrors returns true if the group has any errors

func (*ErrorGroup) Last

func (g *ErrorGroup) Last() *Error

Last returns the last error or nil (useful for "most recent error" scenarios)

func (*ErrorGroup) Len

func (g *ErrorGroup) Len() int

Len returns the number of errors collected (concurrency-safe)

func (*ErrorGroup) Reset

func (g *ErrorGroup) Reset() *ErrorGroup

Reset clears all errors (useful for pooling/reuse scenarios)

func (*ErrorGroup) ToError

func (g *ErrorGroup) ToError() *Error

ToError converts the group to a single *Error Returns nil if no errors, the first error if only one, or a FailMultipleErrors error containing all errors in meta if multiple

func (*ErrorGroup) Unwrap

func (g *ErrorGroup) Unwrap() []error

Unwrap implements the Go 1.20+ multiple error unwrapping interface This allows errors.Is() and errors.As() to check against any error in the group

type ErrorID

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

ErrorID represents a trusted, deterministically-generated error identifier IDs are generated from names using explicit numbering for stability across versions Static (S) and Dynamic (D) have separate counters within each domain Format: LEVEL_DOMAIN_NUM_TYPE (e.g., "0_AUTH_0042_S") Level indicates severity but does not affect uniqueness

func GetID

func GetID(err error) (ErrorID, bool)

GetID extracts the error ID from any error

func ID

func ID(level int, domain string, number int, static bool, name string) ErrorID

ID creates a new trusted ErrorID with explicit numbering This is the ONLY way to create a trusted ErrorID

WARNING: Must be called at package level (var declaration). Calling inside func init() or runtime functions causes unstable numbering. Use go:generate or static analysis to verify.

Parameters:

  • name: Full error name (e.g., "AuthInvalidCredentials", "UserNotFound")
  • domain: Error domain (e.g., "AUTH", "USER") - must be a prefix of the name
  • static: true for static message, false for dynamic
  • level: severity level (0-9 recommended)
  • number: explicit number for this ID (must be unique within domain+type)

Panics if:

  • Name doesn't start with domain
  • Name already exists in registry
  • Name is too similar to existing name (Levenshtein distance < 3)
  • Domain is "FAIL" (reserved for internal errors)
  • Number already used in this domain+type combination

Example:

var AuthInvalidCredentials = fail.ID(0, "AUTH", 0, true, "AuthInvalidCredentials")   // 0_AUTH_0000_S
var AuthInvalidPassword    = fail.ID(0, "AUTH", 1, true, "AuthInvalidPassword")      // 0_AUTH_0001_S
var AuthCustomError        = fail.ID(0, "AUTH", 0, false, "AuthCustomError")         // 1_AUTH_0000_D
var AuthAnotherError       = fail.ID(0, "AUTH", 1, false, "AuthAnotherError")        // 0_AUTH_0001_D
// v0.0.2 - add new ID, gaps are fine
var AuthNewFeature         = fail.ID("AuthNewFeature", "AUTH", true, 0, 100)         // 0_AUTH_0100_S

func (ErrorID) Domain

func (id ErrorID) Domain() string

Domain returns the error domain (e.g., "AUTH", "USER")

func (ErrorID) IsStatic

func (id ErrorID) IsStatic() bool

IsStatic returns true if this is a static error

func (ErrorID) IsTrusted

func (id ErrorID) IsTrusted() bool

IsTrusted returns true if this ID was created through the proper ID() function

func (ErrorID) Level

func (id ErrorID) Level() int

Level returns the severity level

func (ErrorID) Name

func (id ErrorID) Name() string

Name returns the full error name (e.g., "AuthInvalidCredentials")

func (ErrorID) Number

func (id ErrorID) Number() int

Number returns the error number (explicitly assigned for stability)

func (ErrorID) String

func (id ErrorID) String() string

String returns the formatted error ID (e.g., "0_AUTH_0042_S")

type ErrorMatcher

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

ErrorMatcher provides pattern matching on error IDs

func Match

func Match(err error) *ErrorMatcher

Match starts a new error matcher

func (*ErrorMatcher) Case

func (m *ErrorMatcher) Case(id ErrorID, handler func(*Error)) *ErrorMatcher

Case checks if the error matches the given ID and executes the handler

func (*ErrorMatcher) CaseAny

func (m *ErrorMatcher) CaseAny(handler func(*Error), ids ...ErrorID) *ErrorMatcher

func (*ErrorMatcher) CaseDomain

func (m *ErrorMatcher) CaseDomain(handler func(*Error)) *ErrorMatcher

CaseDomain handles any domain error

func (*ErrorMatcher) CaseSystem

func (m *ErrorMatcher) CaseSystem(handler func(*Error)) *ErrorMatcher

CaseSystem handles any system error

func (*ErrorMatcher) Default

func (m *ErrorMatcher) Default(handler func(error))

Default handles any unmatched error

type ErrorType

type ErrorType string

ErrorType indicates whether the error message is static or dynamic

const (
	Static  ErrorType = "S" // Message won't change between occurrences
	Dynamic ErrorType = "D" // Message can vary per occurrence
)

type Frame

type Frame struct {
	Function string `json:"function"`
	File     string `json:"file"`
	Line     int    `json:"line"`
	Package  string `json:"package,omitempty"`
}

Frame represents a single stack frame for error traces

func CaptureStack

func CaptureStack(skip int) []Frame

CaptureStack creates a []Frame from runtime

type HookType

type HookType int
const (
	HookCreate HookType = iota
	HookLog
	HookTrace
	HookMap
	HookWrap
	HookFromFail
	HookFromSuccess
	HookForm
	HookTranslate
	HookMatch
)

type Hooks

type Hooks struct {
	OnMap []func(*Error, map[string]any)
	// contains filtered or unexported fields
}

Hooks manages lifecycle callbacks for errors Access via Registry.Hooks

func (*Hooks) On

func (h *Hooks) On(t HookType, fn any)

On registers a hook with compile-time friendly type validation (no reflect) Panics immediately if function signature doesn't match HookType

type IDRegistry

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

IDRegistry manages error ID generation and validation Numbers are explicitly assigned per domain and type (static/dynamic)

func NewIDRegistry

func NewIDRegistry() *IDRegistry

NewIDRegistry creates a new isolated ID registry (useful for testing or multi-app)

func (*IDRegistry) ExportIDList

func (r *IDRegistry) ExportIDList() ([]byte, error)

ExportIDList returns all registered error IDs as JSON for this registry

func (*IDRegistry) GetAllIDs

func (r *IDRegistry) GetAllIDs() []ErrorID

GetAllIDs returns all registered error IDs sorted by domain, type, then number

func (*IDRegistry) ID

func (r *IDRegistry) ID(name, domain string, static bool, level, number int) ErrorID

ID creates a new trusted ErrorID for this registry

func (*IDRegistry) OverrideAllowRuntimePanics added in v1.1.11

func (r *IDRegistry) OverrideAllowRuntimePanics(allow bool)

OverrideAllowRuntimePanics sets per-registry override

func (*IDRegistry) Reset

func (r *IDRegistry) Reset()

Reset clears all registered IDs (useful for testing)

func (*IDRegistry) ValidateIDs

func (r *IDRegistry) ValidateIDs()

ValidateIDs checks for gaps and duplicates. Call in main() after all init().

type Logger

type Logger interface {
	Log(err *Error)
	LogCtx(ctx context.Context, err *Error)
}

Logger allows users to provide their own logging solution

type Mapper

type Mapper interface {
	Name() string
	Priority() int

	// Map attempts to map the input error to some other error (generic or fail)
	// Returns ok = true if the mapper handled the error, false otherwise
	Map(error) (error, bool)

	// MapToFail : Optional convenience for mapping from generic error to fail.Error type
	MapToFail(err error) (*Error, bool)

	// MapFromFail : Optional convenience for mapping from fail.Error to another error type
	MapFromFail(*Error) (error, bool)
}

Mapper converts errors in any direction: generic->fail, fail->generic, fail->fail, etc.

type MapperList

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

MapperList keeps mappers sorted by priority using container/list

func NewMapperList

func NewMapperList() *MapperList

NewMapperList creates a new MapperList. If includeDefault is true, adds the default mapper with priority -1

func (*MapperList) Add

func (ml *MapperList) Add(m Mapper)

Add inserts a mapper into the list by descending priority

func (*MapperList) All

func (ml *MapperList) All() []Mapper

All returns all mappers as a slice

func (*MapperList) MapError

func (ml *MapperList) MapError(err error) (error, bool)

MapError tries to map an error using mappers in priority order

func (*MapperList) MapFromFail

func (ml *MapperList) MapFromFail(err *Error) (error, bool)

MapFromFail maps from *fail.Error

func (*MapperList) MapToFail

func (ml *MapperList) MapToFail(err error) (*Error, bool)

MapToFail maps to *fail.Error

type Registry

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

Registry holds all registered error definitions and mappers

func NewRegistry

func NewRegistry(name string) *Registry

NewRegistry creates a new isolated registry (for testing or multi-app scenarios)

func (*Registry) AllowInternalLogs added in v1.1.8

func (r *Registry) AllowInternalLogs(allow bool)

AllowInternalLogs enables or disables internal library logging for this registry. See AllowInternalLogs for details.

func (*Registry) AllowStaticMutations added in v1.1.10

func (r *Registry) AllowStaticMutations(allow bool, shouldPanic bool)

AllowStaticMutations controls whether static errors in this registry can be mutated. When allow is false (default), builder methods on static errors return the original error unchanged. When allow is true shoudlPanic is ignored, mutations are permitted but may log warnings if internal logging is enabled.

If shouldPanic is true and allow is false, any builder method called on a static error will panic immediately with a descriptive message. This is useful for catching programming errors during development.

This method is safe for concurrent use.

func (*Registry) Form

func (r *Registry) Form(id ErrorID, defaultMsg string, isSystem bool, meta map[string]any, defaultArgs ...any) *Error

func (*Registry) From

func (r *Registry) From(err error) *Error

func (*Registry) New

func (r *Registry) New(id ErrorID) *Error

func (*Registry) On

func (r *Registry) On(t HookType, fn any)

On is a convenience for setting hooks on custom registries

func (*Registry) Register

func (r *Registry) Register(err *Error)

Register adds an error definition to this registry

func (*Registry) RegisterMany

func (r *Registry) RegisterMany(defs ...*ErrorDefinition)

func (*Registry) RegisterMapper

func (r *Registry) RegisterMapper(mapper Mapper)

func (*Registry) RegisterTranslations

func (r *Registry) RegisterTranslations(locale string, msgs map[ErrorID]string)

RegisterTranslations adds translations for a locale in a specific registry

func (*Registry) RegisterTranslator

func (r *Registry) RegisterTranslator(t Translator) error

func (*Registry) SetDefaultLocale

func (r *Registry) SetDefaultLocale(locale string)

SetDefaultLocale sets the fallback locale for the specific registry

func (*Registry) SetLogger

func (r *Registry) SetLogger(logger Logger)

func (*Registry) SetTracer

func (r *Registry) SetTracer(tracer Tracer)

func (*Registry) Translate

func (r *Registry) Translate(err *Error, translatorName string) (out any, retErr error)

Translate converts an error using the named translator

type RetryConfig

type RetryConfig struct {
	MaxAttempts int
	ShouldRetry func(error) bool

	// Delay returns how long to wait BEFORE the next attempt.
	// attempt starts at 1 for the first retry (not the first call).
	Delay func(attempt int) time.Duration
}

RetryConfig helper for transient errors

type Tracer

type Tracer interface {
	Trace(operation string, fn func() error) error
	TraceCtx(ctx context.Context, operation string, fn func(context.Context) error) error
}

Tracer allows users to provide their own tracing solution

type TranslationRegistry

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

TranslationRegistry holds all translations

type Translator

type Translator interface {
	Name() string

	// Supports reports whether this translator is allowed to translate this error.
	// This is for capability, boundary, or policy checks — not mapping.
	Supports(*Error) error

	// Translate converts the error into an external representation.
	// It may still fail even if Supports returned true (e.g. missing metadata).
	Translate(*Error) (any, error)
}

Translator converts a trusted registry Error into another format (HTTP response, gRPC status, CLI output, etc.)

type ValidationError

type ValidationError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

ValidationError represents a field validation error

func GetValidations

func GetValidations(err error) ([]ValidationError, bool)

GetValidations extracts validation errors from an error

Jump to

Keyboard shortcuts

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