errors

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 15, 2024 License: Apache-2.0 Imports: 12 Imported by: 9

README

Errors

This package provides a suite of tools meant to work with Go 1.13 error wrapping and Go 1.20 multierrors. These helpers allow users to rely on standard Go error patterns while they include some "missing pieces" or additional features that are useful in practice.

Another errors package? Why does Go need all of these error libraries?

Because the language and the standard library have a minimal approach to error handling that leaves out some primitives power users expect to have on hand.

Important among these primitives are stack traces, explicit error collection types (multierrors and error groups) and error context management.

While Go 1.13 introduced error wrapping utilities and Go 1.20 added a minimal multierror collection, it is really useful to have a few more tools on hand.

Installation

This package may not be used in environments:

  1.   running Go 1.19 or lower;
  2.   running on 32-bit architecture;
  3.   running on Windows (currently not supported).

Add the following to your file:

import "github.com/secureworks/errors"

Then, when you run any Go command the toolchain will resolve and fetch the required modules automatically.

If you are using Go 1.13 to Go 1.19, you should use the previous version of this library, which has the same functionality but does not support the specific form that Go 1.20 multierrors take:

$ go get github.com/secureworks/errors@v0.1.2

Because this package re-exports the package-level functions of the standard library "errors" package, you do not need to include that package as well to get New, As, Is, Unwrap, and Join.

Note that Join is a special case: for consistency, and since our multierror is a better implementation, Join returns our implementation (which uses our formatting), not the standard library's implementation.

Use

Documentation is available on pkg.go.dev. You may also look at the examples in the package.

The most important features are listed below.

Package github.com/secureworks/errors:

  • use in place of the standard library with no change in behavior;
  • use the errors.MultiError type as either an explicit multierror implementation or as an implicit multierror passed around with the default error interface; use errors.Join, errors.Append and others to simplify multierror management in your code;
  • embed (singular) stack frames with errors.NewWithFrame("..."), errors.WithFrame(err), and fmt.Errorf("...: %w", err);
  • embed stack traces with errors.NewWithStackTrace("...") and errors.WithStackTrace(err);
  • remove error context with errors.Mask(err), errors.Opaque(err), and errors.WithMessage(err, "...");
  • marshal and unmarshal stack traces as text or JSON.

Package github.com/secureworks/errors/syncerr:

  • use syncerr.CoordinatedGroup to run a group of go routines (in parallel or in series) and synchronize the controlling process on their completion; in essence do exactly what is done in golang.org/x/sync/errgroup;
  • use syncerr.ParallelGroup to run a group of go routines in parallel and coalesce their results into a single multierror.
Roadmap

Possible improvements before reaching v1.0 include:

  • Add support for Windows filepaths in call frames.
  • Include either a linter or a suggested golang-ci lint YAML to support idiomatic use.
License

This library is distributed under the Apache-2.0 license found in the LICENSE file.

Dependencies

This package has no dependencies, but is modeled on other, similar open source libraries. See the codebase for any specific attributions.

Documentation

Overview

Package errors provides utilities for working with Go errors. It is meant to work as a drop-in replacement for the standard library https://go.pkg.dev/errors, and is based on:

https://github.com/pkg/errors,

https://pkg.go.dev/golang.org/x/xerrors, and

https://github.com/uber-go/multierr.

Error context

When we write the following:

if err != nil {
	return err
}

... we allow errors to lose error context (ie human-readable root cause and debugging information).

Go 1.13 introduced "error wrapping," where we can add context messages like this:

if err != nil {
	return fmt.Errorf("contextual information: %w", err)
}

This helps us identify a root cause, and place that cause in some program context recursively.

However, we are not always in control of the full extent of our codebase, and even when we are we don't always write code that provides useful error context.

In addition, there are cases where we want more or different information appended to an error: caller frame locations and stacks, easily identifiable and immutable causes, or collections of errors that result from coalescing the outcome of multiple tasks for example.

This package allows users:

1. to add more fine-grained contextual information to their errors;

2. to chain or group errors and extract their contextual information;

3. to format errors with all of their context when printing them; and

4. to retain a simple API for most use cases while retaining the ability to directly interact with, or tune, error chains and groups.

Error wrapping in Go 1.13

This package is meant to be used with error wrapping.

Importantly: this package does not attempt to replace this system. Instead, errors is meant to enrich it: all the types and interfaces here work with Is, As, and Unwrap; using fmt.Errorf is also supported. In fact, this package re-exports New, Is, As, and Unwrap so that you don't need to import the standard library's "errors" package as well:

import "github.com/secureworks/errors"

var Err = errors.New("example err") // Same as if we had used: import "errors"

Wrapping multiple errors in Go 1.20

This package is meant to be used with the multiple error wrapping support introduced in Go 1.20.

Stack traces or call frames

