errors

package module
v1.0.3 Latest Latest
Warning

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

Go to latest
Published: Apr 18, 2024 License: MIT Imports: 12 Imported by: 12

README

Error Handling with eluv-io/errors-go

CodeQL

The package eluv-io/errors-go makes Go error handling simple, intuitive and effective.

err := someFunctionThatCanFail()
if err != nil {
    return errors.E("file download", errors.K.IO, err, "file", f, "user", usr)
}

The eluv-io/errors-go package promotes and facilitates these main design principles:

  • augment errors with relevant context information all along the call stack
  • provide this additional information in a structured form
  • provide call stack traces with minimal overhead if enabled through build tag and runtime config
  • integrate seamlessly with the structured logging convention of eluv-io/log

For sample code, see

Note that some of the unit tests and examples use the "old-style" explicit functions WithOp(op), WithKind(kind) and WithCause(err) functions to set values. They are not really needed anymore, and code can be simplified by using errors.E() exclusively. Furthermore, the use of templates makes the code even more compact in many situations - see sections below...

Creating & Wrapping Errors with E() and NoTrace()

The eluv-io/errors-go package provides one main function to create new or wrap existing errors with additional information:

// E creates a new error initialized with the given (optional) operation, kind, cause and key-value fields.
// All arguments are optional. If the first argument is a `string`, it is considered to be the operation.
//
// If no kind is specified, K.Other is assumed.
func E(args ...interface{}) *Error {...}

It's important to note that the first argument in E() - if it of type string - is the operation that returns the error, and not the error's reason. See the following examples:

// operation "file download" (op) failed with an "IO" error (kind) due to
// "err" (cause)
errors.E("file download", errors.K.IO, err)

// better than above with additional context information provided as key-value pairs
errors.E("file download", errors.K.IO, err, "file", f, "user", usr)

// sometimes there is no originating error (cause): use "reason" key-value
// pair to explain the cause
errors.E("validate configuration", errors.K.Invalid, 
	"reason", "part store location not specified")

// use "reason" for further clarification even if there is a cause,
// especially if the cause is a regular error (not an *errors.Error)
timeout, err := strconv.Atoi(timeoutString)
if err != nil {
	return errors.E("validate configuration", errors.K.Invalid, err,
		"reason", "failed to parse timeout",
		"timeout", timeoutString)
}

Note that supplementary context information (file & user in the example above) is provided as explicit key-value pairs instead of an opaque, custom-assembled string.

Use errors.NoTrace() instead of errors.E() to prevent recording and printing the stacktrace - see below for more information.

Besides the flexible E() function, the library's Error object also offers explicit methods for setting the different types of data:

errors.E().Op("file download").Kind(errors.K.IO).Cause(err).With("file", f).With("user", usr)

Since that code is just more verbose, the more compact E() function call with parameters should be preferred.

Reducing Code Complexity & Duplication with Template() and TemplateNoTrace

Template() or its shorter alias T() offers a great way to reduce code duplication in producing consistent errors. Imagine a validation function that checks multiple conditions and returns a corresponding error on any violations:

if len(path) == 0 {
    return errors.E("validation", errors.K.Invalid, "id", id, "reason", "no path")
}
if strings.ContainsAny(path, `$~\:`) {
    return errors.E("validation", errors.K.Invalid, "id", id, "reason", "contains illegal characters")
}
target, err := os.Readlink(path)
if err != nil {
    return errors.E("validation", errors.K.IO, err, "id", id, "reason", "not a link")
}

With a template function, this code can be rewritten in a more compact and concise form:

e := errors.Template("validation", errors.K.Invalid, "id", id)
if len(path) == 0 {
    return e("reason", "no path")
}
if strings.ContainsAny(path, `$~\:`) {
    return e("reason", "contains illegal characters")
}
target, err := os.Readlink(path)
if err != nil {
    return e(errors.K.IO, err, "reason", "not a link")
}

Use TemplateNoTrace() or its alias TNoTrace instead of Template() to prevent recording and printing the stacktrace in the generated error.

Use the template's IfNotNil() function to simplify error returns:

e := errors.Template(...)
err := someFunc(...)
return e.IfNotNil(err)

