codex

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 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:

  • HasMessageCode: describes the code; used as the error message.
  • HasIdentifierCode: identifies the code uniquely.
  • HasHintCode: a short hint of the code for presentation.
  • CanBeCode: lists all values of the code type.

Creating Errors

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

Every part of an error has its own audience:

  • code: which error it is.
  • detail, the format and args of Errorf and Wrapf: the explanation of this occurrence. Consumers may present it to callers, so keep internal information out of it.
  • cause: the internal reason, for logging only.

Error() contains all of them for logging; Detail() returns the detail only.

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.
  • CodableError: the code of type C exposed by any error, not only the ones created by this package.

Consuming with an Unknown Code Type

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

  • errors.As, errors.AsType: 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.
  • Error: asserted on a single node by callers that walk the chain by themselves. It provides Code and Detail, and its As method views only this node as its code.

See Example_parts for how each part is consumed, and the other 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}
}

func (e OrderError) CodeIdentifier() string {
	switch e {
	case ORDER_ERROR__NOT_FOUND:
		return "ORDER__NOT_FOUND"
	case ORDER_ERROR__CLOSED:
		return "ORDER__CLOSED"
	default:
		return "ORDER__UNDEFINED"
	}
}

func (e OrderError) CodeHint() string {
	switch e {
	case ORDER_ERROR__NOT_FOUND:
		return "the order does not exist"
	default:
		return ""
	}
}

// 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
Example (Parts)
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}
}

func (e OrderError) CodeIdentifier() string {
	switch e {
	case ORDER_ERROR__NOT_FOUND:
		return "ORDER__NOT_FOUND"
	case ORDER_ERROR__CLOSED:
		return "ORDER__CLOSED"
	default:
		return "ORDER__UNDEFINED"
	}
}

func (e OrderError) CodeHint() string {
	switch e {
	case ORDER_ERROR__NOT_FOUND:
		return "the order does not exist"
	default:
		return ""
	}
}

// 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
	}
}

func main() {
	cause := errors.New("sql: no rows")
	err := codex.Wrapf(ORDER_ERROR__NOT_FOUND, cause, "order %d not found", 1024)

	var e codex.Error
	errors.As(err, &e)

	// code: which error it is, the same for every occurrence
	var id codex.HasIdentifierCode
	var hint codex.HasHintCode
	if e.As(&id) && e.As(&hint) {
		fmt.Printf("code:   %d %s %q\n", e.Code(), id.CodeIdentifier(), hint.CodeHint())
	}

	// detail: the explanation of this occurrence, may be presented to callers
	fmt.Printf("detail: %s\n", e.Detail())

	// cause: the internal reason, for logging only
	fmt.Printf("cause:  %v\n", e.Unwrap())

	// Error: all of the above, for logging
	fmt.Printf("log:    %v\n", err)

}
Output:
code:   1 ORDER__NOT_FOUND "the order does not exist"
detail: order 1024 not found
cause:  sql: no rows
log:    order not found. order 1024 not found. [cause: sql: no rows]

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 detail 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 detail 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.HasMessageCode).Message())
}
Output:
1 order not found
2 order closed

type CodableError added in v0.6.1

type CodableError[C Code] interface {
	// CodeValue returns the carried code as its original type.
	CodeValue() C
}

CodableError is an error exposing its code as type C. Unlike As and AsCode, it is not limited to errors created by this package.

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 interface {
	// Error formats as `code message[. detail][. [cause: cause]]`, for logging.
	Error() string
	// Code returns the carried code as int64.
	Code() int64
	// Detail returns the explanation of this occurrence, formatted from the
	// format and args of Errorf or Wrapf. It is empty for New and Wrap. Detail
	// may be presented to callers, so internal information belongs to the cause.
	Detail() string
	// As views this node as its carried code: the code is assigned to target when
	// the code type is assignable to the element type of target. Unlike errors.As,
	// it never looks into the cause.
	As(target any) bool
	// Unwrap returns the cause.
	Unwrap() error
	// Is reports whether err carries the same code of the same type.
	Is(err error) bool
}

Error is an error carrying a code. It is not generic, so callers that do not know the code type, such as transport layers or loggers, can assert it on each node of an error chain.

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("detail: %s\n", e2.(Error).Detail())

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("expect ecode without Message: %s\n", New(ECode3(2)).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]
detail: user message: any
expecting true errors.Is(e0, e1): true
expecting false errors.Is(e1, e2): false
expect ecode without Message: codex_test.ECode3[2]

func As added in v0.2.7

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

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

type ErrorMessage deprecated 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.

Deprecated: use HasMessageCode instead.

type HasHintCode added in v0.6.1

type HasHintCode interface {
	// CodeHint returns the hint of the code, or empty if it has none.
	CodeHint() string
}

HasHintCode is implemented by code types that provide a short hint for presentation. The hint is the same for every occurrence of the code.

type HasIdentifierCode added in v0.6.1

type HasIdentifierCode interface {
	// CodeIdentifier returns the unique identifier of the code.
	CodeIdentifier() string
}

HasIdentifierCode is implemented by code types that identify themselves uniquely, such as `<domain>__<NAME>`.

type HasMessageCode added in v0.6.1

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

HasMessageCode 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.

Jump to

Keyboard shortcuts

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