For debugging context this package provides the errors.Frame interface and errors.Frames type. Frame is based on the runtime.Frame and xerrors.Frame types (https://pkg.go.dev/golang.org/x/xerrors#Frame) and defines one method, Location:

type Frame interface {
	Location() (function string, file string, line int)
}

You can create a Frame in your code directly with errors.Caller or errors.CallerAt. You can also use the runtime package to acquire a "program counter" (https://pkg.go.dev/runtime#Frame) using errors.FrameFromPC. Finally, you can generate a "synthetic" frame by passing the constituent data directly to errors.NewFrame:

fr := errors.Caller() // Same as errors.CallerAt(0)
fr.Location()         // ... returns "pkg/function.name", "file_name.go", 20

errors.Frames is a slice of error.Frame. You can bunch together a group of errors.Frame instances to create this list, or you can use errors.CallStack or errors.CallStackAt to get the entire call stack (or some subset of it).

stack := errors.CallStack() // Same as errors.CallStackAt(0)
stack[0].Location()         // ... returns "pkg/function.name", "file_name.go", 20

These two approaches to building errors.Frames can be described, from the point of view of adding error context, as "appending frames" or as "attaching a stack trace." Which you want to do depends on your use case: do you want targeted caller references or an entire stack trace attached to your error? The "stack trace" approach is the only one supported by https://github.com/pkg/errors, while the "append frames" approach is supported by https://pkg.go.dev/golang.org/x/xerrors, as examples.

Since the latter approach (appending frames) leads to more compact and efficient debugging information, and since it mirrors the Go idiom of recursively building an error context, this package prefers its use and includes the errors.Errorf function to that effect. Using stack traces is fully supported, however, and errors.FramesFrom will extract a stack trace, even if there are appended frames in an error chain (if both are available), in order to avoid context loss.

Wrapping Errors

This package provides functions for adding context to an error with a group of "error wrappers" that build an "error chain" of values that recursively add that context to some base error. The error wrappers it provides are:

// Attach a stack trace starting at the current caller.
err := errors.WithStackTrace(err)
// Append a frame for the current caller.
err = errors.WithFrame(err)
// Appends a frame for the caller *n* steps up the chain (in this case, 1).
err = errors.WithFrameAt(err, 1)

These wrappers are accompanied by versions that create a new error and immediately wrap it: errors.NewWithStackTrace, errors.NewWithFrame, and errors.NewWithFrameAt.

A final helper, errors.Errorf, is provided to allow for the common idiom:

err := errors.WithFrame(fmt.Errorf("message context: %w", err))
// ... the same as:
// err := errors.Errorf("message context: %w", err)

In order to ensure the user correctly structures errors.Errorf, the function will panic if you are not wrapping an error with the "%w" verb.

Multierrors

Wrapping errors is useful enough, but there are instances when we want to merge multiple errors and handle them as a group, eg: a "singular" result of running multiple tasks, handling a response to some graph resolution where each path may include a separate error, returning some possible error *and* the coalesced result of running a deferred function, etc.

This can be handled with a simple []error slice, but that can be frustrating since so many libraries, codebases and standards expect that well-formatted code adheres to the Go idiom of returning a single error value from a function or providing an error chain to errors.Unwrap.

To solve this the package provides a type errors.MultiError that wraps a slice of errors and implements the error interface (and others: errors.As, errors.Is, fmt.Formatter, et al).

It also provides helper functions for writing code that handles multierrors either as their own type or as a basic error type. For example, if you want to merge the results of two functions into a multierror, you can use:

func actionWrapper() *errors.MultiError {
	err1 := actionA()
	err2 := actionB()
	return errors.NewMultiError(err1, err2)
}

if merr := actionWrapper(); merr != nil {
	fmt.Printf("%+v\n", merr.Unwrap())
}

Retrieving error information

The additional types of context this package's wrappers add: call frames or stack (for debugging) can most easily be extracted from an error or error chain using errors.FramesFrom and errors.ErrorsFrom.

errors.FramesFrom returns an errors.Frames slice. It identifies if the error chain has a stack trace, and if it does it will return the oldest / deepest one available (to get the most context). If the error chain does not have a stack trace, but has frames appended, errors.FramesFrom merges those frames in order from most recent to oldest and returns it.

err := errors.NewWithStackTrace("err")
frames := errors.FramesFrom(err)
len(frames)
// 6

err := errors.NewWithFrame("err")
err = errors.WithFrame(err)
frames := errors.FramesFrom(err)
len(frames)
// 2

err := errors.New("err")
frames := errors.FramesFrom(err)
len(frames)
// 0

errors.ErrorsFrom returns a slice of errors, unwrapping the first multierror found in an error chain and returning the results. If none is found, the slice of errors contains the given error, or is nil if the error is nil:

merr := errors.NewMultiError(errors.New("err"), errors.New("err"))
err := errors.WithStackTrace(merr)
errs := errors.ErrorsFrom(err)
len(errs)
// 2

Masking Errors

Because this errors package allows us to add a fair amount of sensitive context to errors, and since Go errors are often used to provide end users with useful information, it is important to also provide primitives for removing (or "masking") context in an error chain.

Foremost is the wrapper function errors.WithMessage, which will reset a message context (often including information that is logged or that will be provided to an end user), while leaving the rest of the context and type information available on the error chain to be used by calling code. For example:

err := errors.NewWithFrame("user 4356789 missing role Admin: has roles [EndUser] in tenant 42")
// ...
err = errors.WithMessage(err, "user unauthorized")
fmt.Printf("%+v", err)
//> user unauthorized
//> pkg/function.name
//>     file_name.go:20

The resulting error can be unwrapped:

err := errors.Unwrap(err)
fmt.Print(err.Error())
//> user 4356789 missing role Admin: has roles [EndUser] in tenant 42

The opposite effect can be had by using errors.Mask to remove all non-message context:

err := errors.NewWithFrame("user unauthorized")
// ...
err = errors.Mask(err)
fmt.Printf("%+v", err)
//> user unauthorized
errors.FramesFrom(err)
// []

While errors.Mask removes all context, errors.Opaque retains all context but squashes the error chain so that type information, or any context that is not understood by this errors package is removed. This can be useful to ensure errors do not wrap some context from an outside library not under the calling code's control.

Formatted printing of errors

All error values returned from this package implement fmt.Formatter and can be formatted by the fmt package. The following verbs are supported:

%s  print the error's message context. Frames are not included in
    the message.
%v  see %s
%+v extended format. Each Frame of the error's Frames will
    be printed in detail.

This not an exhaustive list, see the tests for more.

Unexported interfaces

Following the precedent of other errors packages, this package is implemented using a series of unexported structs that all conform to various interfaces in order to be activated. All the error wrappers, for example, implement the error interface face type and the unwrap interface:

interface {
	Error() string // The built-in language type "error."
	Unwrap() error // The unexported standard library interface used to unwrap errors.
}

This package does export the important interface errors.Frame, but otherwise it does not export interfaces that are not necessary to use the library. However, if you want to write more complex code that makes use of, or augments, this package, there are unexported interfaces throughout that can be used to work more directly with its types. These include:

interface {
	// Used for extracting context.
	Frames() errors.Frames // Ie "framer," the interface for getting any frames from an error.

	// Used to distinguish a stack trace from other appended frames:
	StackTrace() []uintptr // Ie "stackTracer," the interface for getting a local stack trace from an error.

	// Used to distinguish a frame that was generated from runtime (instead of synthetically):
	PC() uintptr // Ie "programCounter," the interface for getting a frame's program counter.

	// Used to identify an error that coalesces multiple errors:
	Unwrap() []error // Ie "multierror," the interface for getting multiple merged errors.
}

Though none of these are exported by this package, they are considered a part of its stable public interface.

Example (DebugTasks)

By using errors.Errorf and errors.WithFrame we can add useful debugging information and error context to go routines, which can be hard to track down.

We can also coalesce errors into an errors.MultiError and handle it using single error idioms, which is useful for managing subtasks.

package main

import (
	"sync"
	"time"

	"github.com/secureworks/errors"
)

type wrapperType struct{}

func (_ *wrapperType) ReturnError() error {
	return errors.NewWithFrame("err from wrapper type")
}

func runSomeTask(n int) error {
	var wrapper *wrapperType
	time.Sleep(time.Duration(100*n) * time.Millisecond)
	if n%2 == 0 {
		return errors.Errorf(
			"while running some task (%d): %w", n, wrapper.ReturnError())
	}
	return nil
}

// By using errors.Errorf and errors.WithFrame we can add useful
// debugging information and error context to go routines, which can be
// hard to track down.
//
// We can also coalesce errors into an errors.MultiError and handle it
// using single error idioms, which is useful for managing subtasks.
func main() {
	var wg sync.WaitGroup

	errCh := make(chan error)
	for i := 0; i < 4; i++ {
		wg.Add(1)
		i := i
		go func() {
			defer wg.Done()
			err := errors.WithFrame(runSomeTask(i))
			if err != nil {
				errCh <- err
			}
		}()
	}

	go func() {
		wg.Wait()
		close(errCh)
	}()

	var merr error
	for err := range errCh {
		errors.AppendInto(&merr, err)
	}

	if merr != nil {
		pprintf("%+v", merr)
	}

}
Output:

multiple errors:

* error 1 of 2: while running some task (0): err from wrapper type
github.com/secureworks/errors_test.(*wrapperType).ReturnError
	/home/testuser/pkgs/errors/example_debug_tasks_test.go:0
github.com/secureworks/errors_test.runSomeTask
	/home/testuser/pkgs/errors/example_debug_tasks_test.go:0
github.com/secureworks/errors_test.Example_debugTasks.func1
	/home/testuser/pkgs/errors/example_debug_tasks_test.go:0

* error 2 of 2: while running some task (2): err from wrapper type
github.com/secureworks/errors_test.(*wrapperType).ReturnError
	/home/testuser/pkgs/errors/example_debug_tasks_test.go:0
github.com/secureworks/errors_test.runSomeTask
	/home/testuser/pkgs/errors/example_debug_tasks_test.go:0
github.com/secureworks/errors_test.Example_debugTasks.func1
	/home/testuser/pkgs/errors/example_debug_tasks_test.go:0
Example (StreamErrors)

Here we can see that it's straighforward to send errors over some pipe by serializing into bytes.

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"

	"github.com/secureworks/errors"
)

var splitTokensOn = []byte("\n\n")

// Crate a scanner that tokenizes on two newlines.
func tokenizer(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.Index(data, splitTokensOn); i >= 0 {
		// We have a full newline-terminated line.
		return i + 2, data[0:i], nil
	}
	if atEOF {
		return len(data), data, nil
	}
	return 0, nil, nil
}

// Here we can see that it's straighforward to send errors over some
// pipe by serializing into bytes.
func main() {
	r, w := io.Pipe()

	errFrames := errors.NewWithFrame("err w frames")
	errFrames = errors.Errorf("inner context: %w", errFrames)
	errStack := errors.NewWithStackTrace("err w stack")

	go func() {
		var errs = []error{
			errors.Errorf("outer context: %w", errFrames),
			errors.New("basic err"),
			errStack,
		}

		for _, err := range errs {
			fmt.Fprintf(w, "%+v%s", err, splitTokensOn)
		}

		w.Close()
	}()

	scanner := bufio.NewScanner(r)
	scanner.Split(tokenizer)
	for scanner.Scan() {
		err, _ := errors.ErrorFromBytes(scanner.Bytes())
		pprintf("\nREAD IN ERROR: %+v\n", err)
	}

}
Output:


READ IN ERROR: outer context: inner context: err w frames
github.com/secureworks/errors_test.Example_streamErrors
	/home/testuser/pkgs/errors/example_stream_errors_test.go:0
github.com/secureworks/errors_test.Example_streamErrors
	/home/testuser/pkgs/errors/example_stream_errors_test.go:0
github.com/secureworks/errors_test.Example_streamErrors.func1
	/home/testuser/pkgs/errors/example_stream_errors_test.go:0

READ IN ERROR: basic err

READ IN ERROR: err w stack
github.com/secureworks/errors_test.Example_streamErrors
	/home/testuser/pkgs/errors/example_stream_errors_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0
testing.runExamples
	/go/src/testing/example.go:0
testing.(*M).Run
	/go/src/testing/testing.go:0
main.main
	_testmain.go:0
runtime.main
	/go/src/runtime/proc.go:0

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Append

func Append(errs ...error) error

Append is a version of NewMultiError optimized for the common case of merging a small group of errors and expecting the outcome to be an error or nil, akin to the standard library's errors.Join (and it is, in fact, used for this library's implementation of Join).

