faults

package module
v1.0.0 Latest Latest
Warning

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

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

README

faults-go

CI Go Reference

faults-go is a small Go library for creating stable, structured application faults. It wraps errbuilder-go for transport-aware error values and integrates assert-lib for assertion reporting.

Use it when application code needs a durable fault code such as validation.failed while process boundaries still need transport-oriented status codes such as InvalidArgument, Internal, or NotFound.

Installation

go get github.com/ZanzyTHEbar/faults-go

API overview

  • New(code, msg, fields...) creates a structured fault.
  • Wrap(code, msg, cause, fields...) wraps an existing error and returns nil for a nil cause.
  • NewHandler(opts...) creates an assert-lib backed handler for checks, no-error assertions, panic recovery, deferred assertions, and scoped operations.
  • CodeOf(err) and IsCode(err, code) inspect application fault labels.
  • ErrCodeOf(err) inspects the underlying errbuilder transport code.
  • RegisterCode(code, transport) and RegisterCodes(mapping) add or replace process-wide code-to-transport mappings.
  • NewDetails(fields...) and NormalizeFields(fields...) normalize variadic key/value details before storing them on structured errors.

Creating faults

package users

import "github.com/ZanzyTHEbar/faults-go"

func ValidateEmail(email string) error {
    if email == "" {
        return faults.New(
            faults.CodeValidationFailed,
            "email is required",
            "field", "email",
        )
    }
    return nil
}

New stores the application code as the errbuilder label, maps it to a transport code, and attaches normalized details.

Wrapping errors

func LoadUser(id string) error {
    user, err := repository.Find(id)
    if err != nil {
        return faults.Wrap(
            faults.CodeDatabaseRead,
            "load user",
            err,
            "user_id", id,
        )
    }

    _ = user
    return nil
}

Wrap preserves the original cause for errors.Is and errors.As. Context cancellation and deadline causes are mapped to TransportCanceled and TransportDeadlineExceeded automatically.

Handler and assertion integration

package workers

import (
    "bytes"
    "context"

    "github.com/ZanzyTHEbar/faults-go"
)

func Run(ctx context.Context) error {
    var assertionOutput bytes.Buffer
    handler := faults.NewHandler(
        faults.WithTextFormatter(),
        faults.WithWriter(&assertionOutput),
        faults.WithMetadata("component", "worker"),
    )

    if err := handler.Check(ctx, ctx != nil, faults.CodeInvariantFailed, "context is required"); err != nil {
        return err
    }

    op := handler.Operation(ctx, faults.CodeRuntimeFailed, "job", "sync")
    return op.NoError(doWork(ctx), "run worker")
}

Handlers report failures through assert-lib and return structured faults from the same call site. The package-level Check, NoError, Never, and Recover helpers use a default handler.

Inspecting fault codes

err := faults.New(faults.CodeNotFound, "user not found", "user_id", "u_123")

if faults.IsCode(err, faults.CodeNotFound) {
    // handle missing user
}

code := faults.CodeOf(err)       // "not_found"
transport := faults.ErrCodeOf(err) // faults.TransportNotFound

Use CodeOf and IsCode for application behavior. Use ErrCodeOf or TransportCodeOf at process boundaries that need errbuilder transport classes.

Registering project codes

const CodePaymentDeclined faults.Code = "payment.declined"

func init() {
    faults.RegisterCode(CodePaymentDeclined, faults.TransportFailedPrecondition)
}

Multiple mappings can be registered at once:

faults.RegisterCodes(faults.Mapping{
    "billing.customer_missing": faults.TransportNotFound,
    "billing.limit_reached":    faults.TransportResourceExhausted,
})

Registration is process-wide. Register project-specific codes during process startup before faults are created.

Details normalization

Details are passed as variadic key/value pairs:

err := faults.New(
    faults.CodeConfigInvalid,
    "invalid configuration",
    "path", "database.host",
    "value", "",
)

Normalization rules are intentionally stable:

  • keys are converted with fmt.Sprint;
  • nil keys become generated names such as field_0;
  • nil values become faults.NilFieldValue;
  • odd field counts add faults.FieldErrorKey with "odd field count" and store the final key with faults.MissingFieldValue;
  • error values are stored as errors in errbuilder details and render safely when marshaled.

Code-to-transport mapping

Built-in fault codes are mapped to errbuilder transport codes:

Fault code Transport code
CodeConfigRequired TransportInvalidArgument
CodeConfigInvalid TransportInvalidArgument
CodeValidationFailed TransportInvalidArgument
CodeAuthRequired TransportUnauthenticated
CodeDependencyMissing TransportFailedPrecondition
CodeInvariantFailed TransportInternal
CodeNotFound TransportNotFound
CodeResourceLimit TransportResourceExhausted
CodeDatabaseRead TransportInternal
CodeDatabaseWrite TransportInternal
CodeStorageFailed TransportInternal
CodeRuntimeFailed TransportInternal

