codex

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 3 Imported by: 11

Documentation

Overview

Package codex identifies errors by typed integer codes.

A code answers one question: which error is this. codex carries the code with the error and lets callers find it again. It does not define storage, encoding or presentation of codes. Capabilities such as an HTTP status code are declared on the code type and interpreted by whoever consumes them.

Code Types

A code type is an integer type satisfying Code, whose values always fit in int64. It may optionally implement:

  • ErrorMessage: describes the code; used as the error message.
  • CanBeCode: lists all values of the code type.

Creating Errors

  • New: an error carrying a code.
  • Errorf: like New, with a formatted message.
  • Wrap, Wrapf: an error carrying a code and caused by another error. They return nil if the cause is nil.

Matching with a Known Code Type

  • IsCode: whether the chain carries a specific code.
  • Is: whether the chain carries any code of type C.
  • As, AsCode: the first error or code of type C in the chain.

Consuming with an Unknown Code Type

Callers such as transport layers or loggers see only `error`. They define their own capability interfaces and look them up on the carried code:

  • errors.As, errors.AsType (recommended): an error carrying a code can be viewed as its code, so the lookup goes through the whole chain. The target must be an interface type, or implement error; errors.AsType requires the latter, so the code type must implement error to be found by it.
  • Valuer: returns the carried code of a single error, for callers that walk the chain by themselves.

See the package examples for usage.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/xoctopus/x/codex"
)

type OrderError int8

const (
	ORDER_ERROR_UNDEFINED OrderError = iota
	ORDER_ERROR__NOT_FOUND
	ORDER_ERROR__CLOSED
)

func (e OrderError) Message() string {
	switch e {
	case ORDER_ERROR__NOT_FOUND:
		return "order not found"
	case ORDER_ERROR__CLOSED:
		return "order closed"
	default:
		return "undefined"
	}
}

func (OrderError) CodeValues() []any {
	return []any{ORDER_ERROR__NOT_FOUND, ORDER_ERROR__CLOSED}
}

// StatusCode is a capability defined for consumers, codex is unaware of it.
func (e OrderError) StatusCode() int {
	switch e {
	case ORDER_ERROR__NOT_FOUND:
		return 404
	case ORDER_ERROR__CLOSED:
		return 409
	default:
		return 0
	}
}

// StatusDescriber is defined by the consumer which needs the capability.
type StatusDescriber interface {
	StatusCode() int
}

func main() {
	cause := errors.New("sql: no rows")
	err := fmt.Errorf("query order: %w", codex.Wrap(ORDER_ERROR__NOT_FOUND, cause))
	fmt.Println(err)

	// the code type is known
	fmt.Println(codex.IsCode(err, ORDER_ERROR__NOT_FOUND))
	code, _ := codex.AsCode[OrderError](err)
	fmt.Println(code.Message())

	// the code type is unknown: view the error as its code
	var d StatusDescriber
	if errors.As(err, &d) {
		fmt.Println(d.StatusCode())
	}

}
Output:
query order: order not found. [cause: sql: no rows]
true
order not found
404

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsCode added in v0.4.5

func AsCode[C Code](e error) (C, bool)

AsCode is like As, but returns the carried code directly.

func Errorf

func Errorf[C Code](e C, format string, args ...any) error

Errorf returns an error carrying code e, with a formatted message appended to the code message.

func Is added in v0.2.7

func Is[C Code](e error) bool

Is reports whether any error in e's chain carries a code of type C.

func IsCode added in v0.2.7

func IsCode[C Code](e error, code C) bool

IsCode reports whether any error in e's chain carries code.

func New

func New[C Code](e C) error

New returns an error carrying code e.

func Wrap

func Wrap[C Code](e C, cause error) error

Wrap returns an error carrying code e and caused by cause. It returns nil if cause is nil.

func Wrapf

func Wrapf[C Code](e C, cause error, format string, args ...any) error

Wrapf is like Wrap, with a formatted message appended to the code message.

Types

type CanBeCode added in v0.6.0

