Documentation ¶
Overview ¶
Package txerr provides common functions for querying and formatting errors generated by txfile.
Creating errors in txfile for use with txerr must follow some common conventions, so to guarantee some level consistency among packages.
Package txerr expects errors to implement methods for querying error internals. This allows us to treat errors as interfaces, while having package specific implementations. A common set of recommended methods is documented with the Error type.
The Error type is designed to rely on builtin types only. No implementation must be forced to use a type define in txerr or any other common package.
Errors are treated as a tree of individual error values. Each node in the tree can have 0, 1 or N children. Errors capturing a single cause only must implement `Cause() error`. Errors capturing multiple causes must implement `Causes() []error`. An error is a Leaf-Node if: - It does not implement `Cause()` or `Causes()` - It implements `Cause()` and `err.Cause() == nil` - It implements `Causes()` and `len(err.Causes()) == 0`.
Each node in the tree provides addition context. The operation which caused the error (common format: `package-name/type-method`), an error kind for use by the application (query error type and recover), a custom error context and an user message.
Error kinds use type error, but must be uniquely comparable. That is due to interface comparison rules every package defining an error kind must define a custom type + constants for every supported error kind. Common pattern used within txfile:
// this type declaration ensures comparisons with values from other package // will not get us false positives type errKind int //go:generate stringer -type=errKind -linecomment=true const ( Value1 errKind = iota // readable error message Value2 // another readable error message ) func (k errKind) Error() string { // satisfy error interface return k.String() }
Errors can have a package specific context. When creating a context, a nested error should not repeat the variables also present in the parent context. This requires us to implement some kind of context-merging when adding a cause to an error. Common pattern for error types used in txfile:
type Error struct { op string kind error cause error ctx errorCtx msg string } type errorCtx struct { k1 int k2 uint // optional context field (if you are too lazy to create a many error // types use bool or check for empty values) isK3 bool k3 string } // fulfill txerr.Error interface: func (e *Error) Error() string { return txerr.Report(e) } // implement `error` with help of txerr func (e *Error) Op() string { return e.op } func (e *Error) Kind() error { return e.kind } func (e *Error) Cause() error { return e.cause } func (e *Error) Context() string { return e.ctx.String() } func (e *Error) Message() string { return e.msg } // implement Format and Errors to make some logging libs formatting errors // based on these methods happy (e.g. zap). func (e *Error) Format(s fmt.State, c rune) { txerr.Format(e, s, c) } func (e *Error) Errors() []error { if e.cause == nil { return nil } return []error{e.cause} } // context based accessors (optional) func (e *Error) K1() int { return e.ctx.k1 } func (ctx *errorCtx) String() string { buf := &strbld.Builder{} buf.Fmt("k1=%v k2=%v", ctx.k1, ctx.k2) if ctx.isK3 { buf.Fmt(" k3=%v", ctx.k3) } return buf.String() } // optional error building helpers (e.g. for wrapping another error): func (e *Error) of(kind errKind) *Error { e.kind = kind return e } func (e *Error) report(s string) *Error { e.msg = s return e } func (e *Error) causedBy(cause error) *Error { e.cause = cause if other, ok := cause.(*Error); ok { mergeCtx(e, other) } return e } func mergeCtx(err, cause *Error) { ... }
Objects very often cary context to be added to an error. In txfile these kind of objects provide additional helpers for constructing errors. These helpers guarantee that the context is correctly reported:
type myObj struct {...} func (obj *myObj) errWrap(op string, cause error) *Error { return obj.err(op).causedBy(cause) } func (obj *myObj) err(op string) *Error { return &Error{op: op, ctx: errCtx()} } func (obj *myObj) errCtx() errorCtx { ... }
All error construction should use the appropriate constructors:
func (obj *myObj) methodThatFails() error { const op = "<package-name>/obj-<readable-op>" ... // on failure if err != nil { return obj.errWrap(op, err).of(ForApplicationUse).report("custom user message") } ... }
Index ¶
- func FindErrWith(in error, pred func(err error) bool) error
- func FindKind(in error, kind error) error
- func FindKindIf(in error, fn func(kind error) bool) error
- func FindOp(in error, op string) error
- func Format(err error, s fmt.State, c rune)
- func GetKind(in error) error
- func GetOp(in error) string
- func Is(kind error, in error) bool
- func IsOp(op string, in error) bool
- func Iter(in error, fn func(err error) bool)
- func Report(in error, verbose bool) string
- type Error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FindErrWith ¶
FindErrWith returns the first error in the error tree, that matches the given predicate.
func FindKindIf ¶
FindKindIf returns the first error with a kind that fulfills the user predicate
func Format ¶
Format provides a common formatting implementation for adding the fmt.Formatter interface to custom errors. Usage:
func (e *myError) Format(s fmt.State, c rune) { txerr.Format(e, s, c) }
Types ¶
type Error ¶
type Error interface { error // Op reports the failed operation. Op() string // Kind returns the error code, for checking and recovering from errors. Kind() error // Message reports an error message for users consumption. Message() string // Cause returns the cause of this error. If any. Cause() error }
Error defines a common error interface type for use with txerr. The query and format function support a subset of Error being implemented, still it's recommended to implement at least the Error interface for use with txerr.