The following pattern may also be used to record failure of deferred operations without losing information about the original error.

func doSomething(..) (err error) {
	f := acquireResource()
	defer func() {
		err = errors.Append(err, f.Close())
	}()
Example
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	err := errors.Append(
		nil,
		nil,
	)
	fmt.Printf("\n%v", err)

	err = errors.Append(err, nil)
	fmt.Printf("\n%v", err)

	err = errors.Append(
		err,
		errors.New("err1"),
	)
	fmt.Printf("\n%v", err)

	err = errors.Append(err, nil)
	fmt.Printf("\n%v", err)

	err = errors.Append(
		err,
		errors.New("err2"),
	)
	fmt.Printf("\n%v", err)

	err = errors.Append(err, nil)
	fmt.Printf("\n%v", err)

	err = errors.Append(
		err,
		errors.New("err3"),
	)
	fmt.Printf("\n%v", err)

}
Output:

<nil>
<nil>
err1
err1
[err1; err2]
[err1; err2]
[err1; err2; err3]

func AppendInto

func AppendInto(receivingErr *error, appendingErr error) bool

AppendInto appends an error into the destination of an error pointer and returns whether the error being appended was non-nil.

var err error
errors.AppendInto(&err, r.Close())
errors.AppendInto(&err, w.Close())

The above is equivalent to,

err := errors.Append(r.Close(), w.Close())

As AppendInto reports whether the provided error was non-nil, it may be used to build an errors error in a loop more ergonomically. For example:

var err error
for line := range lines {
	var item Item
	if errors.AppendInto(&err, parse(line, &item)) {
		continue
	}
	items = append(items, item)
}
if err != nil {
	log.Fatal(err)
}
Example
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	var aerr error

	errs := []error{
		nil,
		errors.New("err1"),
		errors.New("err2"),
		errors.New("err3"),
	}
	for _, err := range errs {
		errors.AppendInto(&aerr, err)
		fmt.Printf("\n%s", aerr)
	}

}
Output:

%!s(<nil>)
err1
[err1; err2]
[err1; err2; err3]

func AppendResult

func AppendResult(receivingErr *error, resulterFn ErrorResulter)

AppendResult appends the result of calling the given ErrorResulter into the provided error pointer. Use it with named returns to safely defer invocation of fallible operations until a function returns, and capture the resulting errors.

func doSomething(...) (err error) {
	// ...
	f, err := openFile(..)
	if err != nil {
		return err
	}

	// errors will call f.Close() when this function returns, and if the
	// operation fails it will append its error into the returned error.
	defer errors.AppendInvoke(&err, f.Close)

	scanner := bufio.NewScanner(f)
	// Similarly, this scheduled scanner.Err to be called and inspected
	// when the function returns and append its error into the returned
	// error.
	defer errors.AppendResult(&err, scanner.Err)

	// ...
}

Without defer, AppendResult behaves exactly like AppendInto.

err := // ...
errors.AppendResult(&err, errorableFn)