IfNotNil returns nil if err is nil and instantiates and *Error according to the template otherwise. It allows also to pass additional key-value pairs, e.g. e.IfNotNil(err, "key", "val")

Recording & Printing Call Stack Traces

Creating an error with E() or Template() per default also records the program counters of the current call stack, and Error() formats and prints the call stack:

op [getConfig] kind [invalid] cause:
	op [readFile] kind [I/O error] filename [illegal-filename|*] cause [open illegal-filename|*: no such file or directory]
	github.com/eluv-io/errors-go/stack_example_test.go:13 readFile()
	github.com/eluv-io/errors-go/stack_example_test.go:19 getConfig()
	github.com/eluv-io/errors-go/stack_example_test.go:21 getConfig()
	github.com/eluv-io/errors-go/stack_example_test.go:30 ExampleE()
	testing/run_example.go:64                             runExample()
	testing/example.go:44                                 runExamples()
	testing/testing.go:1505                               (*M).Run()
	_testmain.go:165                                      main()

Stack traces from multiple nested Errors are automatically coalesced into a single, comprehensive stack trace that is printed at the end.

The following global variables control stack trace handling at runtime:

  • PopulateStacktrace controls whether program counters are recorded when an Error is created
  • PrintStacktrace controls whether stack traces are printed in Error.Error()
  • PrintStacktracePretty controls the formatting of stack traces
  • MarshalStacktrace controls whether stack traces are marshalled to JSON

The following functions create an Error without stack trace:

  • errors.NoTrace()
  • errors.TemplateNoTrace()
  • errors.TNoTrace()

The following functions allow to suppress or remove stack traces from an Error:

  • errors.ClearStacktrace()
  • Error.FormatTrace()
  • Error.ClearStacktrace()

In addition, stack trace recording can be completely disabled at compile time with the errnostack build tag.

Error Lists with Append()

The Append() function allows collecting multiple errors in an error list and treat that list as a regular error:

