errors

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2019 License: BSD-3-Clause Imports: 6 Imported by: 25

README

errors

BSD-3-Clause Beta Build status Coverage status Go Report Card Github issues Github pull requests GoDoc

bdlm/errors provides simple, concise, useful error handling and annotation.

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.

Changelog

All notable changes to this project are documented in the CHANGELOG. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Quick start

See the documentation for more examples.

go get github.com/bdlm/errors
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)
}

See the documentation for more examples.

The Error interface

The exported package methods return an interface that exposes additional metadata and troubleshooting information:

// Error defines the error interface.
type Error interface {
	// Caller returns the associated Caller instance.
	Caller() Caller

	// Error implements error.
	Error() string

	// Has tests to see if the test error exists anywhere in the error
	// stack.
	Has(test error) bool

	// Is tests to see if the test error matches most recent error in the
	// stack.
	Is(test error) bool

	// Unwrap returns the next error, if any.
	Unwrap() Error
}

// Caller holds runtime.Caller data.
type Caller interface {
	// File returns the file in which the call occurred.
	File() string

	// Func returns the name of the function in which the call occurred.
	Func() string

	// Line returns the line number in the file in which the call occurred.
	Line() int

	// Pc returns the program counter.
	Pc() uintptr

	// Trace returns the call stack.
	Trace() []Caller
}

Documentation

Overview

Package errors provides simple, concise, useful error handling and annotation.

import (
	errs "github.com/mkenney/go-errors"
)

One of the biggest frustrations with Go error handling is the lack of forensic and meta information errors can provide. Out of the box 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. This package 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.

Quick start

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 Has added in v0.2.0

func Has(err, test error) bool

Has returns whether an error or an error stack stack is or contains the referenced error type.

func Is added in v0.2.0

func Is(err, test error) bool

Is returns whether an error or an error stack stack is the referenced error type.

Types

type Caller

type Caller interface {
	// File returns the file in which the call occurred.
	File() string

	// Func returns the name of the function in which the call occurred.
	Func() string

	// Line returns the line number in the file in which the call occurred.
	Line() int

	// Pc returns the program counter.
	Pc() uintptr

	// Trace returns the call stack.
	Trace() []Caller
}

Caller holds runtime.Caller data for an error.

func GetCaller added in v0.2.0

func GetCaller(err error) Caller

GetCaller returns the Caller associated with an Error, if any.

func NewCaller added in v0.2.0

func NewCaller() Caller

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

type E added in v0.2.0

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

E is an Error interface implementation and simply wraps the exported methods as a convenience.

func (E) Caller added in v0.2.0

func (e E) Caller() Caller

Caller implements Error.

func (E) Error added in v0.2.0

func (e E) Error() string

Error implements Error.

func (E) Format added in v0.2.0

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_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_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_test.loadConfig)","error":"service configuration could not be loaded"},{"caller":"#1 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig)","error":"could not decode configuration data"},{"caller":"#2 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig)","error":"could not read configuration file"}]
Example (JsonTracePreformat)
err := loadConfig()
fmt.Printf("% #+v", err)
Output:

[
    {
        "caller": "#0 mocks_test.go:16 (github.com/bdlm/errors_test.loadConfig)",
        "error": "service configuration could not be loaded"
    },
    {
        "caller": "#1 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig)",
        "error": "could not decode configuration data"
    },
    {
        "caller": "#2 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig)",
        "error": "could not read configuration file"
    }
]
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_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_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_test.loadConfig); could not decode configuration data - #1 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig); could not read configuration file - #2 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig);
Example (StringTracePreformat)
err := loadConfig()
fmt.Printf("% +v", err)
Output:

service configuration could not be loaded - #0 mocks_test.go:16 (github.com/bdlm/errors_test.loadConfig);
could not decode configuration data - #1 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig);
could not read configuration file - #2 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig);

func (E) Has added in v0.2.0

func (e E) Has(test error) bool

Has implements Error.

func (E) Is added in v0.2.0

func (e E) Is(test error) bool

Is implements Error.

func (E) MarshalJSON added in v0.2.0

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_test.loadConfig)","error":"service configuration could not be loaded"},{"caller":"#1 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig)","error":"could not decode configuration data"},{"caller":"#2 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig)","error":"could not read configuration file"}]
Example (MarshalIndent)
err := loadConfig()
jsn, _ := json.MarshalIndent(err, "", "    ")

fmt.Println(string(jsn))
Output:

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

func (E) Unwrap added in v0.2.0

func (e E) Unwrap() Error

Unwrap implements Error.

type Error added in v0.2.0

type Error interface {
	// Caller returns the associated Caller instance.
	Caller() Caller

	// Error implements error.
	Error() string

	// Has tests to see if the test error exists anywhere in the error
	// stack.
	Has(test error) bool

	// Is tests to see if the test error matches most recent error in the
	// stack.
	Is(test error) bool

	// Unwrap returns the next error, if any.
	Unwrap() Error
}

Error defines the interface for accessing the details of an error managed by this package.

func Errorf added in v0.2.0

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

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

func New

func New(msg string) Error

New returns an error that contains caller data.

Example
package main

import (
	"fmt"

	errors "github.com/bdlm/errors"
)

func main() {
	err := errors.New("this is an error message")

	fmt.Println(err)
}
Output:

this is an error message

func Trace added in v0.2.0

func Trace(e error) Error

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

func Track added in v0.2.0

func Track(e error) Error

Track updates the error stack with additional caller data.

func Unwrap added in v0.2.0

func Unwrap(e error) Error

Unwrap returns the previous error.

Example
package main

import (
	"fmt"

	errors "github.com/bdlm/errors"
)

func main() {
	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_test.loadConfig); could not decode configuration data - #1 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig); could not read configuration file - #2 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig);
could not decode configuration data - #0 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig); could not read configuration file - #1 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig);
could not read configuration file - #0 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig);
read: end of input - #0 examples_test.go:162 (github.com/bdlm/errors_test.ExampleUnwrap_iterateStack);

func Wrap

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

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:174 (github.com/bdlm/errors_test.ExampleWrap);
service configuration could not be loaded - #1 mocks_test.go:16 (github.com/bdlm/errors_test.loadConfig);
could not decode configuration data - #2 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig);
could not read configuration file - #3 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig);

func WrapE added in v0.2.0

func WrapE(e, err error) Error

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:192 (github.com/bdlm/errors_test.ExampleWrapE);
service configuration could not be loaded - #1 mocks_test.go:16 (github.com/bdlm/errors_test.loadConfig);
could not decode configuration data - #2 mocks_test.go:21 (github.com/bdlm/errors_test.decodeConfig);
could not read configuration file - #3 mocks_test.go:26 (github.com/bdlm/errors_test.readConfig);

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