type CanBeCode interface {
	// CodeValues returns all defined values of the code type.
	CodeValues() []any
}

CanBeCode is implemented by code types that can list all of their values, for example to document every code of a domain.

Example
var c codex.CanBeCode = ORDER_ERROR_UNDEFINED

for _, v := range c.CodeValues() {
	fmt.Printf("%d %s\n", v, v.(codex.ErrorMessage).Message())
}
Output:
1 order not found
2 order closed

type Code

type Code interface {
	~int | ~int8 | ~int16 | ~int32 | ~uint8 | ~uint16 | ~uint32
}

Code constrains the underlying integer type of an error code. It uses Go's type approximation (~int, ~int8, etc.) to allow integer-based types whose values always fit in int64, so ~uint is excluded.

type Error

type Error[C Code] interface {
	// Code returns typed code value
	Code() C
	// Error implements an error
	Error() string
	// Unwrap unwraps caused error
	Unwrap() error
	// Is returns if input is same type of CodeError and with same code
	Is(error) bool
}

Error is a generic interface defining an error with a typed code. It extends the standard error interface with methods to access the typed code, unwrap the underlying cause, and compare errors by code. The generic parameter C must satisfy the Code interface.

Example
fmt.Println(New(ECODE_UNDEFINED).Error())
e0 := Errorf(ECODE__REASON1, "user message: %d", 1)
fmt.Println(e0.Error())
e1 := Wrap(ECODE__REASON1, errors.New("cause1"))
fmt.Println(e1.Error())
fmt.Printf("cause by unwrapping: %v\n", errors.Unwrap(e1))
e2 := Wrapf(ECODE__REASON2, errors.New("cause2"), "user message: %s", "any")
fmt.Println(e2.Error())
fmt.Printf("cause by unwrapping: %v\n", errors.Unwrap(e2))

fmt.Printf("expecting true errors.Is(e0, e1): %v\n", errors.Is(e0, e1))
fmt.Printf("expecting false errors.Is(e1, e2): %v\n", errors.Is(e1, e2))
fmt.Printf("expecting nil Wrap(ECODE__REASON1, nil): %v\n", Wrap(ECODE__REASON1, nil))
fmt.Printf("expecting nil Wrapf(ECODE__REASON2, nil): %v\n", Wrapf(ECODE__REASON2, nil, "whatever"))

e3 := New(ECode3(2))
fmt.Printf("expect ecode without Message: %s\n", e3.Error())
Output:
[region:1] undefined
[region:2] reason1. user message: 1
[region:2] reason1. [cause: cause1]
cause by unwrapping: cause1
[region:3] reason2. user message: any. [cause: cause2]
cause by unwrapping: cause2
expecting true errors.Is(e0, e1): true
expecting false errors.Is(e1, e2): false
expecting nil Wrap(ECODE__REASON1, nil): <nil>
expecting nil Wrapf(ECODE__REASON2, nil): <nil>
expect ecode without Message: codex_test.ECode3[2]

func As added in v0.2.7

func As[C Code](e error) (Error[C], bool)

As finds the first error in e's chain carrying a code of type C.

type ErrorMessage added in v0.6.0

type ErrorMessage interface {
	// Message returns the error description string for the code.
	Message() string
}

ErrorMessage is implemented by code types that describe themselves. When the carried code implements it, the error message starts with Message instead of the default `Type[value]` form.

type Valuer added in v0.6.0

type Valuer interface {
	// CodeValue returns the carried code as its original type.
	CodeValue() any
}

Valuer exposes the typed code carried by an error to callers which do not know the code type, so they can assert capabilities on the code by their own interfaces.

Example
err := codex.New(ORDER_ERROR__CLOSED)

if v, ok := err.(codex.Valuer); ok {
	code := v.CodeValue()
	fmt.Printf("%T(%d)\n", code, code)
	if d, ok := code.(StatusDescriber); ok {
		fmt.Println(d.StatusCode())
	}
}
Output:
codex_test.OrderError(2)
409

Jump to

Keyboard shortcuts

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