xerrors

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2022 License: MIT Imports: 8 Imported by: 22

README

go-xerrors

go-xerrors is an idiomatic and lightweight package that provides a set of functions to make working with errors easier. It adds support for stack traces, multierrors, and simplifies working with wrapped errors and panics. The go-xerrors package is fully compatible with Go errors 1.13, supporting the errors.As, errors.Is, and errors.Unwrap functions.

Main features:

  • Stack traces
  • Multierrors
  • More flexible error warping
  • Simplified panic handling

Installation

go get -u github.com/mdobak/go-xerrors

Usage

Basic errors and stack traces

The most basic usage of go-xerrors is to create a new error with a stack trace. This can be done with the xerrors.New function. The simplest usage is to pass a string, which will be used as the error message.

err := xerrors.New("something went wrong")

However, calling the Error method on this error will only return the error message, not the stack trace. To get the stack trace, the xerrors.StackTrace function can be used. This function will return an xerrors.Callers object, which contains the stack trace. The String method on this object can be used to get a string representation of the stack trace.

trace := xerrors.StackTrace(err)
fmt.Print(trace)

Output:

	at main.TestMain (/home/user/app/main_test.go:10)
	at testing.tRunner (/home/user/go/src/testing/testing.go:1259)
	at runtime.goexit (/home/user/go/src/runtime/asm_arm64.s:1133)

Another way to display the stack trace is to use the xerrors.Print, xerrors.Sprint, or xerrors.Fprint functions. These functions will detect if the error passed contains a stack trace and print it to the stderr if it does. It is done by checking if the error implements the xerrors.DetailedError interface. This interface has a single method, ErrorDetails, that returns an additional information about the error, such as the stack trace.

xerrors.Print(err)

Output:

Error: access denied
	at main.TestMain (/home/user/app/main_test.go:10)
	at testing.tRunner (/home/user/go/src/testing/testing.go:1259)
	at runtime.goexit (/home/user/go/src/runtime/asm_arm64.s:1133)

The reason why standard Error() method does not return the stack trace is because most developers expect the Error() method to return only a one-line error message without punctuation at the end. This library follows this convention.

Sentinel errors

Sentinel errors are errors that are defined as constants. They are useful to check if an error is of a specific type without having to compare the error message. This library provides a xerrors.Message function that can be used to create a sentinel error.

var ErrAccessDenied = xerrors.Message("access denied")
// ...
if errors.Is(err, ErrAccessDenied) {
    // ...
}
Error wrapping

The xerrors.New function accepts not only strings but also other errors. For example, it can be used to add a stack trace to sentinel errors.

var ErrAccessDenied = xerrors.Message("access denied")
// ...
err := xerrors.New(ErrAccessDenied)
//
if errors.Is(err, ErrAccessDenied) {
    xerrors.Print(err) // prints error along with the stack trace
}

Another way to use the xerrors.New function is to wrap an existing error with a new error message.

err := xerrors.New("unable to open resource", ErrAccessDenied)
fmt.Print(err.Error()) // unable to open resource: access denied

It is also possible to wrap an error with another error. Unlike the fmt.Errorf function, references to both errors will be preserved, so it is possible to check if the new error is one of the wrapped errors.

var ErrAccessDenied = xerrors.Message("access denied")
var ErrResourceOpenFailed = xerrors.Message("unable to open resource")
// ...
err := xerrors.New(ErrResourceOpenFailed, ErrAccessDenied)
fmt.Print(err.Error()) // unable to open resource: access denied
errors.Is(err, ErrResourceOpenFailed) // true
errors.Is(err, ErrAccessDenied) // true
Multierrors

Multierrors are a set of errors that can be treated as a single error. The xerrors package provides the xerrors.Append function to create them. It works similarly to the append function in the Go language. The function accepts a variadic number of errors and returns a new error that contains all of them. The returned error supports errors.Is and errors.As methods. However, the errors.Unwrapmethod is not supported.