func processBatch(items []work) error {
	var errs error
	for idx, item := range items {
		err := item.Execute()
		if err != nil {
			errs = errors.Append(errs, errors.E(err, "item", idx)
		}
	}
	return errs
}

errors.Append(list, err) appends a given error (or multiple errors) to an error list. The list itself can start out as nil or an initial regular error, in which case the function appends the errors to a new errors.List and returns the list.

An error list's string representation includes all appended errors:

error-list count [2]
    0: op [read] kind [I/O error] cause [EOF]
    1: op [write] kind [operation cancelled]

With stack traces:

error-list count [2]
    0: op [read] kind [I/O error] cause [EOF]
    github.com/eluv-io/errors-go/list_test.go:192 ExampleAppend()
    testing/run_example.go:64                     runExample()
    testing/example.go:44                         runExamples()
    testing/testing.go:1505                       (*M).Run()
    _testmain.go:165                              main()

    1: op [write] kind [operation cancelled]
    github.com/eluv-io/errors-go/list_test.go:192 ExampleAppend()
    testing/run_example.go:64                     runExample()
    testing/example.go:44                         runExamples()
    testing/testing.go:1505                       (*M).Run()
    _testmain.go:165                              main()

Integration with eluv-io/log-go

Thanks to providing all data as key-value pairs (including operation, kind and cause), the Error objects integrate seamlessly with the logging package whose main principle is that of structured logging. This means, for example, that errors are marshaled as JSON objects when the JSON handler is used for logging:

{
  "fields": {
    "error": {
      "cause": "EOF",
      "file": "/tmp/app-config.yaml",
      "kind": "I/O error",
      "op": "failed to parse config",
      "stacktrace": "runtime/asm_amd64.s:2337: runtime.goexit:\n\truntime/proc.go:195: ...main:\n\tsample/log_sample.go:66: main.main:\n\teluvio/log/sample/sub/sub.go:17: eluvio/log/sample/sub.Call"
    },
    "logger": "/eluvio/log/sample/sub"
  },
  "level": "warn",
  "timestamp": "2018-03-02T16:52:04.831737+01:00",
  "message": "failed to read config, using defaults"
}

Notice the "error" object: it is a JSON object with all data provided as fields. In addition, the error also contains a stacktrace when logged in JSON format.

Documentation

Overview

Package errors provides sophisticated, but simple error handling for Go. Refer to the README.md, examples and unit tests for usage and details.

The original idea and code was based on https://github.com/upspin/upspin/blob/master/errors/errors.go, but has undergone substantial re-factoring to simplify usage and provide additional flexibility.

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultFieldOrder []string = nil

DefaultFieldOrder defines the default order of an Error's fields in its String and JSON representations.

The first empty string "" acts as alias for all fields that are unreferenced in the field order slice. They are printed in the order they were added in E() and With().

Trailing field keys after "" will be printed after any unreferenced fields.

"op", "kind" and "cause" fields are specially treated if they don't appear in the order slice. "op" and "kind" will be printed first among the unreferenced fields. "cause" will be printed last (even after all trailing fields). Hence the default nil is equivalent to []string{"op", "kind", "", "cause"}

View Source
var K = struct {
	Other          Kind // Unclassified error.
	NotImplemented Kind // The functionality is not yet implemented.
	Invalid        Kind // Invalid operation for this type of item.
	Permission     Kind // Permission denied.
	IO             Kind // External I/O error such as network failure.
	Exist          Kind // Item already exists.
	NotExist       Kind // Item does not exist. Also see NotFound!
	NotFound       Kind // Item should exist but cannot be found. Also see NotExist!
	NotDir         Kind // Item is not a directory.
	Finalized      Kind // Part or content is already finalized.
	NotFinalized   Kind // Part or content is not yet finalized.
	NoNetRoute     Kind // No route found for the requested operation
	Internal       Kind // Generic internal error
	AVProcessing   Kind // error in audio/video processing
	AVInput        Kind // error encountered in media input (stall, corruption, unexpected)
	NoMediaMatch   Kind // None of the accepted media type matches the content
	Unavailable    Kind // The server cannot handle the request temporarily (e.g. overloaded or down for maintenance).
	Cancelled      Kind // The operation was cancelled.
	Timeout        Kind // The operation was timed out.
	Warn           Kind // The error is not an actual error, but rather a warning that something might be wrong.
}{
	Other:          "unclassified error",
	NotImplemented: "not implemented",
	Invalid:        "invalid",
	Permission:     "permission denied",
	IO:             "I/O error",
	Exist:          "item already exists",
	NotExist:       "item does not exist",
	NotFound:       "item cannot be found",
	Finalized:      "item is already finalized",
	NotFinalized:   "item is not finalized",
	NoNetRoute:     "no route found",
	Internal:       "internal error",
	AVProcessing:   "a/v processing error",
	AVInput:        "a/v input error",
	NoMediaMatch:   "no media type match",
	Unavailable:    "service unavailable",
	Cancelled:      "operation cancelled",
	Timeout:        "operation timed out",
	Warn:           "warning",
}

K defines the kinds of errors.

View Source
var MarshalStacktrace = true

MarshalStacktrace controls whether stacktraces are marshaled to JSON or not. If enabled, an extra "stacktrace" field is added to the error's JSON struct.

View Source
var MarshalStacktraceAsArray = true

MarshalStacktraceAsArray controls whether stacktraces are marshaled to JSON as a single string blob or as JSON array containing the individual lines of the stacktrace.

View Source
var NilError = (*nilError)(nil)

NilError is an error that represents a "nil" error value, but allows to call the Error() method without panicking. NilError.Error() returns the empty string "".

View Source
var PrintStacktrace = true

PrintStacktrace controls whether stacktraces are printed per default or not.

View Source
var PrintStacktracePretty = true

PrintStacktracePretty enables additional formatting of stacktraces by aligning functions to the longest source filename.

Pretty print:

github.com/eluv-io/errors-go/stack_with_long_filename_test.go:6 createErrorWithExtraLongFilename()
github.com/eluv-io/errors-go/stack_test.go:108                  func4()
github.com/eluv-io/errors-go/stack_test.go:109                  func4()
github.com/eluv-io/errors-go/stack_test.go:104                  T.func3()
github.com/eluv-io/errors-go/stack_test.go:99                   T.func2()
github.com/eluv-io/errors-go/stack_test.go:90                   func1()
github.com/eluv-io/errors-go/stack_test.go:47                   TestStack()
testing/testing.go:909                                          tRunner()
runtime/asm_amd64.s:1357                                        goexit()

Regular:

github.com/eluv-io/errors-go/stack_with_long_filename_test.go:6	createErrorWithExtraLongFilename()
github.com/eluv-io/errors-go/stack_test.go:108	func4()
github.com/eluv-io/errors-go/stack_test.go:109	func4()
github.com/eluv-io/errors-go/stack_test.go:104	T.func3()
github.com/eluv-io/errors-go/stack_test.go:99	T.func2()
github.com/eluv-io/errors-go/stack_test.go:90	func1()
github.com/eluv-io/errors-go/stack_test.go:47	TestStack()
testing/testing.go:909	tRunner()
runtime/asm_amd64.s:1357	goexit()
View Source
var Separator = ":\n\t"

Separator is the string used to separate nested errors. By default, nested errors are indented on a new line.

Functions

func Append

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

Append appends one or multiple errors `errs` to the *ErrorList err and returns the updated error list.

If err is not an *ErrorList (any other type of error or nil), then a new list is created and err added to it. Then all additional errs are appended, unwrapping them if any of them are ErrorLists themselves.

Any nil errors within errs will be ignored. If the final list contains a single error, the error is returned instead of the list.

Example
{
	fmt.Println("nil error:")
	fmt.Println(errors.Append(nil, nil))
	fmt.Println()
}

{
	var err error
	err = errors.Append(err, io.EOF)
	fmt.Println("single error:")
	fmt.Println(err)
	fmt.Println()
}

{
	var err error
	err = errors.Append(err, io.EOF)
	err = errors.Append(err, io.ErrNoProgress)
	err = errors.Append(err, io.ErrClosedPipe)
	fmt.Println("multiple errors:")
	fmt.Println(err)
}

{
	var err error
	err = errors.Append(errors.E("read", errors.K.IO, io.EOF), errors.E("write", errors.K.Cancelled))
	fmt.Println("complex errors:")
	fmt.Println(err)

	/*
		With stacktraces enabled it would look like this:
		error-list count [2]
			0: op [read] kind [I/O error] cause [EOF]
			github.com/eluv-io/errors-go/list_test.go:192 ExampleAppend()
			testing/run_example.go:64                     runExample()
			testing/example.go:44                         runExamples()
			testing/testing.go:1505                       (*M).Run()
			_testmain.go:165                              main()

			1: op [write] kind [operation cancelled]
			github.com/eluv-io/errors-go/list_test.go:192 ExampleAppend()
			testing/run_example.go:64                     runExample()
			testing/example.go:44                         runExamples()
			testing/testing.go:1505                       (*M).Run()
			_testmain.go:165                              main()
	*/    main()
	*/
}
Output:


nil error:
<nil>

single error:
EOF

multiple errors:
error-list count [3]
	0: EOF
	1: multiple Read calls return no data or error
	2: io: read/write on closed pipe

complex errors:
error-list count [2]
	0: op [read] kind [I/O error] cause [EOF]
	1: op [write] kind [operation cancelled]

func As

func As(err error, target interface{}) bool

As is a convenience function that simply calls the stdlib errors.As(): As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true.

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

An error matches target if the error's concrete value is assignable to the value pointed to by target, or if the error has a method As(interface{}) bool such that As(target) returns true. In the latter case, the As method is responsible for setting target.

As will panic if target is not a non-nil pointer to either a type that implements error, or to any interface type. As returns false if err is nil.

func ClearStacktrace

func ClearStacktrace(err error) error

ClearStacktrace removes the stacktrace from the given error if it's an instance of *Error. Does nothing otherwise.

func Field added in v1.0.3

func Field(err error, key string) interface{}

Field returns the result of calling the Field() method on the given err if it is an *Error. Returns nil otherwise.

func GetField

func GetField(err error, key string) (string, bool)

GetField returns the result of calling the GetField() method on the given err if it is an *Error. Returns "", false otherwise.

func GetRootCause

func GetRootCause(err error) error

GetRootCause returns the first nested error that is not an *Error object. Returns NilError if err is nil.

func Ignore

func Ignore(f func() error)

Ignore simply ignores a potential error returned by the given function.

Useful in defer statements where the deferred function returns an error, i.e.

defer writer.Close()

can be written as

defer errors.Ignore(writer.Close)

func Is

func Is(err, target error) bool

Is is a convenience function that simply calls the stdlib errors.Is() Is reports whether any error in err's chain matches target.

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

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

func IsKind

func IsKind(expected Kind, err interface{}) bool

IsKind reports whether err is an *Error of the given Kind. Returns false if err is nil.

func IsNotExist

func IsNotExist(err error) bool

IsNotExist reports whether err is an *Error of Kind NotExist. Returns false if err is nil.

func Match

func Match(err1, err2 error) bool

Match compares two errors.

If one of the arguments is not of type *Error, Match will return reflect.DeepEqual(err1, err2).

Otherwise it returns true iff every non-zero element of the first error is equal to the corresponding element of the second. If the Cause field is a *Error, Match recurs on that field; otherwise it compares the strings returned by the Error methods. Elements that are in the second argument but not present in the first are ignored.

For example:

Match(errors.E("authorize", errors.Permission), err)

tests whether err is an Error with op=authorize and kind=Permission.

Example
err := errors.Str("network unreachable")

// Construct an error, one we pretend to have received from a test.
got := errors.E("get", errors.K.IO, err)

// Now construct a reference error, which might not have all
// the fields of the error from the test.
expect := errors.E().WithKind(errors.K.IO).WithCause(err)

fmt.Println("Match:", errors.Match(expect, got))

// Now one that's incorrect - wrong Kind.
got = errors.E().WithOp("get").WithKind(errors.K.Permission).WithCause(err)

fmt.Println("Mismatch:", errors.Match(expect, got))
Output:


Match: true
Mismatch: false

func PopulateStacktrace

func PopulateStacktrace() bool

func SetPopulateStacktrace added in v1.0.3

func SetPopulateStacktrace(b bool)

func Str

func Str(text string) error

Str is an alias for the standard errors.New() function

func TypeOf

func TypeOf(val interface{}) string

TypeOf returns the type of the given value as string.

func Unwrap

func Unwrap(err error) error

Unwrap is a convenience function that simply calls the stdlib errors.Unwrap(): Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.

func UnwrapAll

func UnwrapAll(err error) error

UnwrapAll returns the last non-nil error when repeatedly calling Unwrap on the given error. Returns NilError if err is nil.

Types

type DefaultKind

type DefaultKind string

DefaultKind is a Kind that can be set on an Error or Template that is only used if the kind is not otherwise set e.g. with an explicit call to Error.Kind(kind) or by inheriting it from a nested error.

	e := errors.Template("read user", errors.K.Invalid.Default())
 return e(nested)

In the above example, the returned error will have the nested error's kind if it's defined or K.Invalid otherwise. If the template definition didn't use Default(), the returned error would always be K.Invalid, regardless of the kind in the nested error.

type Error

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

Error is the type that implements the error interface and which is returned by E(), NoTrace(), etc.

Example
// Single error.
e1 := errors.E("get", errors.K.IO, io.EOF)
fmt.Println("\nSimple error:")
fmt.Println(e1)

// Nested error.
fmt.Println("\nNested error:")
e2 := errors.E("read", e1)
fmt.Println(e2)
Output:


Simple error:
op [get] kind [I/O error] cause [EOF]

Nested error:
op [read] kind [I/O error] cause:
	op [get] kind [I/O error] cause [EOF]

func E

func E(args ...interface{}) *Error

E creates a new error initialized with the given (optional) operation, kind, cause and key-value fields. All arguments are optional, but if provided they have to be specified in that order.

If no kind is specified, the kind of the cause is used if available. Otherwise, K.Other is assigned.

The op should specify the operation that failed, e.g. "download" or "load config". It should not be an error "message" like "download failed" or "failed to load config" - the fact that the operation has failed is implied by the error itself.

Examples:

errors.E()                             --> error with kind set to errors.K.Other, all other fields empty
errors.E("download")                   --> same as errors.E().WithOp("download")
errors.E("download", errors.K.IO)      --> same as errors.E().WithOp("download").WithKind(errors.K.IO)
errors.E("download", errors.K.IO, err) --> same as errors.E().WithOp("download").WithKind(errors.K.IO).WithCause(err)
errors.E("download", errors.K.IO, err, "file", f, "user", usr) --> same as errors.E()...With("file", f).With("user", usr)
errors.E(errors.K.NotExist, "file", f) --> same as errors.E().WithKind(errors.K.NotExist).With("file", f)
Example
reset := enableStacktraces()
defer reset()

err := getConfig()
printError(err)
Output:


op [getConfig] kind [I/O error] cause:
	op [readFile] kind [I/O error] filename [illegal-filename|*] cause [open illegal-filename|*: no such file or directory]
	github.com/eluv-io/errors-go/stack_example_test.go:   readFile()
	github.com/eluv-io/errors-go/stack_example_test.go:   getConfig()
	github.com/eluv-io/errors-go/stack_example_test.go:   getConfig()
	github.com/eluv-io/errors-go/stack_example_test.go:   ExampleE()

func FromContext

func FromContext(ctx context.Context, args ...interface{}) *Error

FromContext creates an error from the given context and additional error arguments as passed to E(). It returns

  • nil if ctx.Err() returns nil
  • an error from the given args and kind Timeout if the ctx timed out
  • an error from the given args and kind Cancelled if the ctx was cancelled
  • an error from the given args and the cause set to ctx.Err() otherwise.

func GetRoot

func GetRoot(err interface{}) *Error

GetRoot returns the innermost nested *Error of the given error, or nil if the provided object is not an *Error.

func NoTrace

func NoTrace(args ...interface{}) *Error

NoTrace is the same as E, but does not populate a stack trace. Use in cases where the stacktrace is not desired.

func Wrap

func Wrap(err error, args ...interface{}) *Error

Wrap wraps the given error in an Error instance with E(err) if err is not an *Error itself - otherwise returns err as *Error unchanged. Returns nil if err is nil.

func (*Error) Cause

func (e *Error) Cause() error

Cause returns the error's cause or nil if no cause is set.

func (*Error) ClearStacktrace

func (e *Error) ClearStacktrace() *Error

ClearStacktrace creates a copy of this error and removes the stacktrace from it and all nested causes.

func (*Error) Error

func (e *Error) Error() string

Error returns the string presentation of this Error. Fields are ordered according to DefaultFieldOrder. The stacktrace is printed if available.

Example
defer resetDefaultFieldOrder()()

err := errors.E("get user", errors.K.IO, io.EOF, "account", "acme", "user", "joe")
fmt.Println(err)

errors.DefaultFieldOrder = []string{"op", "kind", "", "cause"} // same as default (nil)
fmt.Println(err)

errors.DefaultFieldOrder = []string{"kind"}
fmt.Println(err)

errors.DefaultFieldOrder = []string{"op", "user"}
fmt.Println(err)

errors.DefaultFieldOrder = []string{"op", "cause"}
fmt.Println(err)

fmt.Println()
fmt.Println("Nested:")
err = errors.E("get info", errors.K.Invalid, err)

errors.DefaultFieldOrder = nil
fmt.Println(err)

// not putting the "cause" last is a bad idea with nested errors...
errors.DefaultFieldOrder = []string{"op", "cause"}
fmt.Println(err)
Output:


op [get user] kind [I/O error] account [acme] user [joe] cause [EOF]
op [get user] kind [I/O error] account [acme] user [joe] cause [EOF]
kind [I/O error] op [get user] account [acme] user [joe] cause [EOF]
op [get user] user [joe] kind [I/O error] account [acme] cause [EOF]
op [get user] cause [EOF] kind [I/O error] account [acme] user [joe]

Nested:
op [get info] kind [invalid] cause:
	op [get user] kind [I/O error] account [acme] user [joe] cause [EOF]
op [get info] cause:
	op [get user] cause [EOF] kind [I/O error] account [acme] user [joe] kind [invalid]

func (*Error) ErrorNoTrace

func (e *Error) ErrorNoTrace() string

ErrorNoTrace returns the error as string just like Error() but omits the stack trace.

func (*Error) Field

func (e *Error) Field(key string) interface{}

Field returns the given field from this error or any nested errors. Returns nil if the field does not exist.

func (*Error) FormatError

func (e *Error) FormatError(printStack bool, fieldOrder ...string) string

FormatError converts this error to a string like String(), but prints fields according to the given field order. See DefaultFieldOrder for more information.

A stacktrace (if available) is printed if printStack is true.

Example
jsn := `{`
e := errors.Template("parse", errors.K.Invalid, "account", 5, "json", jsn)

var err error
var user map[string]interface{}
if err = json.Unmarshal([]byte(jsn), &user); err != nil {
	err = e(err, "reason", "failed to decode user")
}

fmt.Println(err)
fmt.Println(errors.Wrap(err).FormatError(false, "op", "kind", "reason", "", "cause"))
Output:


op [parse] kind [invalid] account [5] json [{] reason [failed to decode user] cause [unexpected end of JSON input]
op [parse] kind [invalid] reason [failed to decode user] account [5] json [{] cause [unexpected end of JSON input]

func (*Error) GetField

func (e *Error) GetField(key string) (string, bool)

GetField attempts to retrieve the field with the given key in this Error and returns its value converted to a string with fmt.Sprint(val). If the field doesn't exist, it tries to find it (recursively) in the 'cause' of the error. Returns the retrieved field value and true if found, or the empty string and false if not found.

func (*Error) Kind

func (e *Error) Kind() Kind

Kind returns the error's kind or errors.K.Other if no kind is set.

func (*Error) MarshalJSON

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

MarshalJSON marshals this error as a JSON object

func (*Error) Op

func (e *Error) Op() string

Op returns the error's operation or "" if no op is set.

func (*Error) UnmarshalJSON

func (e *Error) UnmarshalJSON(b []byte) error

UnmarshalJSON unmarshals the given JSON text, retaining the order of fields according to the JSON structure.

func (*Error) Unwrap

func (e *Error) Unwrap() error

func (*Error) With

func (e *Error) With(args ...interface{}) *Error

With adds additional context information in the form of key value pairs and returns this error instance for call chaining.

func (*Error) WithCause

func (e *Error) WithCause(err error) *Error

WithCause sets the given original error and returns this error instance for call chaining. If the cause is an *Error and this error's kind is not yet initialized, it inherits the kind of the cause.

func (*Error) WithDefaultKind

func (e *Error) WithDefaultKind(kind Kind) *Error

WithDefaultKind sets the given kind as default and returns this error instance for call chaining. The default kind is only used if the kind is not otherwise set e.g. with an explicit call to Error.Kind(kind) or by inheriting it from a nested error. It's equivalent to calling Error.With(kind.Default()).

func (*Error) WithKind

func (e *Error) WithKind(kind Kind) *Error

WithKind sets the given kind and returns this error instance for call chaining.

func (*Error) WithOp

func (e *Error) WithOp(op string) *Error

WithOp sets the given operation and returns this error instance for call chaining.

type ErrorList

type ErrorList struct {
	Errors []error
}

ErrorList is a collection of errors.

func UnmarshalJsonErrorList

func UnmarshalJsonErrorList(bts []byte) (ErrorList, error)

UnmarshalJsonErrorList unmarshals a list of errors. JSON objects are unmarshalled into Error objects, strings into generic errors created with Str(s). Empty objects or strings are ignored.

The exact JSON structure is:

{
  "errors": [
	{"op": "op1"},
	"EOF",
	{},
	{"op": "op2"}
  ]
}

Empty errors are removed from the returned list.

func (*ErrorList) Append

func (e *ErrorList) Append(errs ...error)

func (*ErrorList) Error

func (e *ErrorList) Error() string

Error returns the error list as a formatted, multi-line string.

func (*ErrorList) ErrorOrNil

func (e *ErrorList) ErrorOrNil() error

ErrorOrNil returns an error interface if this Error represents a list of errors, or returns nil if the list of errors is empty.

func (*ErrorList) MarshalJSON

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

func (*ErrorList) UnmarshalJSON

func (e *ErrorList) UnmarshalJSON(bts []byte) error

type Kind

type Kind string

Kind is the Go type for error kinds. Use the pre-defined kinds in errors.K, or

func (Kind) Default

func (k Kind) Default() DefaultKind

Default turns this Kind into a default value. See DefaultKind for more information.

Example
e := errors.Template("validate", errors.K.Invalid.Default())

nested1 := errors.E(io.EOF)
fmt.Println(e(nested1)) // kind [invalid]

nested2 := errors.E(errors.K.IO, io.EOF)
fmt.Println(e(nested2)) // kind [I/O error]
Output:


op [validate] kind [invalid] cause:
	kind [unclassified error] cause [EOF]
op [validate] kind [I/O error] cause:
	kind [I/O error] cause [EOF]

type TFn

type TFn func(err error, fields ...interface{}) error

type TemplateFn

type TemplateFn func(fields ...interface{}) *Error

func T

func T(fields ...interface{}) TemplateFn

T is an alias for Template.

func TNoTrace

func TNoTrace(fields ...interface{}) TemplateFn

TNoTrace is an alias for TemplateNoTrace

func Template

func Template(fields ...interface{}) TemplateFn

Template returns a function that creates a base error with an initial set of fields. When called, additional fields can be passed that complement the error template:

e := errors.Template("unmarshal", K.Invalid)
...
if invalid {
  return e("reason", "invalid format")
}
...
if err != nil {
  return e(err)
}
Example
var err error
e := errors.Template("validate", errors.K.Invalid)

// add fields
err = e("reason", "invalid character", "character", "$")
fmt.Println(err)

// override kind and set cause
err = e().WithKind(errors.K.IO).WithCause(io.EOF)
fmt.Println(err)

// Overriding kind and setting cause also works by simply passing them as
// args to the template function...
err = e(errors.K.IO, io.EOF)
fmt.Println(err)

// IfNotNil() returns an error only if the provided cause is not nil
err = e.IfNotNil(nil)
fmt.Println(err)

e = e.Add("sub", "validateNestedData")
err = e("reason", "missing hash")
fmt.Println(err)
Output:


op [validate] kind [invalid] reason [invalid character] character [$]
op [validate] kind [I/O error] cause [EOF]
op [validate] kind [I/O error] cause [EOF]
<nil>
op [validate] kind [invalid] sub [validateNestedData] reason [missing hash]

func TemplateNoTrace

func TemplateNoTrace(fields ...interface{}) TemplateFn

TemplateNoTrace is like Template but produces an error without stacktrace information.

func (TemplateFn) Add

func (t TemplateFn) Add(fields ...interface{}) TemplateFn

Add adds additional fields to this template.

Example
reset := enableStacktraces()
defer reset()

e := errors.Template("example", errors.K.Invalid)
e = e.Add("key", "value")
printError(e(io.EOF))
Output:


op [example] kind [invalid] key [value] cause [EOF]
	github.com/eluv-io/errors-go/stack_example_test.go:   ExampleTemplateFn_Add()

func (TemplateFn) Fields added in v1.0.1

func (t TemplateFn) Fields(moreFields ...interface{}) []interface{}

Fields returns the fields of the template, excluding "op", "kind" and "cause", appended with the given fields. This is useful in situations where the same information is used for logging and in errors.

func (TemplateFn) IfNotNil

func (t TemplateFn) IfNotNil(err error, fields ...interface{}) error

IfNotNil returns an error based on this template iff 'err' is not nil. Otherwise returns nil.

Example
reset := enableStacktraces()
defer reset()

e := errors.Template("example", errors.K.Invalid, "key", "value")
printError(e.IfNotNil(io.EOF))
Output:


op [example] kind [invalid] key [value] cause [EOF]
	github.com/eluv-io/errors-go/stack_example_test.go:   ExampleTemplateFn_IfNotNil()

func (TemplateFn) MarshalJSON added in v1.0.1

func (t TemplateFn) MarshalJSON() ([]byte, error)

MarshalJSON marshals this template to JSON.

func (TemplateFn) String added in v1.0.1

func (t TemplateFn) String() string

String returns a string representation of this template.

Jump to

Keyboard shortcuts

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