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