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 ¶
- 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 Code
- type Error
- type ErrorMessage
- type Valuer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Errorf ¶
Errorf returns an error carrying code e, 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 ¶
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]
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