var err error
if len(unsername) == 0 {
    err = xerrors.Append(err, xerrors.New("username cannot be empty"))
}
if len(password) < 8 {
    err = xerrors.Append(err, xerrors.New("password is too short"))
}

The error list can be displayed in several ways. The simplest way is to use the Error method, which will display errors as a long, one-line string:

the following errors occurred: [username cannot be empty, password is too short]

Another way is to use one of the following functions: xerrors.Print, xerrors.Sprint, or xerrors.Fprint. The advantage of using these functions is that they will also print additional details, such as stack traces, and the error message is much easier to read:

Error: the following errors occurred: [username cannot be empty, password is too short]
1. Error: username cannot be empty
	at xerrors.TestFprint (/home/user/app/main_test.go:10)
	at testing.tRunner (/home/user/go/src/testing/testing.go:1439)
	at runtime.goexit (/home/user/go/src/runtime/asm_arm64.s:1259)
2. Error: password is too short
	at xerrors.TestFprint (/home/user/app/main_test.go:13)
	at testing.tRunner (/home/user/go/src/testing/testing.go:1439)
	at runtime.goexit (/home/user/go/src/runtime/asm_arm64.s:1259)

Finally, multierror implements the xerrors.MultiError interface, which provides the Errors method that returns a list of errors.

Recovered panics

In Go, the values returned by the recover built-in do not implement the error interface, which may be inconvenient. This library provides two functions to easily convert a recovered value into an error.

The first function, xerrors.Recover, works similarly to the recover built-in. This function must always be called directly using the defer keyword. The callback will only be called during a panic, and the provided error will contain a stack trace:

defer xerrors.Recover(func (err error) {
    xerrors.Print(err)
})

The second function allows converting a value returned from recover built-in to an error with a stack trace:

defer func() {
    if r := recover(); r != nil {
        err := xerrors.FromRecover(r)
        xerrors.Print(err)
    }
}()
Documentation

This package offers a few additional functions and interfaces that may be useful in some use cases. More information about them can be found in the documentation:

https://pkg.go.dev/github.com/mdobak/go-xerrors

License

Licensed under MIT License

Documentation

Overview

Package xerrors is an idiomatic and lightweight package that provides a set of functions to make working with errors easier. It adds support for stack traces, multierrors, and simplifies working with wrapped errors and panics. The `go-xerrors` package is fully compatible with Go errors 1.13, supporting the `errors.As`, `errors.Is`, and `errors.Unwrap` functions.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append

func Append(err error, errs ...error) error

Append adds more errors to an existing list of errors. If err is not a list of errors, then it will be converted into a list. Nil errors are ignored. It does not record a stack trace.

If the list is empty, nil is returned. If the list contains only one error, that error is returned instead of list.

The returned list of errors is compatible with Go 1.13 errors, and it supports the errors.Is and errors.As methods. However, the errors.Unwrap method is not supported.

Append is not thread-safe.

func Fprint

func Fprint(w io.Writer, err error) (int, error)

Fprint formats an error and writes it to the given writer.

If the error implements the DetailedError interface, the result from the ErrorDetails method is used for each wrapped error, otherwise the standard Error method is used. A formatted error can be multi-line and always ends with a newline.

func FromRecover

func FromRecover(r interface{}) error

FromRecover takes the result of the recover() built-in and converts it to an error with a stack trace.

This function must be invoked in the same function as recover(), otherwise the returned stack trace will not be correct.

func Message

func Message(msg string) error

Message creates a simple error with the given message. It does not record a stack trace. Each call returns a distinct error value even if the message is identical.

This function is intended to create sentinel errors, sometimes referred to as "constant errors".

func New

func New(vals ...interface{}) error

New creates a new error from the given value and records a stack trace at the point it was called. If multiple values are provided, then each error is wrapped by the previous error. Calling New(a, b, c), where a, b, and c are errors, is equivalent to calling New(WithWrapper(WithWrapper(a, b), c)).

This function may be used to:

- Add a stack trace to an error: New(err)

- Create a message error with a stack trace: New("access denied")

