txerr/doc.go (1 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// 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")
// }
//
// ...
// }
package txerr