// ...is roughly equivalent to...

err := // ...
errors.AppendInto(&err, errorableFn())

The advantage of the indirection introduced by ErrorResulter is to make it easy to defer the invocation of a function. Without this indirection, the invoked function will be evaluated at the time of the defer block rather than when the function returns.

// BAD: This is likely not what the caller intended. This will evaluate
// foo() right away and append its result into the error when the
// function returns.
defer errors.AppendInto(&err, errorableFn())

// GOOD: This will defer invocation of foo until the function returns.
defer errors.AppendResult(&err, errorableFn)
Example
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

type testErrCloser struct{}

func (_ *testErrCloser) Close() error {
	return errors.New("and a closer error to boot!")
}

func main() {
	errFn := func() (err error) {
		closer := &testErrCloser{}
		defer errors.AppendResult(&err, closer.Close)

		err = errors.New("some error we got")
		if err != nil {
			return
		}
		return
	}

	noErrFn := func() (err error) {
		closer := &testCloser{}
		defer errors.AppendResult(&err, closer.Close)

		return
	}

	fmt.Println()

	err := errFn()
	if err != nil {
		fmt.Println(err)
	}

	err = noErrFn()
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("noErrFn returned nil")
	}

}

type testCloser struct{ err error }

func (t testCloser) Close() error {
	return t.err
}
Output:

[some error we got; and a closer error to boot!]
noErrFn returned nil

func As

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

As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true. Otherwise, it returns false.

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.

An error type might provide an As method so it can be treated as if it were a different error type.

As panics if target is not a non-nil pointer to either a type that implements error, or to any interface type.

func ErrorFromBytes

func ErrorFromBytes(byt []byte) (err error, ok bool)

ErrorFromBytes parses a stack trace or stack dump provided as bytes into an error. The format of the text is expected to match the output of printing with a formatter using the `%+v` verb. When an error is successfully parsed the second result is true; otherwise it is false. If you receive an error and the second result is false, well congrats you got an error.

Currently, this only supports single errors with or without a stack trace or appended frames.

TODO(PH): ensure ErrorFromBytes works with: multierror.

func Errorf

func Errorf(format string, values ...interface{}) error

Errorf is a shorthand for:

fmt.Errorf("some msg: %w", errors.WithFrame(err))

It is made available to support the best practice of adding a call stack frame to the error context alongside a message when building a chain. When possible, prefer using the full syntax instead of this shorthand for clarity.

Similar to fmt.Errorf, this function supports multiple `%w` verbs to generate a multierror: each wrapped error will have a frame attached to it.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	err = errors.Errorf("outer context: %w", err)
	pprintf("%+v", err)

}
Output:

outer context: err message
github.com/secureworks/errors_test.ExampleErrorf
	/home/testuser/pkgs/errors/examples_test.go:0
Example (AppendingDebuggingContext)
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	err = errors.Errorf("context: %w", err)
	err = errors.Errorf("outermost context: %w", err)
	pprintf("%+v", err)

}
Output:

outermost context: context: err message
github.com/secureworks/errors_test.ExampleErrorf_appendingDebuggingContext
	/home/testuser/pkgs/errors/examples_test.go:0
github.com/secureworks/errors_test.ExampleErrorf_appendingDebuggingContext
	/home/testuser/pkgs/errors/examples_test.go:0

func ErrorsFrom

func ErrorsFrom(err error) []error

ErrorsFrom returns a list of errors that the supplied error is composed of. If the error is a multierror then Unwrap is called on it. If the given error is nil, a nil slice is returned. If the error is not composed of other errors, the returned slice contains just the error that was passed in.

ErrorsFrom is useful when it is unknown if a given error is a multierror or not.

Callers of this function are free to modify the returned slice, but modifications to the errors themselves may race.

Example
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	err := errors.NewMultiError(
		errors.New("err1"),
		errors.New("err2"),
		errors.New("err3"),
	).ErrorOrNil()

	fmt.Println(err) // Print the multierror for comparison.

	errs := errors.ErrorsFrom(err)
	for _, err := range errs {
		fmt.Println(err)
	}

}
Output:

[err1; err2; err3]
err1
err2
err3
Example (Nil)
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	errs := errors.ErrorsFrom(nil)
	for _, err := range errs { // errs has 0 length.
		fmt.Println(err)
	}

}
Output:

Example (SingleError)
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	err := errors.New("err")
	err = errors.Errorf("inner context: %w", err)
	err = errors.Errorf("outer context: %w", err)

	errs := errors.ErrorsFrom(err)
	for _, err := range errs { // errs contains the given error.
		fmt.Println(err)
	}

}
Output:

outer context: inner context: err

func Is

func Is(err, target error) bool

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.

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(target error) bool { return target == fs.ErrExist }

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

func Join added in v0.2.0

func Join(errs ...error) error

Join returns an error that wraps the given errors. Any nil error values are discarded. Join returns nil if every value in errs is nil. The error formats as the concatenation of the strings obtained by calling the Error method of each element of errs.

A non-nil error returned by Join implements the Unwrap() []error method.

Since you are using the github.com/secureworks/errors package, be aware that this returns a MultiError instead of the default standard library's implementation.

func Mask

func Mask(err error) error

Mask returns an error with the same message context as err, but that does not match err and can't be unwrapped. As and Is will return false for all meaningful values.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("new err message")
	err = errors.Errorf("context: %w", err)
	err = errors.Errorf("outermost context: %w", err)
	err = errors.Mask(errors.WithMessage(err, "err"))

	// Should show frames.
	pprintf("%+v", err)

}
Output:

err

func New

func New(text string) error

New returns an error that formats as the given text. Each call to New returns a distinct error value even if the text is identical.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	pprintf("%+v", err)

}
Output:

err message

func NewWithFrame

func NewWithFrame(msg string) error

NewWithFrame returns a new error annotated with a call stack frame.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.NewWithFrame("err message")
	pprintf("%+v", err)

}
Output:

err message
github.com/secureworks/errors_test.ExampleNewWithFrame
	/home/testuser/pkgs/errors/examples_test.go:0

func NewWithFrameAt

func NewWithFrameAt(msg string, skipCallers int) error

NewWithFrameAt returns a new error annotated with a call stack frame. The second param allows you to tune how many callers to skip (in case this is called in a helper you want to ignore, for example).

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.NewWithFrameAt("err message", 1)
	pprintf("%+v", err)

}
Output:

err message
testing.runExample
	/go/src/testing/run_example.go:0

func NewWithFrames

func NewWithFrames(msg string, ff Frames) error

NewWithFrames returns a new error annotated with a list of frames.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	frames := errors.CallStackAtMost(0, 2)
	err := errors.NewWithFrames("err message", frames)
	pprintf("%+v", err)

}
Output:

err message
github.com/secureworks/errors_test.ExampleNewWithFrames
	/home/testuser/pkgs/errors/examples_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0

func NewWithStackTrace

func NewWithStackTrace(msg string) error

NewWithStackTrace returns a new error annotated with a stack trace.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.NewWithStackTrace("err message")
	pprintf("%+v", err)

}
Output:

err message
github.com/secureworks/errors_test.ExampleNewWithStackTrace
	/home/testuser/pkgs/errors/examples_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0
testing.runExamples
	/go/src/testing/example.go:0
testing.(*M).Run
	/go/src/testing/testing.go:0
main.main
	_testmain.go:0
runtime.main
	/go/src/runtime/proc.go:0

func Opaque

func Opaque(err error) error

Opaque returns an error with the same message context as err, but that does not match err. As and Is will return false for all meaningful values.

If err is a chain with Frames, then those are retained as wrappers around the opaque error, so that the error does not lose any information. Otherwise, err cannot be unwrapped.

You can think of Opaque as squashing the history of an error.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

type unknownErrorType struct {
	error       error
	SecretValue string
}

func (e *unknownErrorType) Error() string {
	return e.error.Error()
}

func (e *unknownErrorType) Unwrap() error {
	return e.error
}

func main() {
	err := errors.New("err message")
	err = errors.Errorf("context: %w", err)
	err = &unknownErrorType{error: err, SecretValue: "secret data we don't want to leak"}
	err = errors.Errorf("outermost context: %w", err)
	err = errors.Opaque(err)

	// Opaque squashes the error chain, removing any outside types that may
	// have snuck in, while retaining all the errors package data we know
	// about.
	var unkErr *unknownErrorType
	if errors.As(err, &unkErr) {
		fmt.Println("leaked data:", unkErr.SecretValue)
	}
	pprintf("%+v", err)

}
Output:

outermost context: context: err message
github.com/secureworks/errors_test.ExampleOpaque
	/home/testuser/pkgs/errors/examples_test.go:0
github.com/secureworks/errors_test.ExampleOpaque
	/home/testuser/pkgs/errors/examples_test.go:0

func PCFromFrame

func PCFromFrame(v interface{}) uintptr

PCFromFrame extracts the frame location program counter (pc) from either this package's Frame implementation (using an unexported interface), a raw uintptr (for identity), or runtime.Frame. Does not distinguish between an empty or nil frame, an unsupported frame implementation, or some other error: all return 0.

Example (RuntimeFrame)
package main

import (
	"fmt"
	"runtime"

	"github.com/secureworks/errors"
)

func main() {
	var pcs [1]uintptr
	runtime.Callers(0, pcs[:])
	frames := runtime.CallersFrames(pcs[:])
	frame, _ := frames.Next()

	pc := errors.PCFromFrame(frame)
	fmt.Printf(" %t", pc == pcs[0]-1)

}
Output:

true
Example (RuntimePC)
package main

import (
	"fmt"
	"runtime"

	"github.com/secureworks/errors"
)

func main() {
	framePC, _, _, _ := runtime.Caller(0)

	pc := errors.PCFromFrame(framePC)
	fmt.Printf("%t", pc == framePC)

}
Output:

true
Example (RuntimeProgramCounter)
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	type programCounter interface {
		PC() uintptr
	}
	fr := errors.Caller()
	pcer, _ := fr.(programCounter)

	pc := errors.PCFromFrame(fr)
	fmt.Printf(" %t", pc == pcer.PC())

}
Output:

true

func Unwrap

func Unwrap(err error) error

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 WithFrame

func WithFrame(err error) error

WithFrame adds a call stack frame to the error by wrapping it.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	err = errors.WithFrame(err)
	pprintf("%+v", err)

}
Output:

err message
github.com/secureworks/errors_test.ExampleWithFrame
	/home/testuser/pkgs/errors/examples_test.go:0

func WithFrameAt

func WithFrameAt(err error, skipCallers int) error

WithFrameAt adds a call stack frame to the error by wrapping it. The second param allows you to tune how many callers to skip (in case this is called in a helper you want to ignore, for example).

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	err = errors.WithFrameAt(err, 1)
	pprintf("%+v", err)

}
Output:

err message
testing.runExample
	/go/src/testing/run_example.go:0

func WithFrames

func WithFrames(err error, ff Frames) error

WithFrames adds a list of frames to the error by wrapping it.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	frames := errors.CallStackAtMost(0, 2)
	err = errors.WithFrames(err, frames)
	pprintf("%+v", err)

}
Output:

err message
github.com/secureworks/errors_test.ExampleWithFrames
	/home/testuser/pkgs/errors/examples_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0

func WithMessage

func WithMessage(err error, msg string) error

WithMessage overwrites the message for the error by wrapping it. The error chain is maintained so that As, Is, and FramesFrom all continue to work.

Example
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	err := errors.New("new err message")
	err = errors.Errorf("context: %w", err)
	err = errors.Errorf("outermost context: %w", err)
	err = errors.WithMessage(err, "new err message")

	fmt.Print(err)

}
Output:

new err message

func WithStackTrace

func WithStackTrace(err error) error

WithStackTrace adds a stack trace to the error by wrapping it.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	err = errors.WithStackTrace(err)
	pprintf("%+v", err)

}
Output:

err message
github.com/secureworks/errors_test.ExampleWithStackTrace
	/home/testuser/pkgs/errors/examples_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0
testing.runExamples
	/go/src/testing/example.go:0
testing.(*M).Run
	/go/src/testing/testing.go:0
main.main
	_testmain.go:0
runtime.main
	/go/src/runtime/proc.go:0

Types

type ErrorResulter

type ErrorResulter func() error

ErrorResulter is a function that may fail with an error. Use it with AppendResult to append the result of calling the function into an error. This allows you to conveniently defer capture of failing operations.

type Frame

type Frame interface {
	// Location returns the frame's caller's characteristics for help with
	// identifying and debugging the codebase.
	//
	// Location results are generated uniquely per Frame implementation.
	// When using this package's implementation, note that the results are
	// evaluated and expanded lazily when the frame was generated from the
	// local call stack: Location is not safe for concurrent access.
	Location() (function string, file string, line int)
}

Frame defines an interface for accessing and displaying stack frame information for debugging, optimizing or inspection. Usually you will find Frame in a Frames slice, acting as a stack trace or stack dump.

Frames are meant to be seen, so we have implemented the following default formatting verbs on it:

"%s"  – the base name of the file (or `unknown`) and the line number (if known)
"%q"  – the same as `%s` but wrapped in `"` delimiters
"%d"  – the line number
"%n"  – the basic function name, ie without a full package qualifier
"%v"  – the full path of the file (or `unknown`) and the line number (if known)
"%+v" – a standard line in a stack trace: a full function name on one line,
        and a full file name and line number on a second line
"%#v" – a Golang representation with the type (`errors.Frame`)

