Documentation
¶
Overview ¶
Package errors is inspired by `pkg/errors` (https://github.com/pkg/errors) and uses a similar API but adds support for error codes. Error codes are always optional.
import ( errs "github.com/mkenney/go-errors" )
Error stacks ¶
An error stack is an array of errors.
Create a new stack
if !decodeSomeJSON() {
err := errs.New("validation failed")
}
Base a new stack off any error
err := decodeSomeJSON() err = errs.Wrap(err, "could not read configuration")
Define error codes ¶
Adding support for error codes is the primary motivation behind this project. See `codes.go` (https://github.com/mkenney/go-errors/blob/master/codes.go). `HTTPStatus` is optional and a convenience property that allows automation of HTTP status responses based on internal error codes. The `Code` definition associated with error at the top of the stack (most recent error) should be used for HTTP status output.
import (
errs "github.com/mkenney/go-errors"
)
const (
// Error codes below 1000 are reserved future use by the errors
// package.
UserError errs.Code = iota + 1000
InternalError
)
func init() {
errs.Codes[UserError] = errs.Metadata{
Internal: "bad user input",
External: "A user error occurred",
HTTPStatus: 400,
}
errs.Codes[InternalError] = errs.Metadata{
Internal: "could not save data",
External: "An internal server occurred",
HTTPStatus: 500,
}
}
func SomeFunc() error {
return errs.New("SomeFunc failed because of things", InternalError)
}
Define a new error with an error code ¶
Creating a new error defines the root of a backtrace.
_, err := ioutil.ReadAll(r)
if err != nil {
return errs.New("read failed", errs.ErrUnknown)
}
Adding context to an error ¶
The errors.Wrap function returns a new error that adds context to the original error and starts an error stack:
_, err := ioutil.ReadAll(r)
if err != nil {
return errs.Wrap(err, "read failed", errs.ErrUnknown)
}
In this case, if the original `err` is not an instance of `Stack`, that error becomes the root of the error stack.
Building an error stack ¶
Most cases will build a stack trace off a series of errors returned from the call stack:
import (
"fmt"
errs "github.com/mkenney/go-errors"
)
func main() {
err := loadConfig()
fmt.Printf("%#v", err)
}
func readConfig() error {
err := fmt.Errorf("read: end of input")
return errs.Wrap(err, "could not read configuration file", errs.ErrEOF)
}
func decodeConfig() error {
err := readConfig()
return errs.Wrap(err, "could not decode configuration data", errs.ErrInvalidJSON)
}
func loadConfig() error {
err := decodeConfig()
return errs.Wrap(err, "service configuration could not be loaded", errs.ErrFatal)
}
But for cases where a set of errors need to be captured from a single procedure, the `With()` call can be used. The with call adds an error to the stack behind the leading error:
import (
errs "github.com/mkenney/go-errors"
)
func doSteps() error {
var errStack errs.Err
err := doStep1()
if nil != err {
errStack.With(err, "step 1 failed")
}
err = doStep2()
if nil != err {
errStack.With(err, "step 2 failed")
}
err = doStep3()
if nil != err {
errStack.With(err, "step 3 failed")
}
return errStack
}
Root cause of an error stack ¶
Retrieving the root cause of an error stack is straightforward:
log.Println(err.(errs.Stack).Cause())
Similar to `pkg/errors`, you can easily switch on the type of any error in the stack (including the causer):
switch err.(errs.Err).Cause().(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
Output formats ¶
The Formatter interface has been implemented to provide access to a stack trace with the `%v` verb.
Standard error output, use with error codes to ensure appropriate user-facing messages `%s`:
0002: Internal Server Error
Single-line stack trace, useful for logging `%v`:
#0 - "service configuration could not be loaded" example_test.go:22 `github.com/mkenney/go-errors_test.loadConfig` {0002: a fatal error occurred} #1 - "could not decode configuration data" example_test.go:17 `github.com/mkenney/go-errors_test.decodeConfig` {0200: invalid JSON data could not be decoded} #2 - "could not read configuration file" example_test.go:12 `github.com/mkenney/go-errors_test.readConfig` {0100: unexpected EOF}
Multi-line condensed stack trace `%#v`:
#0 - "service configuration could not be loaded" example_test.go:22 `github.com/mkenney/go-errors_test.loadConfig` {0002: a fatal error occurred}
#1 - "could not decode configuration data" example_test.go:17 `github.com/mkenney/go-errors_test.decodeConfig` {0200: invalid JSON data could not be decoded}
#2 - "could not read configuration file" example_test.go:12 `github.com/mkenney/go-errors_test.readConfig` {0100: unexpected EOF}
Multi-line detailed stack trace `%+v`:
#0: `github.com/mkenney/go-errors_test.loadConfig` error: service configuration could not be loaded line: example_test.go:22 code: 2 - a fatal error occurred entry: 17741072 message: Internal Server Error #1: `github.com/mkenney/go-errors_test.decodeConfig` error: could not decode configuration data line: example_test.go:17 code: 200 - invalid JSON data could not be decoded entry: 17740848 message: Invalid JSON Data #2: `github.com/mkenney/go-errors_test.readConfig` error: could not read configuration file line: example_test.go:12 code: 100 - unexpected EOF entry: 17740576 message: End of input
Index ¶
- Constants
- Variables
- type Call
- type Code
- type Coder
- type Err
- func (errs Err) Caller() std.Caller
- func (errs Err) Cause() error
- func (errs Err) Code() std.Code
- func (errs Err) Detail() string
- func (errs Err) Error() string
- func (errs Err) Format(state fmt.State, verb rune)
- func (errs Err) HTTPStatus() int
- func (errs Err) Msg() string
- func (errs Err) String() string
- func (errs Err) Trace() std.Trace
- func (errs Err) With(err error, msg string, data ...interface{}) Err
- type ErrCode
- type ErrMsg
- type Msg
Examples ¶
Constants ¶
const ( // ErrUnknown - 0: An unknown error occurred. ErrUnknown std.Code = iota // ErrFatal - 1: An fatal error occurred. ErrFatal )
Internal errors
const ( // ErrEOF - 100: An invalid HTTP method was requested. ErrEOF std.Code = iota + 100 ErrReader )
I/O errors
const ( // ErrDecodingFailed - Decoding failed due to an error with the data. ErrDecodingFailed std.Code = iota + 200 // ErrDecodingJSON - JSON data could not be decoded. ErrDecodingJSON // ErrDecodingToml - Toml data could not be decoded. ErrDecodingToml // ErrDecodingYaml - Yaml data could not be decoded. ErrDecodingYaml // ErrEncodingFailed - Encoding failed due to an error with the data. ErrEncodingFailed // ErrEncodingJSON - JSON data could not be encoded. ErrEncodingJSON // ErrEncodingToml - Toml data could not be encoded. ErrEncodingToml // ErrEncodingYaml - Yaml data could not be encoded. ErrEncodingYaml // ErrInvalidJSON - Data is not valid JSON. ErrInvalidJSON // ErrInvalidToml - Data is not valid Toml. ErrInvalidToml // ErrInvalidYaml - Data is not valid Yaml. ErrInvalidYaml // ErrTypeConversionFailed - Data type conversion failed. ErrTypeConversionFailed )
Encoding errors
const ( // ErrInvalidHTTPMethod - 300: An invalid HTTP method was requested. ErrInvalidHTTPMethod std.Code = iota + 300 )
Server errors
Variables ¶
var Codes = map[std.Code]Coder{}
Codes contains a map of error codes to metadata
Functions ¶
This section is empty.
Types ¶
type Call ¶
type Call struct {
// contains filtered or unexported fields
}
Call implements bdlm/std/error.Caller, holding runtime.Caller data.
func (Call) Ok ¶
Ok implements bdlm/std/error.Caller, returning whether the caller data was successfully recovered.
type Coder ¶
type Coder interface {
// Internal only (logs) error text.
Detail() string
// HTTP status that should be used for the associated error code.
HTTPStatus() int
// External (user) facing error text.
String() string
}
Coder defines an interface for an error code.
type Err ¶
type Err []ErrMsg
Err defines an error heap.
func From ¶
From creates a new error stack based on a provided error and returns it.
Example ¶
package main
import (
"errors"
"fmt"
errs "github.com/bdlm/errors"
)
func main() {
// Converting an error from another package into an error stack is
// straightforward.
err := errors.New("my error")
if _, ok := err.(errs.Err); !ok {
err = errs.From(0, err)
}
fmt.Println(err)
fmt.Println(err.(errs.Err).Detail())
}
Output: 0000: An unknown error occurred my error
func New ¶
New returns an error with caller information for debugging.
Example ¶
package main
import (
"fmt"
errs "github.com/bdlm/errors"
)
func main() {
var err error
// If an error code isn't used or doesn't have a corresponding
// ErrCode defined, the error message is returned.
err = errs.New(0, "this is an error message")
fmt.Println(err)
// If an error with a corresponding ErrCode is specified, the
// user-safe error string mapped to the error code is returned,
// along with the code.
err = errs.New(errs.ErrFatal, "this is an error message")
fmt.Println(err)
}
Output: 0000: An unknown error occurred 0001: A fatal error occurred
func Wrap ¶
Wrap wraps an error into a new stack led by msg.
Example (Backtrace) ¶
// To build up an error stack, add context to each error before
// returning it up the call stack.
err := loadConfig()
if nil != err {
err = errs.Wrap(err, 0, "failed to load configuration")
}
// The %v formatting verb can be used to print out the stack trace
// in various ways. The %v verb is the default and prints out the
// standard error message.
fmt.Println(err)
// The %-v verb is useful for logging and prints the trace on a
// single line.
fmt.Printf("%-v\n\n", err)
// The %#v verb prints each cause in the stack on a separate line.
fmt.Printf("%#v\n\n", err)
// The %+v verb prints a verbose detailed backtrace intended for
// human consumption.
fmt.Printf("%+v\n\n", err)
Output: 0000: An unknown error occurred #4 - "failed to load configuration" examples_test.go:37 `github.com/bdlm/errors_test.ExampleWrap_backtrace` {0000: failed to load configuration} #3 - "service configuration could not be loaded" mocks_test.go:31 `github.com/bdlm/errors_test.loadConfig` {1000: the configuration is invalid} #2 - "could not decode configuration data" mocks_test.go:36 `github.com/bdlm/errors_test.decodeConfig` {0208: could not decode configuration data} #1 - "could not read configuration file" mocks_test.go:41 `github.com/bdlm/errors_test.readConfig` {0100: unexpected EOF} #0 - "read: end of input" mocks_test.go:41 `github.com/bdlm/errors_test.readConfig` {0000: read: end of input} #4 - "failed to load configuration" examples_test.go:37 `github.com/bdlm/errors_test.ExampleWrap_backtrace` {0000: failed to load configuration} #3 - "service configuration could not be loaded" mocks_test.go:31 `github.com/bdlm/errors_test.loadConfig` {1000: the configuration is invalid} #2 - "could not decode configuration data" mocks_test.go:36 `github.com/bdlm/errors_test.decodeConfig` {0208: could not decode configuration data} #1 - "could not read configuration file" mocks_test.go:41 `github.com/bdlm/errors_test.readConfig` {0100: unexpected EOF} #0 - "read: end of input" mocks_test.go:41 `github.com/bdlm/errors_test.readConfig` {0000: read: end of input} #4: `github.com/bdlm/errors_test.ExampleWrap_backtrace` error: failed to load configuration line: examples_test.go:37 code: 0000: failed to load configuration message: 0000: An unknown error occurred #3: `github.com/bdlm/errors_test.loadConfig` error: service configuration could not be loaded line: mocks_test.go:31 code: 1000: the configuration is invalid message: 1000: Configuration not valid #2: `github.com/bdlm/errors_test.decodeConfig` error: could not decode configuration data line: mocks_test.go:36 code: 0208: could not decode configuration data message: 0208: could not decode configuration data #1: `github.com/bdlm/errors_test.readConfig` error: could not read configuration file line: mocks_test.go:41 code: 0100: unexpected EOF message: 0100: End of input #0: `github.com/bdlm/errors_test.readConfig` error: read: end of input line: mocks_test.go:41 code: 0000: read: end of input message: 0000: An unknown error occurred
func (Err) Detail ¶
Detail implements the Coder interface. Detail returns the single-line stack trace.
func (Err) Format ¶
Format implements fmt.Formatter. https://golang.org/pkg/fmt/#hdr-Printing
Format formats the stack trace output. Several verbs are supported:
%s - Returns the user-safe error string mapped to the error code or
the error message if none is specified.
%v - Alias for %s
%#v - Returns the full stack trace in a single line, useful for
logging. Same as %#v with the newlines escaped.
%-v - Returns a multi-line stack trace, one column-delimited line
per error.
%+v - Returns a multi-line detailed stack trace with multiple lines
per error. Only useful for human consumption.
func (Err) HTTPStatus ¶
HTTPStatus returns the associated HTTP status code, if any. Otherwise, returns 200.
func (Err) With ¶
With adds a new error to the stack without changing the leading cause.
Example ¶
// To add to an error stack without modifying the leading cause, add
// additional errors to the stack with the With() method.
err := loadConfig()
if nil != err {
if e, ok := err.(errs.Err); nil != err && ok {
err = e.With(errs.New(0, "failed to load configuration"), "loadConfig returned an error")
} else {
err = errs.From(0, err)
}
}
fmt.Println(err)
Output: 1000: Configuration not valid
type ErrCode ¶
type ErrCode struct {
// External (user) facing error text.
Ext string
// Internal only (logs) error text.
Int string
// HTTP status that should be used for the associated error code.
HTTP int
}
ErrCode implements coder
func (ErrCode) HTTPStatus ¶
HTTPStatus returns the associated HTTP status code, if any. Otherwise, returns 200.
type ErrMsg ¶
type ErrMsg interface {
Caller() std.Caller
Code() std.Code
Error() string
Msg() string
SetCode(std.Code) ErrMsg
Trace() std.Trace
}
ErrMsg defines the interface to error message data.