etype

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Nov 27, 2019 License: MIT Imports: 0 Imported by: 3

README

Wrapping errors

This package has some examples of using custom error types with Go's xerrors package. Import or copy this package if you want Sentinels. Use the examples to build your own application-specific error types and have them work smoothly with xerrors.

Everything works the same with Go 1.13 errors. xerrors is used here so people can use this with older compilers. If you are using Go 1.13+, you can use fmt.Errorf instead of xerrors.Errorf.

The Sentinels are of course based on Dave Cheney's "Constant Time" essay and dotGo 2019 presentation. https://dave.cheney.net/2019/06/10/constant-time

Sentinels

Sentinels are basic errors with nothing more than a type and a string value.

You can create a single Sentinel type and an Is method to handle all sentinels in your program:

type Sentinel string

func (e Sentinel) Error() string {
	return string(e)
}

func (e Sentinel) Is(err error) bool {
	sentinel, ok := err.(Sentinel)
	if !ok {
		return false
	}
	return sentinel == e
}

Create sentinels like this:

const ErrBroken = Sentinel("a specific thing broke")

So you can do this:

if xerrors.Is(err, ErrBroken) ...

If any error in the chain starting with err is an ErrBroken, then Is will be true.

You don't have to do anything special to use Sentinels in this way.

Your application or package can easily set up lots of sentinels, each of which can be distinguished with xerrors.Is:

const (
    ErrBroken = Sentinel("broke this way")
    ErrBusted = Sentinel("broke that way")
)

Custom Error Types

The next level of complexity is wrapping an error in a custom type to "tag" an error with boolean attributes. For example, you could have a custom error type that indicates that an error is temporary, or that it was the result of a timeout.

These kinds of error type require a constructor and a couple of methods. But when these are written, the new error types fit right into the xerrors wrapping techniques.

Here is an example of using a new type to indicate temporary errors.

First we create the new type, and all we have to do is embed error:

type TemporaryError struct {
    error
}

Now we build a constructor:

func NewTemporary(err error) error {
    return &TemporaryError{xerrors.Errorf("temporary: %w", err)}
}

(You have to use xerrors.Errorf to wrap the err because the returned value from xerrors.Errorf has both Error and Unwrap methods at the right "level".)

Now we add Is and Unwrap methods on our new error type so that xerrors.Is and xerrors.Unwrap work right:

func (e *TemporaryError) Is(err error) bool {
	_, ok := err.(*TemporaryError)
	return ok
}

func (e *TemporaryError) Unwrap() error {
	return xerrors.Unwrap(e.error)
}

Now your new error type is a fully paid up member of the club. You can do this to any error chain:

if xerrors.Is(err, &TemporaryError{}) ...

Or you can write a shortcut:

func IsTemporary(err error) bool {
	return xerrors.Is(err, &TemporaryError{})
}

and do this:

if IsTemporary(err) ...

Complex error types

Sometimes you want to add more context to an error. Suppose you want to set status codes that can be found at the top of your program.

First create a new error type by embedding error and adding any additional fields you need:

type ErrStatusCode struct {
	error
	statusCode int
}

Create a constructor:

func WithStatusCode(err error, code int) error {
	return &ErrStatusCode{
		error:      xerrors.Errorf("code %d: %w", code, err),
		statusCode: code,
	}
}

And add the standard Is and Unwrap methods:

func (e *ErrStatusCode) Is(err error) bool {
	_, ok := err.(*ErrStatusCode)
	return ok
}

func (e *ErrStatusCode) Unwrap() error {
	return xerrors.Unwrap(e.error)
}

In addition, write any methods that make sense for your new type:

func (e *ErrStatusCode) StatusCode() int {
	return e.statusCode
}

Now at the top of your program you can grab an error's status code, if it has one, like this:

var errStatusCode *ErrStatusCode
var code int
if xerrors.As(err, &errStatusCode) {
    code = errStatusCode.StatusCode()
}

It doesn't matter how many times the ErrStatusCode has been wrapped; As will find it.

First cause

With standardized Unwrap methods, you can easily find the original error in the chain:

func Cause(err error) error {
	type wrapper interface {
		Unwrap() error
	}
	for err != nil {
		cause, ok := err.(wrapper)
		if !ok {
			break
		}
		err = cause.Unwrap()
	}
	return err
}

Visualizing the error chain

If you ever wonder what the error chain looks like, you can do this:

type Printer func(format string, args ...interface{})

func Chain(printf Printer, err error) {
	printf("error chain:\n")
	for err != nil {
		printf("\t%T %v\n", err, err)
		err = xerrors.Unwrap(err)
	}
}

Here is what the output can look like if you call it like this:

Chain(fmt.Printf, err)
error chain:
    *fmt.wrapError some annotation: code 400: temporary: original error
    *myapp.ErrStatusCode code 400: temporary: original error
    *myapp.TemporaryError temporary: original error
    etype.Sentinel original error

Annotations

With the xerrors.Errorf, you can annotate errors without getting complicated, and without losing the benefits of Is and As, no matter how many annotations are added:

if err != nil {
    return xerrors.Errorf("could get details for user %s: %w", user_id, err)
}

The original error is wrapped with an annotation, and no information is lost. The entire chain can still be unwrapped at the top of the program.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Cause

func Cause(err error) error

Cause returns the lowest error in the chain.

Types

type Sentinel

type Sentinel string

A Sentinel is a simple error string that can be a const. See https://dave.cheney.net/2019/06/10/constant-time

func (Sentinel) Error

func (e Sentinel) Error() string

func (Sentinel) Is

func (e Sentinel) Is(err error) bool

Is returns true if err is a Sentinel and is the same kind of sentinel as the receiver. (ie, the string values are the same)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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