- Wrap an error with a message: New("access denied", io.EOF)

- Wrap one error in another: New(ErrAccessDenied, io.EOF)

- Add a message to a sentinel error: New(ErrReadError, "access denied")

Values are converted to errors according to the following rules:

- If a value is an error, it will be used as is.

- If a value is a string, then new error with a given string as a message will be created.

- If a value is nil, it will be ignored.

- If a value implements the fmt.Stringer interface, then a String() method will be used to create an error.

- For other types the result of fmt.Sprint will be used to create a message error.

It is possible to use errors.Is function on returned error to check whether an error has been used in the New function.

If the function is called with no arguments or all arguments are nil, it returns nil.

To create a simple message error without a stack trace to be used as a sentinel error, use the Message function instead.

func Print

func Print(err error)

Print formats an error and prints it on stderr.

If the error implements the DetailedError interface, the result from the ErrorDetails method is used for each wrapped error, otherwise the standard Error method is used. A formatted error can be multi-line and always ends with a newline.

func Recover

func Recover(fn func(err error))

Recover wraps the recover() built-in and converts a value returned by it to an error with a stack trace. The fn callback will be invoked only during panicking.

This function must always be used *directly* with the "defer" keyword. Otherwise, it will not work.

func Sprint

func Sprint(err error) string

Sprint formats an error and returns it as a string.

If the error implements the DetailedError interface, the result from the ErrorDetails method is used for each wrapped error, otherwise the standard Error method is used. A formatted error can be multi-line and always ends with a newline.

func WithStackTrace

func WithStackTrace(err error, skip int) error

WithStackTrace adds a stack trace to the error at the point it was called. The skip argument is the number of stack frames to skip.

This function is useful when you want to skip the first few frames in a stack trace. To add a stack trace to a sentinel error, use the New function.

If err is nil, then nil is returned.

func WithWrapper

func WithWrapper(wrapper error, err error) error

WithWrapper wraps err with wrapper.

The error used as wrapper should be a simple error, preferably a sentinel error. This is because details such as the wrapper's stack trace are ignored.

The Unwrap method will unwrap only err but errors.Is, errors.As works with both of the errors.

If wrapper is nil, then err is returned. If err is nil, then nil is returned.

Types

type Callers

type Callers []uintptr

Callers is a list of program counters returned by the runtime.Callers.

func StackTrace

func StackTrace(err error) Callers

StackTrace returns a stack trace from given error or the first stack trace from the wrapped errors.

func (Callers) Format

func (c Callers) Format(s fmt.State, verb rune)

Format implements the fmt.Formatter interface.

The verbs:

%s	a stack trace
%v	same as %s, the plus or hash flags print struct details
%q	a double-quoted Go string with same contents as %s

func (Callers) Frames

func (c Callers) Frames() []Frame

Frames returns a slice of structures with a function/file/line information.

func (Callers) String

func (c Callers) String() string

String implements the fmt.Stringer interface.

type DetailedError

type DetailedError interface {
	error
	ErrorDetails() string
}

DetailedError provides extended information about an error. The ErrorDetails method returns a longer, multi-line description of the error. It always ends with a new line.

type Frame

type Frame struct {
	File     string
	Line     int
	Function string
}

func (Frame) Format

func (f Frame) Format(s fmt.State, verb rune)

Format implements the fmt.Formatter interface.

The verbs:

%s	function, file and line number in a single line
%f	filename
%d	line number
%n	function name, the plus flag adds a package name
%v	same as %s, the plus or hash flags print struct details
%q	a double-quoted Go string with same contents as %s

func (Frame) String

func (f Frame) String() string

String implements the fmt.Stringer interface.

type MultiError

type MultiError interface {
	error
	Errors() []error
}

MultiError is an error that contains multiple errors.

type StackTracer

type StackTracer interface {
	error
	StackTrace() Callers
}

StackTracer provides a stack trace for an error.

type Wrapper

type Wrapper interface {
	error
	Unwrap() error
}

Wrapper provides context around another error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL