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 ¶
- func AsCode[C Code](e error) (C, bool)
- func Errorf[C Code](e C, format string, args ...any) error
- func Is[C Code](e error) bool
- func IsCode[C Code](e error, code C) bool
- func New[C Code](e C) error
- func Wrap[C Code](e C, cause error) error
- func Wrapf[C Code](e C, cause error, format string, args ...any) error
- type CanBeCode
- type CodableError
- type Code
- type Error
- type ErrorMessagedeprecated
- type HasHintCode
- type HasIdentifierCode
- type HasMessageCode
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Errorf ¶
Errorf returns an error carrying code e, 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 ¶
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]
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.