Marshaling a frame as text uses the `%+v` format. Marshaling as JSON returns an object with location data:

{"function":"test.pkg.in/example.init","file":"/src/example.go","line":10}

A Frame is immutable, so no setters are provided, but you can copy one trivially with:

function, file, line := oldFrame.Location()
newFrame := errors.NewFrame(function, file, line)
Example (Printf)

The underlying type generated here implements fmt.Formatter.

package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	fr := errors.Caller()

	fmt.Println()
	pprintf("%%s:  %s\n", fr)
	pprintf("%%q:  %q\n", fr)
	pprintf("%%n:  %n\n", fr)
	pprintf("%%d:  %d\n", fr)
	pprintf("%%v:  %v\n", fr)
	pprintf("%%#v: %#v\n", fr)
	pprintf("%%+v: %+v\n", fr)

}
Output:

%s:  examples_test.go:0
%q:  "examples_test.go:0"
%n:  ExampleFrame_printf
%d:  77
%v:  /home/testuser/pkgs/errors/examples_test.go:0
%#v: errors.Frame("/home/testuser/pkgs/errors/examples_test.go:0")
%+v: github.com/secureworks/errors_test.ExampleFrame_printf
	/home/testuser/pkgs/errors/examples_test.go:0
Example (ProgramCounter)

The underlying type generated here implements the unexported interface programCounter.

package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	type programCounter interface {
		PC() uintptr
	}

	localFr, ok := errors.Caller().(programCounter)
	if !ok {
		panic(errors.New("well this is a fine predicament"))
	}

	synthFr, ok := errors.NewFrame("fn.name", "file.go", 10).(programCounter)
	if !ok {
		panic(errors.New("well this is a fine predicament"))
	}

	// Who knows what the actual pointer value is: >0 means it was generated
	// from a local call stack, while 0 means it was created synthetically.
	fmt.Printf("%t %t", localFr.PC() > 0, synthFr.PC() > 0)

}
Output:

true false

func Caller

func Caller() Frame

Caller returns a Frame that describes the proximate frame on the caller's stack.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprint allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprint(v ...interface{}) {
	entries := strings.Split(fmt.Sprint(v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	fr := errors.Caller()
	pprint(fr)

}
Output:

/home/testuser/pkgs/errors/examples_test.go:0

func CallerAt

func CallerAt(skipCallers int) Frame

CallerAt returns a Frame that describes a frame on the caller's stack. The argument skipCaller is the number of frames to skip over.

func FrameFromPC

func FrameFromPC(pc uintptr) Frame

FrameFromPC creates a Frame from a program counter.

Example
package main

import (
	"fmt"
	"regexp"
	"runtime"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	pc, _, _, _ := runtime.Caller(0)
	fr := errors.FrameFromPC(pc)
	pprintf("%+v", fr)

}
Output:

github.com/secureworks/errors_test.ExampleFrameFromPC
	/home/testuser/pkgs/errors/examples_test.go:0

func NewFrame

func NewFrame(function string, file string, line int) Frame

NewFrame creates a "synthetic" Frame that describes the given location characteristics. This can be used to deserialize stack traces or stack dumps, or write clear tests that work with these.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	fr := errors.NewFrame("fn.name", "file.go", 10)
	pprintf("%+v", fr)

}
Output:

fn.name
	file.go:0

type Frames

type Frames []Frame

Frames is a slice of Frame data. This can represent a stack trace or some subset of a stack trace.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprint allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprint(v ...interface{}) {
	entries := strings.Split(fmt.Sprint(v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	stack := errors.CallStack()
	pprint(stack)

}
Output:

[/home/testuser/pkgs/errors/examples_test.go:0 /go/src/testing/run_example.go:0 /go/src/testing/example.go:0 /go/src/testing/testing.go:0 _testmain.go:0 /go/src/runtime/proc.go:0]
Example (JsonMarshal)

The underlying types generated by errors implement json.Marshaler. This may not hold for slices of other types that implement errors.Frame.

package main

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	stack := errors.CallStack()[0:1] // Remove stdlib frames.
	byt, err := json.MarshalIndent(stack, "", "    ")
	if err != nil {
		panic(errors.New("well this is a fine predicament"))
	}

	pprintf("\n%s", string(byt))

}
Output:

[
    {
        "function": "github.com/secureworks/errors_test.ExampleFrames_jsonMarshal",
        "file": "/home/testuser/pkgs/errors/examples_test.go",
        "line": 180
    }
]
Example (Printf)
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	stack := errors.CallStack()

	fmt.Println()
	pprintf("%+v", stack)

}
Output:

github.com/secureworks/errors_test.ExampleFrames_printf
	/home/testuser/pkgs/errors/examples_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0
testing.runExamples
	/go/src/testing/example.go:0
testing.(*M).Run
	/go/src/testing/testing.go:0
main.main
	_testmain.go:0
runtime.main
	/go/src/runtime/proc.go:0

func CallStack

func CallStack() Frames

CallStack returns all the Frames that describe the caller's stack.

func CallStackAt

func CallStackAt(skipCallers int) Frames

CallStackAt returns all the Frames that describe the caller's stack. The argument skipCaller is the number of frames to skip over.

func CallStackAtMost

func CallStackAtMost(skipCallers int, maxFrames int) Frames

CallStackAtMost returns a subset of Frames that describe the caller's stack. The argument skipCaller is the number of frames to skip over, and the argument maxFrames is the maximum number of frames to return (if the entire stack is less than maxFrames, the entireStack is returned). maxFrames of zero or fewer is ignored:

CallStackAtMost(0, 0) // ... returns the entire stack for the caller

func FramesFrom

func FramesFrom(err error) (ff Frames)

FramesFrom extracts all the Frames annotated across an error chain in order (if any). To do this it traverses the chain while aggregating frames.

If this method finds any frames on an error that were added as a stack trace (ie, the error was wrapped by WithStackTrace) then the stack trace deepest in the chain is returned alone, ignoring all other stack traces and frames. This lets us we retain the most information possible without returning a confusing frame set. Therefore, try not to mix the WithFrame and WithStackTrace patterns in a single error chain.

FramesFrom will not traverse a multierror, since there is no sensible way to structure the returned frames.

Example (AppendedFrames)
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.New("err message")
	err = errors.Errorf("context: %w", err)
	err = errors.Errorf("outermost context: %w", err)
	frames := errors.FramesFrom(err)
	pprintf("\n%+v", frames)

}
Output:

github.com/secureworks/errors_test.ExampleFramesFrom_appendedFrames
	/home/testuser/pkgs/errors/examples_test.go:0
github.com/secureworks/errors_test.ExampleFramesFrom_appendedFrames
	/home/testuser/pkgs/errors/examples_test.go:0