Unknown or empty codes normalize to CodeUnknown and map to TransportUnknown unless registered explicitly.

Releases

Releases are automated with GitHub Actions and semantic-release:

  1. CI runs formatting, tests, vet, native build, and cross-builds.
  2. Pushes to main or master run semantic-release with Conventional Commits.
  3. semantic-release updates CHANGELOG.md, creates the GitHub release and tag, and publishes release assets.
  4. The Go publication job requests the new version from proxy.golang.org and pkg.go.dev so documentation is indexed.

Local publication checks can be simulated with:

./scripts/test-publication.sh

License

MIT. See LICENSE.

Documentation

Overview

Package faults provides structured application fault errors backed by errbuilder-go and assertion reporting backed by assert-lib.

Fault codes are stable project labels such as "validation.failed" or a project-specific code registered with RegisterCode. Transport codes are the errbuilder status class used at process boundaries.

Index

Constants

View Source
const (
	FieldErrorKey     = "fault_field_error"
	MissingFieldValue = "<missing>"
	NilFieldValue     = "<nil>"
)

Variables

This section is empty.

Functions

func Check

func Check(ctx context.Context, truth bool, code Code, msg string, fields ...any) error

Check reports and returns a structured fault with a default Handler when truth is false.

func IsCode

func IsCode(err error, code Code) bool

IsCode reports whether err carries code as its application fault label.

func Never

func Never(ctx context.Context, code Code, msg string, fields ...any) error

Never reports and returns a structured fault with a default Handler.

func New

func New(code Code, msg string, fields ...any) error

New returns a structured fault with code, msg, and normalized details.

func NewDetails

func NewDetails(fields ...any) errbuilder.ErrDetails

NewDetails converts key/value fields into errbuilder details.

func NoError

func NoError(ctx context.Context, err error, code Code, msg string, fields ...any) error

NoError reports and wraps err with a default Handler when err is non-nil.

func Recover

func Recover(ctx context.Context, recovered any, code Code, msg string, fields ...any) error

Recover converts a recovered panic payload with a default Handler.

func RegisterCode

func RegisterCode(code Code, transport TransportCode)

RegisterCode registers or replaces a process-wide transport mapping for a project-specific fault code. Call it during process initialization. Empty codes are ignored.

func RegisterCodes

func RegisterCodes(mapping Mapping)

RegisterCodes registers or replaces multiple code mappings.

func Wrap

func Wrap(code Code, msg string, cause error, fields ...any) error

Wrap returns a structured fault that wraps cause. Nil causes return nil.

Types

type Code

type Code string

Code is a stable, project-owned fault label stored on structured errors and assertion events. Codes are application semantics; transport codes are mapped separately through errbuilder.

const (
	CodeUnknown Code = "unknown"

	CodeConfigRequired   Code = "config.required"
	CodeConfigInvalid    Code = "config.invalid"
	CodeValidationFailed Code = "validation.failed"

	CodeAuthRequired Code = "auth.required"

	CodeDependencyMissing Code = "dependency.missing"
	CodeInvariantFailed   Code = "invariant.failed"
	CodeNotFound          Code = "not_found"
	CodeResourceLimit     Code = "resource.limit"

	CodeDatabaseRead  Code = "database.read"
	CodeDatabaseWrite Code = "database.write"
	CodeStorageFailed Code = "storage.failed"
	CodeRuntimeFailed Code = "runtime.failed"
)

func CodeOf

func CodeOf(err error) Code

CodeOf returns the application fault code stored on err. It returns CodeUnknown when err is nil or is not a faults/errbuilder error.

func NormalizeCode

func NormalizeCode(code Code) Code

NormalizeCode returns CodeUnknown for empty labels and trims surrounding whitespace from user-provided codes.

func (Code) ErrCode

func (c Code) ErrCode() TransportCode

ErrCode returns the errbuilder transport code for c.

func (Code) String

func (c Code) String() string

type Field

type Field struct {
	Key   string
	Value any
}

Field is a normalized detail key/value pair.

func NormalizeFields

func NormalizeFields(fields ...any) []Field

NormalizeFields converts variadic key/value fields into stable string keys. Odd field counts are preserved with FieldErrorKey and a MissingFieldValue for the last key.

type Handler

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

Handler integrates structured faults with assert-lib assertion reporting.

func NewHandler

func NewHandler(opts ...Option) *Handler

NewHandler returns a Handler with production-safe defaults: JSON assertions to stderr and a no-op exit function.

func (*Handler) AddData

func (h *Handler) AddData(key string, value any)

AddData adds assertion metadata to an existing handler.

