errors

package module
v2.1.2 Latest Latest
Warning

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

Go to latest
Published: May 12, 2021 License: MIT Imports: 9 Imported by: 4

README

errors

Mature This project adheres to Semantic Versioning. This package is considered mature, you should expect package stability in Minor and Patch version releases

  • Major: backwards incompatible package updates
  • Minor: feature additions, removal of deprecated features
  • Patch: bug fixes, backward compatible model and function changes, etc.

CHANGELOG

Release GoDoc Build status Coverage status Go Report Card Github issues Github pull requests MIT

errors provides simple, concise, useful error handling and annotation. This package aims to implement the Error Inspection and Error Values Go2 draft designs.

One of the biggest frustrations with Go error handling is the lack of forensic and meta information errors should provide. By default errors are just a string and possibly a type. They can't tell you where they occurred or the path through the call stack they followed. The error implementation in Go is robust enough to control program flow but it's not very efficient for troubleshooting or analysis.

Since the idom in Go is that we pass the error back up the stack anyway:

if nil != err {
	return err
}

it's trivial to make errors much more informative with a simple error package. bdlm/errors makes this easy and supports tracing the call stack and the error callers with relative ease. Custom error types are also fully compatible with this package and can be used freely.

Install

go get github.com/bdlm/errors/v2

Quick start

See the documentation for more examples. All package methods work with any error type as well as nil values, and error instances implement the Unwrap, Is, Marshaler, and Formatter interfaces as well as the github.com/bdlm/std/errors interfaces.

Create an error
var MyError = errors.New("My error")
Create an error using formatting verbs
var MyError = errors.Errorf("My error #%d", 1)
Wrap an error
if nil != err {
	return errors.Wrap(err, "the operation failed")
}
Wrap an error with another error
err := try1()
if nil != err {
	err2 := try2()
	if nil != err2 {
		return errors.WrapE(err, err2)
	}
	return err
}
Get the previous error, if any
err := doWork()
if prevErr := errors.Unwrap(err); nil != prevErr {
	...
}
Test to see if a specific error type exists anywhere in an error stack
var MyError = errors.New("My error")
func main() {
	err := doWork()
	if errors.Is(err, MyError) {
		...
	}
}
Iterate through an error stack
err := doWork()
for nil != err {
	fmt.Println(err)
	err = errors.Unwrap(err)
}
Formatting verbs

errors implements the %s and %v fmt.Formatter formatting verbs and several modifier flags:

Verbs
  • %s - Returns the error string of the last error added.
  • %v - Alias for %s
Flags
  • # - JSON formatted output, useful for logging
  • - - Output caller details, useful for troubleshooting
  • + - Output full error stack details, useful for debugging
  • - (space) Add whitespace formatting for readability, useful for development
Examples

fmt.Printf("%s", err)

An error occurred

fmt.Printf("%v", err)

An error occurred

fmt.Printf("%-v", err)

#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors) - An error occurred

fmt.Printf("%+v", err)

#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors) - An error occurred #1 stack_test.go:39 (github.com/bdlm/error_test.TestErrors) - An error occurred

fmt.Printf("%#v", err)

{"error":"An error occurred"}

fmt.Printf("%#-v", err)

{"caller":"#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors)","error":"An error occurred"}

fmt.Printf("%#+v", err)

[{"caller":"#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors)","error":"An error occurred"},{"caller":"#1 stack_test.go:39 (github.com/bdlm/error_test.TestErrors)","error":"An error occurred"}]

fmt.Printf("% #-v", err)

{
    "caller": "#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors)",
    "error": "An error occurred"
}

fmt.Printf("% #+v", err)

[
    {
        "caller": "#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors)",
        "error": "An error occurred"
    },
    {
        "caller": "#1 stack_test.go:39 (github.com/bdlm/error_test.TestErrors)",
        "error": "An error occurred"
    }
]

See the documentation for more examples.

Documentation

Overview

Package errors provides simple, concise, useful error handling and annotation. This package aims to implement the Error Inspection and Error Values Go2 draft designs.

https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md https://go.googlesource.com/proposal/+/master/design/go2draft.md

import (
	"github.com/bdlm/errors/v2"
)

One of the biggest frustrations with Go error handling is the lack of forensic and meta information errors should provide. By default errors are just a string and possibly a type. They can't tell you where they occurred or the path through the call stack they followed. The error implementation in Go is robust enough to control program flow but it's not very efficient for troubleshooting or analysis.

Since the idom in Go is that we pass the error back up the stack anyway:

if nil != err {
	return err
}

it's trivial to make errors much more informative with a simple error package. `bdlm/errors` makes this easy and supports tracing the call stack and the error callers with relative ease. Custom error types are also fully compatible with this package and can be used freely.

Install

go get github.com/bdlm/errors/v2

Quick Start

All package methods work with any `error` type as well as `nil` values, and error instances implement the Unwrap, Is, Marshaler, and Formatter interfaces as well as the github.com/bdlm/std/errors interfaces.

Create an error:

var MyError = errors.New("My error")

Create an error using formatting verbs:

var MyError = errors.Errorf("My error #%d", 1)

Wrap an error:

if nil != err {
	return errors.Wrap(err, "the operation failed")
}

Wrap an error with another error:

err := try1()
if nil != err {
	err2 := try2()
	if nil != err2 {
		return errors.Wrap(err, err2)
	}
	return err
}

Get the previous error, if any:

err := doWork()
if prevErr := errors.Unwrap(err); nil != prevErr {
	...
}

Test for a specific error type:

var MyError = errors.New("My error")
func main() {
	err := doWork()
	if errors.Is(err, MyError) {
		...
	}
}

Test to see if a specific error type exists anywhere in an error stack:

var MyError = errors.New("My error")
func main() {
	err := doWork()
	if errors.Has(err, MyError) {
		...
	}
}

Iterate through an error stack:

err := doWork()
for nil != err {
	fmt.Println(err)
	err = errors.Unwrap(err)
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func As added in v2.1.0

func As(err, test error) error

As searches the error stack for an error that can be cast to the test argument, which must be a pointer. If it succeeds it performs the assignment and returns the result, otherwise it returns nil.

func Caller

func Caller(err error) std_caller.Caller

Caller returns the Caller associated with an error, if any.

func Is

func Is(err, test error) bool

Is reports whether any error in err's chain matches test.

The chain consists of err itself followed by the sequence of errors obtained by repeatedly calling Unwrap.

An error is considered to match a test if it is equal to that test or if it implements a method Is(error) bool such that Is(test) returns true.

An error type might provide an Is method so it can be treated as equivalent to an existing error. For example, if MyError defines

func (m MyError) Is(test error) bool { return test == os.ErrExist }

then Is(MyError{}, os.ErrExist) returns true. See syscall.Errno.Is for an example in the standard library.

func NewCaller

func NewCaller() std_caller.Caller

NewCaller returns a new Caller containing data for the current call stack.

func Unwrap

func Unwrap(err error) error

Unwrap returns the previous error.

Example
err1 := errors.New("error 1")
err2 := errors.Wrap(err1, "error 2")
err := errors.Unwrap(err2)

fmt.Println(err)
Output:

error 1
Example (IterateStack)
err := loadConfig()

// Iterate through an error stack, last in - first out.
for err != nil {
	fmt.Printf("%+v\n", err)
	err = errors.Unwrap(err)
}
Output:

service configuration could not be loaded - #0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig); could not decode configuration data - #1 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig); could not read configuration file - #2 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig); read: end of input - #3 n/a
could not decode configuration data - #0 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig); could not read configuration file - #1 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig); read: end of input - #2 n/a
could not read configuration file - #0 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig); read: end of input - #1 n/a
read: end of input - #0 n/a

Types

type E

type E struct {
	// contains filtered or unexported fields
}

E is a github.com/bdlm/std.Error interface implementation and simply wraps the exported package methods as a convenience.

func Errorf

func Errorf(msg string, data ...interface{}) *E

Errorf formats according to a format specifier and returns an error that contains caller data.

func New

func New(msg string) *E

New returns an error that contains caller data.

Example
err := errors.New("this is an error message")

fmt.Println(err)
Output:

this is an error message

func Trace

func Trace(e error) *E

Trace adds an additional caller line to the error trace trace on an error to aid in debugging and forensic analysis.

func Track

func Track(e error) *E

Track updates the error stack with additional caller data.

func Wrap

func Wrap(e error, msg string, data ...interface{}) *E

Wrap returns a new error that wraps the provided error.

Example
// Wrap an error with additional metadata.
err := loadConfig()
err = errors.Wrap(err, "loadConfig returned an error")

fmt.Printf("% +v", err)
Output:

loadConfig returned an error - #0 examples_test.go:180 (github.com/bdlm/errors/v2_test.ExampleWrap);
service configuration could not be loaded - #1 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig);
could not decode configuration data - #2 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig);
could not read configuration file - #3 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig);
read: end of input - #4 n/a

func WrapE

func WrapE(e, err error) *E

WrapE returns a new error that wraps the provided error.

Example
var internalServerError = grpcErrors.Error(
	grpcCodes.Internal,
	"internal server error",
)

// Wrap an error with another error to maintain context.
err := loadConfig()
if nil != err {
	err = errors.WrapE(err, internalServerError)
}

fmt.Printf("% +v", err)
Output:

rpc error: code = Internal desc = internal server error - #0 examples_test.go:199 (github.com/bdlm/errors/v2_test.ExampleWrapE);
service configuration could not be loaded - #1 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig);
could not decode configuration data - #2 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig);
could not read configuration file - #3 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig);
read: end of input - #4 n/a

func (*E) Caller

func (e *E) Caller() std_caller.Caller

Caller implements std_error.Caller.

func (*E) Error

func (e *E) Error() string

Error implements std_error.Error.

func (*E) Format

func (e *E) Format(state fmt.State, verb rune)

Format implements fmt.Formatter. https://golang.org/pkg/fmt/#hdr-Printing

Verbs:

%s      Returns the error string of the last error added
%v      Alias for %s

Flags:

#      JSON formatted output, useful for logging
-      Output caller details, useful for troubleshooting
+      Output full error stack details, useful for debugging
' '    (space) Add whitespace formatting for readability, useful for development

Examples:

%s:    An error occurred
%v:    An error occurred
%-v:   #0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors) - An error occurred
%+v:   #0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors) - An error occurred #1 stack_test.go:39 (github.com/bdlm/error_test.TestErrors) - An error occurred
%#v:   {"error":"An error occurred"}
%#-v:  {"caller":"#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors)","error":"An error occurred"}
%#+v:  [{"caller":"#0 stack_test.go:40 (github.com/bdlm/error_test.TestErrors)","error":"An error occurred"},{"caller":"#0 stack_test.go:39 (github.com/bdlm/error_test.TestErrors)","error":"An error occurred"}]
Example (Json)
err := loadConfig()
fmt.Printf("%#v", err)
Output:

[{"error":"service configuration could not be loaded"}]
Example (JsonDetail)
err := loadConfig()
fmt.Printf("%#-v", err)
Output:

[{"caller":"#0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig)","error":"service configuration could not be loaded"}]
Example (JsonDetailPreformat)
err := loadConfig()
fmt.Printf("% #-v", err)
Output:

[
    {
        "caller": "#0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig)",
        "error": "service configuration could not be loaded"
    }
]
Example (JsonPreformat)
err := loadConfig()
fmt.Printf("% #v", err)
Output:

[
    {
        "error": "service configuration could not be loaded"
    }
]
Example (JsonTrace)
err := loadConfig()
fmt.Printf("%#+v", err)
Output:

[{"caller":"#0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig)","error":"service configuration could not be loaded"},{"caller":"#1 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig)","error":"could not decode configuration data"},{"caller":"#2 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig)","error":"could not read configuration file"},{"caller":"#3 n/a","error":"read: end of input"}]
Example (JsonTracePreformat)
err := loadConfig()
fmt.Printf("% #+v", err)
Output:

[
    {
        "caller": "#0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig)",
        "error": "service configuration could not be loaded"
    },
    {
        "caller": "#1 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig)",
        "error": "could not decode configuration data"
    },
    {
        "caller": "#2 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig)",
        "error": "could not read configuration file"
    },
    {
        "caller": "#3 n/a",
        "error": "read: end of input"
    }
]
Example (String)
err := loadConfig()
fmt.Println(err)
Output:

service configuration could not be loaded
Example (StringDetail)
err := loadConfig()
fmt.Printf("%-v", err)
Output:

service configuration could not be loaded - #0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig);
Example (StringDetailPreformat)
err := loadConfig()
fmt.Printf("% -v", err)
Output:

service configuration could not be loaded - #0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig);
Example (StringPreformat)
err := loadConfig()
fmt.Printf("% v", err)
Output:

service configuration could not be loaded
Example (StringTrace)
err := loadConfig()
fmt.Printf("%+v", err)
Output:

service configuration could not be loaded - #0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig); could not decode configuration data - #1 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig); could not read configuration file - #2 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig); read: end of input - #3 n/a
Example (StringTracePreformat)
err := loadConfig()
fmt.Printf("% +v", err)
Output:

service configuration could not be loaded - #0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig);
could not decode configuration data - #1 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig);
could not read configuration file - #2 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig);
read: end of input - #3 n/a

func (*E) Is

func (e *E) Is(test error) bool

Is implements std_error.Error.

func (*E) MarshalJSON

func (e *E) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaller interface.

Example (Marshal)
err := loadConfig()
jsn, _ := json.Marshal(err)

fmt.Println(string(jsn))
Output:

[{"caller":"#0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig)","error":"service configuration could not be loaded"},{"caller":"#1 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig)","error":"could not decode configuration data"},{"caller":"#2 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig)","error":"could not read configuration file"},{"caller":"#3 n/a","error":"read: end of input"}]
Example (MarshalIndent)
err := loadConfig()
jsn, _ := json.MarshalIndent(err, "", "    ")

fmt.Println(string(jsn))
Output:

[
    {
        "caller": "#0 mocks_test.go:16 (github.com/bdlm/errors/v2_test.loadConfig)",
        "error": "service configuration could not be loaded"
    },
    {
        "caller": "#1 mocks_test.go:21 (github.com/bdlm/errors/v2_test.decodeConfig)",
        "error": "could not decode configuration data"
    },
    {
        "caller": "#2 mocks_test.go:26 (github.com/bdlm/errors/v2_test.readConfig)",
        "error": "could not read configuration file"
    },
    {
        "caller": "#3 n/a",
        "error": "read: end of input"
    }
]

func (*E) Unwrap

func (e *E) Unwrap() error

Unwrap implements std_error.Wrapper.

Jump to

Keyboard shortcuts

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