Example (StackTrace)
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := errors.NewWithStackTrace("err message")
	err = errors.Errorf("context: %w", err)
	err = errors.Errorf("outermost context: %w", err)
	frames := errors.FramesFrom(err)
	pprintf("\n%+v", frames)

}
Output:

github.com/secureworks/errors_test.ExampleFramesFrom_stackTrace
	/home/testuser/pkgs/errors/examples_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0
testing.runExamples
	/go/src/testing/example.go:0
testing.(*M).Run
	/go/src/testing/testing.go:0
main.main
	_testmain.go:0
runtime.main
	/go/src/runtime/proc.go:0

func FramesFromBytes

func FramesFromBytes(byt []byte) (Frames, error)

FramesFromBytes parses a stack trace or stack dump provided as bytes into a stack of Frames. The format of the text is expected to match the output of printing with a formatter using the `%+v` verb.

Example
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	stackDump := []byte(`err message
github.com/secureworks/errors_test.FnName
	/home/testuser/pkgs/errors/examples_test.go:0
github.com/secureworks/errors_test.FnWrapper
	/home/testuser/pkgs/errors/examples_test.go:0
runtime.main
	/go/src/runtime/proc.go:0
`)
	stack, _ := errors.FramesFromBytes(stackDump)
	fmt.Printf("\n%+v", stack)

}
Output:

github.com/secureworks/errors_test.FnName
	/home/testuser/pkgs/errors/examples_test.go:0
github.com/secureworks/errors_test.FnWrapper
	/home/testuser/pkgs/errors/examples_test.go:0
runtime.main
	/go/src/runtime/proc.go:0

func FramesFromJSON

func FramesFromJSON(byt []byte) (Frames, error)

FramesFromJSON parses a stack trace or stack dump provided as JSON-encoded bytes into a stack of Frames. json. Unmarshal does not work because it is meant to marshal into pre-allocated items, where Frames are defined only as interfaces.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	rawJSON := []byte(`[
    {
        "function": "github.com/secureworks/errors_test.FnName",
        "file": "/home/testuser/pkgs/errors/examples_test.go",
        "line": 200
    },
    {
        "function": "github.com/secureworks/errors_test.FnWrapper",
        "file": "/home/testuser/pkgs/errors/examples_test.go",
        "line": 190
    },
    {
        "function": "runtime.main",
        "file": "/go/src/runtime/proc.go",
        "line": 255
    }
]`)
	stack, _ := errors.FramesFromJSON(rawJSON)
	pprintf("\n%+v", stack)

}
Output:

github.com/secureworks/errors_test.FnName
	/home/testuser/pkgs/errors/examples_test.go:0
github.com/secureworks/errors_test.FnWrapper
	/home/testuser/pkgs/errors/examples_test.go:0
runtime.main
	/go/src/runtime/proc.go:0

func (Frames) Format

func (ff Frames) Format(s fmt.State, verb rune)

func (Frames) MarshalJSON

func (ff Frames) MarshalJSON() ([]byte, error)

type MultiError

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

MultiError is a list of errors. For compatibility, this type also implements the standard library error interface we refer to in this documentation as "multierror:"

type interface {
	Unwrap() []error
}

MultiError includes helpers for managing groups of errors using Go patterns. They are not synchronized for concurrent use, but can be used in concurrent code if the user manages synchronization.

The MultiError pattern is for top-level collection of error groups only, and are flattened when appended: no errors contained in its list are a multierror. (This is the major difference with the standard library implementation, which stores errors as a tree if they are progressively built). This flattening is not recursive, however: if a multierror is wrapped inside another error, it is not flattened, since this could cause us to lose information or context.

Unlike some error collection / multiple-error packages, we rely on an exported MultiError type to make it obvious how it should be handled in the codebase. While it can be treated as an error when necessary, we must be vigilant about nil-checking with MultiError:

if merr := errors.NewMultiError(nil); merr != nil {
	// This will always be true!
}

// Instead, check the length of the errors:
if merr := errors.NewMultiError(nil); len(merr.Unwrap()) > 0 {
	// This works ...
}

// Or use ErrorsOrNil to get a clean error interface:
if merr := errors.NewMultiError(nil); merr.ErrorOrNil() != nil {
	// This works ...
}

For simple error-joining, use Append or AppendInto, which only speak in the error interface.

Example (As)
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

type unknownErrorType struct {
	error       error
	SecretValue string
}

func (e *unknownErrorType) Error() string {
	return e.error.Error()
}

func (e *unknownErrorType) Unwrap() error {
	return e.error
}

func main() {
	err1 := errors.Errorf("context: %w",
		&unknownErrorType{error: errors.New("err"), SecretValue: "secret A"})

	fmt.Println()

	var unkErr *unknownErrorType
	if errors.As(err1, &unkErr) {
		fmt.Printf("basic unwrap found: %s\n", unkErr.SecretValue)
	} else {
		fmt.Println("basic unwrap not found")
	}

	// MultiError implements As by iterating over each error in order,
	// unwrapping the contained values.
	err2 := errors.Errorf("outer context: %w", errors.NewMultiError(
		errors.New("err"),
		err1,
		// Last in order, so not reached.
		&unknownErrorType{error: errors.New("err"), SecretValue: "secret B"},
	))
	if errors.As(err2, &unkErr) {
		fmt.Printf("multi unwrap found: %s\n", unkErr.SecretValue)
	} else {
		fmt.Println("multi unwrap not found")
	}

	// To get all, you must unwrap to MultiError and then unwrap contained values.
	var merr *errors.MultiError
	if errors.As(err2, &merr) {
		for i, err := range merr.Unwrap() {
			if errors.As(err, &unkErr) {
				fmt.Printf("unmerged %d unwrap found: %s\n", i, unkErr.SecretValue)
			} else {
				fmt.Printf("unmerged %d unwrap not found\n", i)
			}
		}
	}

}
Output:

basic unwrap found: secret A
multi unwrap found: secret A
unmerged 0 unwrap not found
unmerged 1 unwrap found: secret A
unmerged 2 unwrap found: secret B
Example (Is)
package main

import (
	"fmt"

	"github.com/secureworks/errors"
)

func main() {
	errSentinel := errors.New("sentinel err")
	errA := errors.Errorf("ctx A: %w", errSentinel)
	errB := errors.Errorf("ctx B: %w", errors.New("err"))
	errC := errors.Errorf("ctx C: %w", errSentinel)

	fmt.Println()

	// MultiError implements Is by iterating over each error in order,
	// unwrapping the contained values.
	err := errors.Errorf("outer context: %w", errors.NewMultiError(
		errA,
		errB,
		errC,
	))
	if errors.Is(err, errSentinel) {
		fmt.Printf("multi err sentinel found: %s\n", err)
	} else {
		fmt.Println("multi err sentinel not found")
	}

	// To check all, you must unwrap to MultiError and then check contained
	// values.
	var merr *errors.MultiError
	if errors.As(err, &merr) {
		for i, err := range merr.Unwrap() {
			if errors.Is(err, errSentinel) {
				fmt.Printf("unmerged %d sentinel found: %s\n", i, err)
			} else {
				fmt.Printf("unmerged %d sentinel not found\n", i)
			}
		}
	}

}
Output:

multi err sentinel found: outer context: [ctx A: sentinel err; ctx B: err; ctx C: sentinel err]
unmerged 0 sentinel found: ctx A: sentinel err
unmerged 1 sentinel not found
unmerged 2 sentinel found: ctx C: sentinel err
Example (Printf)
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprintf allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprintf(format string, v ...interface{}) {
	entries := strings.Split(fmt.Sprintf(format, v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	merrEmpty := errors.NewMultiError()
	merrFull := errors.NewMultiError(
		errors.New("err1"),
		errors.NewWithFrame("err2"),
		errors.NewWithStackTrace("err3"),
	)
	merrWrapped := errors.Errorf("context: %w", merrFull)

	fmt.Println()
	pprintf("1. %+v\n", merrEmpty)
	pprintf("2. %+v\n", merrFull)
	pprintf("3. %+v\n", merrWrapped)

}
Output:

1. empty errors: []
2. multiple errors:

* error 1 of 3: err1

* error 2 of 3: err2
github.com/secureworks/errors_test.ExampleMultiError_printf
	/home/testuser/pkgs/errors/examples_test.go:0

* error 3 of 3: err3
github.com/secureworks/errors_test.ExampleMultiError_printf
	/home/testuser/pkgs/errors/examples_test.go:0
testing.runExample
	/go/src/testing/run_example.go:0
testing.runExamples
	/go/src/testing/example.go:0
testing.(*M).Run
	/go/src/testing/testing.go:0
main.main
	_testmain.go:0
runtime.main
	/go/src/runtime/proc.go:0

3. context: [err1; err2; err3]
github.com/secureworks/errors_test.ExampleMultiError_printf
	/home/testuser/pkgs/errors/examples_test.go:0

func NewMultiError

func NewMultiError(errs ...error) (merr *MultiError)

NewMultiError returns a MultiError from a group of errors. Nil error values are not included, so the size of the MultiError may be less than the number of errors passed to the function.

If any of the given errors is a multierror, it is flattened into the new MultiError, allowing "append-like" behavior.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprint allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprint(v ...interface{}) {
	entries := strings.Split(fmt.Sprint(v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	merr := errors.NewMultiError(
		errors.New("err1"),
		errors.New("err2"),
		errors.New("err3"),
	)
	pprint(merr)

}
Output:

[err1; err2; err3]
Example (FlattensMultiErrors)
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprint allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprint(v ...interface{}) {
	entries := strings.Split(fmt.Sprint(v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	merrInner := errors.NewMultiError(
		errors.New("err1"),
		errors.New("err2"),
	)
	merr := errors.NewMultiError(
		merrInner,
		errors.New("err3"),
	)
	pprint(merr)

}
Output:

[err1; err2; err3]
Example (IsAnError)
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprint allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprint(v ...interface{}) {
	entries := strings.Split(fmt.Sprint(v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	err := (error)(errors.NewMultiError(
		errors.New("err1"),
		errors.New("err2"),
		errors.New("err3"),
	))
	pprint(err)

}
Output:

[err1; err2; err3]

func (*MultiError) Error

func (merr *MultiError) Error() string

func (*MultiError) ErrorOrNil

func (merr *MultiError) ErrorOrNil() error

ErrorOrNil is used to get a clean error interface for reflection, nil checking and other comparisons. If the MultiError is empty it returns nil, and if there is a single error then it is unnested. Otherwise, it returns a MultiError retyped for the error interface.

Retrieving the MultiError is simple, since NewMultiError flattens MultiErrors passed to it:

err := errors.NewMultiError(e1, e2, e3).ErrorOrNil()
newMErr := errors.NewMultiError(err)
newMErr.Errors() // => []error{e1, e2, e3}
Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprint allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprint(v ...interface{}) {
	entries := strings.Split(fmt.Sprint(v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	pprint("\n", errors.NewMultiError().ErrorOrNil())
	pprint("\n", errors.NewMultiError(nil).ErrorOrNil())
	pprint("\n", errors.NewMultiError(errors.New("err")).ErrorOrNil())
	pprint("\n", errors.NewMultiError(errors.New("err"), errors.New("err")).ErrorOrNil())

}
Output:

<nil>
<nil>
err
[err; err]

func (*MultiError) Errors deprecated

func (merr *MultiError) Errors() []error

Errors is the version v0.1 interface for multierrors. This pre-dated the release of Go 1.20, so Unwrap() []error was not a clear standard yet. It now is.

Deprecated: use Unwrap instead.

func (*MultiError) Format

func (merr *MultiError) Format(s fmt.State, verb rune)

func (*MultiError) Unwrap

func (merr *MultiError) Unwrap() []error

Unwrap returns the underlying value of the MultiError: a slice of errors. It returns a nil slice if the error is nil or has no errors.

This interface may be used to handle multierrors in code that may not want to expect a MultiError type directly:

if merr, ok := err.(interface{ Unwrap() [] error }); ok {
	// ...
}

Do not modify the returned errors and expect the MultiError to remain stable.

Example
package main

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/secureworks/errors"
)

var sharedPath = "/home/testuser/pkgs/errors/"
var matchInternalPath = regexp.MustCompile(`((/.+)+)/src/`)
var matchPackagePath = regexp.MustCompile(`((/.+)+)/errors/`)
var matchLineNumbers = regexp.MustCompile(`:[0-9]+`)

// pprint allows these tests to pass in any environment by grepping
// filepaths in the output, and to ease matching by removing line
// numbers from the call stacks.
func pprint(v ...interface{}) {
	entries := strings.Split(fmt.Sprint(v...), " ")
	for i := range entries {
		entries[i] = matchInternalPath.ReplaceAllString(entries[i], "/go/src/")
		entries[i] = matchPackagePath.ReplaceAllString(entries[i], sharedPath)
		entries[i] = matchLineNumbers.ReplaceAllString(entries[i], ":0")
	}
	fmt.Print(strings.Join(entries, " "))
}

func main() {
	merr := errors.NewMultiError(
		errors.New("err1"),
		errors.New("err2"),
		errors.New("err3"),
	)
	for _, err := range merr.Unwrap() {
		pprint("\n", err)
	}

}
Output:

err1
err2
err3

Directories

Path Synopsis
internal
constraints
Package constraints should only be used as a blank import.
Package constraints should only be used as a blank import.
Package syncerr provides synchronization utilities for working with errors generated by goroutines.
Package syncerr provides synchronization utilities for working with errors generated by goroutines.

Jump to

Keyboard shortcuts

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