func (*Handler) AddFlush

func (h *Handler) AddFlush(flusher assert.AssertFlush)

AddFlush registers an assert-lib flusher.

func (*Handler) AssertHandler

func (h *Handler) AssertHandler() *assert.AssertHandler

AssertHandler returns the underlying assert-lib handler.

func (*Handler) Check

func (h *Handler) Check(ctx context.Context, truth bool, code Code, msg string, fields ...any) error

Check reports and returns a structured fault when truth is false.

func (*Handler) Never

func (h *Handler) Never(ctx context.Context, code Code, msg string, fields ...any) error

Never reports and returns a structured fault for unreachable paths.

func (*Handler) NoError

func (h *Handler) NoError(ctx context.Context, err error, code Code, msg string, fields ...any) error

NoError reports and wraps err when it is non-nil.

func (*Handler) Operation

func (h *Handler) Operation(ctx context.Context, code Code, fields ...any) Operation

Operation binds context, code, and common fields for repeated checks.

func (*Handler) ProcessDeferred

func (h *Handler) ProcessDeferred(ctx context.Context)

ProcessDeferred flushes deferred assertions.

func (*Handler) Recover

func (h *Handler) Recover(ctx context.Context, recovered any, code Code, msg string, fields ...any) error

Recover converts a recovered panic payload into a structured fault.

type Mapping

type Mapping map[Code]TransportCode

Mapping declares fault-code to transport-code mappings. Use RegisterCodes at process startup for project-specific fault codes.

type Operation

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

Operation is a scoped assertion/fault helper.

func (Operation) Check

func (op Operation) Check(truth bool, msg string, fields ...any) error

Check reports and returns a structured fault when truth is false.

func (Operation) Fail

func (op Operation) Fail(msg string, fields ...any) error

Fail reports and returns a structured fault for a failed operation.

func (Operation) NoError

func (op Operation) NoError(err error, msg string, fields ...any) error

NoError reports and wraps err using the operation code and fields.

type Option

type Option func(*handlerOptions)

Option configures a Handler.

func WithDebugMode

func WithDebugMode() Option

WithDebugMode enables stack traces in assertion output.

func WithDeferMode

func WithDeferMode() Option

WithDeferMode defers assertion failures until ProcessDeferred is called.

func WithExitFunc

func WithExitFunc(exitFunc func(int)) Option

WithExitFunc sets the assertion failure exit function.

func WithFormatter

func WithFormatter(formatter assert.Formatter) Option

WithFormatter sets the assert-lib formatter.

func WithMetadata

func WithMetadata(key string, value any) Option

WithMetadata adds static assertion metadata to every handler assertion.

func WithTextFormatter

func WithTextFormatter() Option

WithTextFormatter uses assert-lib's text formatter.

func WithVerboseMode

func WithVerboseMode() Option

WithVerboseMode enables verbose assert-lib output.

func WithWriter

func WithWriter(writer io.Writer) Option

WithWriter sets the assertion output writer.

type TransportCode

type TransportCode = errbuilder.ErrCode

TransportCode is the errbuilder transport/status class used by RPC, HTTP, and other boundaries.

const (
	TransportCanceled           TransportCode = errbuilder.CodeCanceled
	TransportUnknown            TransportCode = errbuilder.CodeUnknown
	TransportInvalidArgument    TransportCode = errbuilder.CodeInvalidArgument
	TransportDeadlineExceeded   TransportCode = errbuilder.CodeDeadlineExceeded
	TransportNotFound           TransportCode = errbuilder.CodeNotFound
	TransportAlreadyExists      TransportCode = errbuilder.CodeAlreadyExists
	TransportPermissionDenied   TransportCode = errbuilder.CodePermissionDenied
	TransportResourceExhausted  TransportCode = errbuilder.CodeResourceExhausted
	TransportFailedPrecondition TransportCode = errbuilder.CodeFailedPrecondition
	TransportAborted            TransportCode = errbuilder.CodeAborted
	TransportOutOfRange         TransportCode = errbuilder.CodeOutOfRange
	TransportUnimplemented      TransportCode = errbuilder.CodeUnimplemented
	TransportInternal           TransportCode = errbuilder.CodeInternal
	TransportUnavailable        TransportCode = errbuilder.CodeUnavailable
	TransportDataLoss           TransportCode = errbuilder.CodeDataLoss
	TransportUnauthenticated    TransportCode = errbuilder.CodeUnauthenticated
)

func ErrCodeOf

func ErrCodeOf(err error) TransportCode

ErrCodeOf returns the errbuilder transport code carried by err.

func TransportCodeOf

func TransportCodeOf(code Code) TransportCode

TransportCodeOf returns the registered transport mapping for code.

Jump to

Keyboard shortcuts

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