Documentation
¶
Overview ¶
Package assert provides always-on runtime assertions for detecting programming bugs.
Unlike test assertions, these assertions are intended to remain enabled in production code. They are designed for detecting invariant violations, programming errors, and impossible states - NOT for input validation or expected error conditions.
Design Philosophy ¶
Assertions are for catching bugs, not for handling user input:
- Use assertions for conditions that should NEVER be false if the code is correct
- Use error returns for conditions that CAN legitimately fail (I/O, user input, etc.)
- Assertions return errors so callers can stop execution immediately
Good assertion usage:
a := assert.New(ctx, logger, "transaction", "create")
if err := a.NotNil(ctx, config, "config must be loaded before server starts"); err != nil {
return err
}
if err := a.That(ctx, len(items) > 0, "processItems called with empty slice"); err != nil {
return err
}
Bad assertion usage (use error returns instead):
// DON'T: User input validation _ = a.That(ctx, email != "", "email is required") // Use validation errors // DON'T: I/O that can fail _ = a.NoError(ctx, file.Read(), "file must read") // Use proper error handling
Core Assertion Methods ¶
The package provides five core assertion methods on Asserter:
a.That(ctx context.Context, ok bool, msg string, kv ...any) error
Returns an error if ok is false. General-purpose assertion.
a.NotNil(ctx context.Context, v any, msg string, kv ...any) error
Returns an error if v is nil. Handles both untyped nil and typed nil (nil interface
values with concrete types).
a.NotEmpty(ctx context.Context, s string, msg string, kv ...any) error
Returns an error if s is an empty string.
a.NoError(ctx context.Context, err error, msg string, kv ...any) error
Returns an error if err is not nil. Automatically includes the error in context.
a.Never(ctx context.Context, msg string, kv ...any) error
Always returns an error. Use for unreachable code paths.
Key-Value Context ¶
All assertion methods accept optional key-value pairs to provide context in logs and errors:
if err := a.That(ctx, balance >= 0, "balance must not be negative",
"account_id", accountID,
"balance", balance,
); err != nil {
return err
}
The error message will include:
assertion failed: balance must not be negative
assertion=That
account_id=550e8400-e29b-41d4-a716-446655440000
balance=-100
Odd numbers of key-value arguments are handled gracefully with a "MISSING_VALUE" marker.
Domain Predicates ¶
The package includes predicate functions for common domain validations:
// Numeric predicates assert.Positive(n int64) bool // n > 0 assert.NonNegative(n int64) bool // n >= 0 assert.NotZero(n int64) bool // n != 0 assert.InRange(n, min, max int64) bool // min <= n <= max // String predicates assert.ValidUUID(s string) bool // valid UUID format // Financial predicates (using shopspring/decimal) assert.ValidAmount(d decimal.Decimal) bool // exponent in [-18, 18] assert.ValidScale(scale int) bool // scale in [0, 18] assert.PositiveDecimal(d decimal.Decimal) bool // d > 0 assert.NonNegativeDecimal(d decimal.Decimal) bool // d >= 0
Use predicates with Asserter:
if err := a.That(ctx, assert.Positive(count), "count must be positive", "count", count); err != nil {
return err
}
if err := a.That(ctx, assert.ValidUUID(id), "invalid UUID", "id", id); err != nil {
return err
}
Usage Examples ¶
Pre-conditions (validate inputs at function entry):
func ProcessTransaction(ctx context.Context, tx *Transaction) error {
a := assert.New(ctx, logger, "transaction", "process")
if err := a.NotNil(ctx, tx, "transaction must not be nil"); err != nil {
return err
}
if err := a.NotEmpty(ctx, tx.ID, "transaction must have ID", "tx", tx); err != nil {
return err
}
// ... rest of function
}
Post-conditions (validate outputs before return):
func CreateAccount(ctx context.Context, name string) (*Account, error) {
a := assert.New(ctx, logger, "account", "create")
acc := &Account{ID: uuid.New(), Name: name}
if err := a.NotNil(ctx, acc.ID, "created account must have ID"); err != nil {
return nil, err
}
return acc, nil
}
Unreachable code:
switch status {
case Active:
return handleActive()
case Inactive:
return handleInactive()
case Deleted:
return handleDeleted()
default:
return a.Never(ctx, "unhandled status", "status", status)
}
Goroutine Halting ¶
In goroutines, use Halt to stop execution after a failed assertion:
go func() {
a := assert.New(ctx, logger, "transaction", "sync")
if err := a.That(ctx, ready, "sync not ready"); err != nil {
a.Halt(err)
}
// ... rest of goroutine
}()
Observability Integration ¶
Failed assertions emit telemetry signals:
Metrics: Records assertion_failed_total with component/operation/assertion labels. Initialize with InitAssertionMetrics(metricsFactory).
Tracing: Records assertion.failed span events (with stack traces in non-prod). Automatically uses the span from the context.
Stack Traces ¶
Stack traces are included in logs and trace events only in non-production environments (ENV != production and GO_ENV != production).
Index ¶
- Constants
- Variables
- func BalanceIsZero(available, onHold decimal.Decimal) bool
- func BalanceSufficientForRelease(onHold, releaseAmount decimal.Decimal) bool
- func DateAfter(date, reference time.Time) bool
- func DateNotInFuture(date time.Time) bool
- func DebitsEqualCredits(debits, credits decimal.Decimal) bool
- func InRange(n, minVal, maxVal int64) bool
- func InRangeInt(n, minVal, maxVal int) bool
- func InitAssertionMetrics(factory *metrics.MetricsFactory)
- func NonNegative(n int64) bool
- func NonNegativeDecimal(amount decimal.Decimal) bool
- func NonZeroTotals(debits, credits decimal.Decimal) bool
- func NotZero(n int64) bool
- func Positive(n int64) bool
- func PositiveDecimal(amount decimal.Decimal) bool
- func PositiveInt(n int) bool
- func ResetAssertionMetrics()
- func TransactionCanBeReverted(status string, hasParent bool) bool
- func TransactionCanTransitionTo(current, target string) bool
- func TransactionHasOperations(operations []string) bool
- func TransactionOperationsMatch(operations, allowed []string) bool
- func ValidAmount(amount decimal.Decimal) bool
- func ValidPort(port string) bool
- func ValidSSLMode(mode string) bool
- func ValidScale(scale int) bool
- func ValidTransactionStatus(status string) bool
- func ValidUUID(s string) bool
- type Asserter
- func (asserter *Asserter) Halt(err error)
- func (asserter *Asserter) Never(ctx context.Context, msg string, kv ...any) error
- func (asserter *Asserter) NoError(ctx context.Context, err error, msg string, kv ...any) error
- func (asserter *Asserter) NotEmpty(ctx context.Context, s, msg string, kv ...any) error
- func (asserter *Asserter) NotNil(ctx context.Context, v any, msg string, kv ...any) error
- func (asserter *Asserter) That(ctx context.Context, ok bool, msg string, kv ...any) error
- type AssertionError
- type AssertionMetrics
- type Logger
Constants ¶
const AssertionSpanEventName = constant.EventAssertionFailed
AssertionSpanEventName is the event name used when recording assertion failures on spans.
Variables ¶
var ErrAssertionFailed = errors.New("assertion failed")
ErrAssertionFailed is the sentinel error for failed assertions.
Functions ¶
func BalanceIsZero ¶
BalanceIsZero returns true if both available and onHold balances are exactly zero.
func BalanceSufficientForRelease ¶
BalanceSufficientForRelease returns true if the available on-hold balance is sufficient to release the specified amount.
func DateNotInFuture ¶
DateNotInFuture returns true if the date is not in the future (i.e., <= now). Zero time is considered valid (returns true).
func DebitsEqualCredits ¶
DebitsEqualCredits returns true if debits and credits are exactly equal. This validates the fundamental double-entry accounting invariant: for every transaction, total debits MUST equal total credits.
Note: Uses decimal.Equal() for exact comparison without floating point issues. Even a tiny difference indicates a bug in amount calculation.
Example:
a.That(ctx, assert.DebitsEqualCredits(debitTotal, creditTotal),
"double-entry violation: debits must equal credits",
"debits", debitTotal, "credits", creditTotal)
func InRange ¶
InRange returns true if min <= n <= max.
Note: If min > max (inverted range), always returns false. This is fail-safe behavior - callers should ensure min <= max for correct results.
Example:
a.That(ctx, assert.InRange(page, 1, 1000), "page out of range", "page", page)
func InRangeInt ¶
InRangeInt returns true if min <= n <= max. This is the int variant of InRange (which uses int64).
Note: If min > max (inverted range), always returns false. This is fail-safe behavior - callers should ensure min <= max for correct results.
Example:
a.That(ctx, assert.InRangeInt(cfg.PoolSize, 1, 100), "POOL_SIZE out of range", "value", cfg.PoolSize)
func InitAssertionMetrics ¶
func InitAssertionMetrics(factory *metrics.MetricsFactory)
InitAssertionMetrics initializes assertion metrics with the provided MetricsFactory. This should be called once during application startup after telemetry is initialized.
func NonNegative ¶
NonNegative returns true if n >= 0.
Example:
a.That(ctx, assert.NonNegative(balance), "balance must not be negative", "balance", balance)
func NonNegativeDecimal ¶
NonNegativeDecimal returns true if amount >= 0.
Example:
a.That(ctx, assert.NonNegativeDecimal(balance), "balance must not be negative", "balance", balance)
func NonZeroTotals ¶
NonZeroTotals returns true if both debits and credits are non-zero. A transaction with zero totals is meaningless and indicates a bug.
Example:
a.That(ctx, assert.NonZeroTotals(debitTotal, creditTotal),
"transaction totals must be non-zero",
"debits", debitTotal, "credits", creditTotal)
func NotZero ¶
NotZero returns true if n != 0.
Example:
a.That(ctx, assert.NotZero(divisor), "divisor must not be zero", "divisor", divisor)
func Positive ¶
Positive returns true if n > 0.
Example:
a.That(ctx, assert.Positive(count), "count must be positive", "count", count)
func PositiveDecimal ¶
PositiveDecimal returns true if amount > 0.
Example:
a.That(ctx, assert.PositiveDecimal(price), "price must be positive", "price", price)
func PositiveInt ¶
PositiveInt returns true if n > 0. This is the int variant of Positive (which uses int64).
Example:
a.That(ctx, assert.PositiveInt(cfg.MaxWorkers), "MAX_WORKERS must be positive", "value", cfg.MaxWorkers)
func ResetAssertionMetrics ¶
func ResetAssertionMetrics()
ResetAssertionMetrics clears the assertion metrics singleton (useful for tests).
func TransactionCanBeReverted ¶
TransactionCanBeReverted returns true if a transaction can be reverted. The transaction can only be reverted if:
- Status is APPROVED
- It has no parent transaction (i.e., it is not a reversal of another transaction)
This ensures only original transactions can be reverted, not reversals.
func TransactionCanTransitionTo ¶
TransactionCanTransitionTo returns true if transitioning from current to target is valid. The transaction state machine only allows: PENDING -> APPROVED or PENDING -> CANCELED.
Note: This is for forward transitions only. Revert is a separate operation.
Example:
a.That(ctx, assert.TransactionCanTransitionTo(current, next),
"invalid status transition",
"current", current,
"next", next)
func TransactionHasOperations ¶
TransactionHasOperations returns true if the transaction has operations.
func TransactionOperationsMatch ¶
TransactionOperationsMatch returns true if the operations match allowed operations.
func ValidAmount ¶
ValidAmount returns true if the decimal's exponent is within reasonable bounds. The exponent must be in the range [-18, 18] to align with supported precision for financial calculations (scale up to 18 decimal places).
Note: This validates exponent bounds only, not coefficient size. For user-facing validation, consider additional bounds checks on the coefficient.
Example:
a.That(ctx, assert.ValidAmount(amount), "amount has invalid precision", "amount", amount)
func ValidPort ¶
ValidPort returns true if port is a valid network port number (1-65535). The port must be a numeric string representing a value in the valid range.
Note: Port 0 is invalid for configuration purposes (it's used for dynamic allocation). Empty strings, non-numeric values, and out-of-range values return false.
Example:
a.That(ctx, assert.ValidPort(cfg.DBPort), "DB_PORT must be valid port", "port", cfg.DBPort)
func ValidSSLMode ¶
ValidSSLMode returns true if mode is a valid PostgreSQL SSL mode. Valid modes are: disable, allow, prefer, require, verify-ca, verify-full. Empty string is also valid (uses PostgreSQL default).
Note: SSL modes are case-sensitive per PostgreSQL documentation. Unknown modes will cause connection failures.
Example:
a.That(ctx, assert.ValidSSLMode(cfg.DBSSLMode), "DB_SSLMODE invalid", "mode", cfg.DBSSLMode)
func ValidScale ¶
ValidScale returns true if scale is in the range [0, 18]. Scale represents the number of decimal places for financial amounts.
Example:
a.That(ctx, assert.ValidScale(scale), "invalid scale", "scale", scale)
func ValidTransactionStatus ¶
ValidTransactionStatus returns true if status is a valid transaction status. Valid statuses are: CREATED, APPROVED, PENDING, CANCELED, NOTED.
Note: Statuses are case-sensitive and must match exactly.
Example:
a.That(ctx, assert.ValidTransactionStatus(tran.Status.Code),
"invalid transaction status",
"status", tran.Status.Code)
Types ¶
type Asserter ¶
type Asserter struct {
// contains filtered or unexported fields
}
Asserter evaluates invariants and emits telemetry on failure.
func New ¶
New creates an Asserter with context, logging, and labels. component and operation are used for telemetry labeling.
func (*Asserter) Halt ¶
Halt terminates the current goroutine if err is not nil. Use this after a failed assertion in goroutines to prevent further execution.
func (*Asserter) Never ¶
Never always returns an error. Use for code paths that should be unreachable.
Example:
return asserter.Never(ctx, "unhandled status", "status", status)
func (*Asserter) NoError ¶
NoError returns an error if err is not nil. The error message and type are automatically included in the assertion context for debugging.
Example:
if err := asserter.NoError(ctx, err, "compute must succeed", "input", input); err != nil {
return err
}
func (*Asserter) NotEmpty ¶
NotEmpty returns an error if s is an empty string.
Example:
if err := asserter.NotEmpty(ctx, userID, "userID must be provided"); err != nil {
return err
}
func (*Asserter) NotNil ¶
NotNil returns an error if v is nil. This function correctly handles both untyped nil and typed nil (nil interface values with concrete types).
Example:
if err := asserter.NotNil(ctx, config, "config must be initialized"); err != nil {
return err
}
type AssertionError ¶
type AssertionError struct {
Assertion string
Message string
Component string
Operation string
Details string
}
AssertionError represents a failed assertion with rich context.
func (*AssertionError) Error ¶
func (entry *AssertionError) Error() string
Error returns the formatted assertion failure message.
func (*AssertionError) Unwrap ¶
func (entry *AssertionError) Unwrap() error
Unwrap returns the sentinel assertion error for errors.Is.
type AssertionMetrics ¶
type AssertionMetrics struct {
// contains filtered or unexported fields
}
AssertionMetrics provides assertion-related metrics using OpenTelemetry. It wraps lib-uncommons' MetricsFactory for consistent metric handling.
func GetAssertionMetrics ¶
func GetAssertionMetrics() *AssertionMetrics
GetAssertionMetrics returns the singleton AssertionMetrics instance. Returns nil if InitAssertionMetrics has not been called.
func (*AssertionMetrics) RecordAssertionFailed ¶
func (am *AssertionMetrics) RecordAssertionFailed( ctx context.Context, component, operation, assertion string, )
RecordAssertionFailed increments the assertion_failed_total counter with labels. If metrics are not initialized, this is a no-op.