multierr

package module
v1.1.1 Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2024 License: MIT Imports: 3 Imported by: 0

README

multierr

Go Report Card GoDoc

multierr is a package that allows combining multiple errors into a single error type. This allows functions to return multiple errors at once.

Callers can either use the returned multi-error as a conventional error (which is printed as a nice human-readable string), or continue working with it by appending or unwrapping individual errors.

Use cases

Validation

When validating configurations or user-input, it's always a great user-experience to see all problems at once. Just append all individual errors to a multi-error and return it to the user.

APIs

When implementing APIs (be it a WebServer's REST-API, a protobuf RPC API or any other interface), validating incoming data and returning all problems at once greatly improves a developer's quality of life.

No longer do API-users need to call an endpoint just to receive the next error they need to fix.

Collecting go-routine errors

Sometimes, multiple concurrently-running go-routines can each return an error. Which error should you report? The first one? What about the others, log them or ignore them? A multi-error can simply collect all those errors and return them at once.

Usage

Simple validation
type Input struct {
	Name string
	Age  int
}

func (i *Input) Validate() error {
	var valErr error

	if i.Name == "" {
		valErr = multierr.Append(valErr, errors.New("missing name"))
	}
	if i.Age < 18 {
		valErr = multierr.Append(valErr, errors.New("too young"))
	}
	return valErr
}

This prints the following output:

2 errors occurred:
  - missing name
  - too young
Custom title

If you instead return multierr.Titled(valErr, "Invalid input:"), you can get the following output:

Invalid input:
  - missing name
  - too young
Combining multiple multi-errors

When validating nested structures, you often receive errors from sub-validators. The same can happen when calling functions.

These cases can be handled in 4 different ways, all of them producing great error messages:

Option 1: multiErr.Append(valErr, err)
type Input struct {
	Name    string
	Age     int
	Address Address
}

type Address struct {
	City   string
	Street string
}

func (i *Input) Validate() error {
	var valErr error

	if i.Name == "" {
		valErr = multierr.Append(valErr, errors.New("missing name"))
	}
	if i.Age < 18 {
		valErr = multierr.Append(valErr, errors.New("too young"))
	}
	valErr = multierr.Append(valErr, i.Address.Validate())

	return multierr.Titled(valErr, "invalid input:")
}

func (a *Address) Validate() error {
	var valErr error

	if a.City == "" {
		valErr = multierr.Append(valErr, errors.New("missing city"))
	}
	if a.Street == "" {
		valErr = multierr.Append(valErr, errors.New("missing street"))
	}
	return valErr
}

This is the simplest version.
And you just got rid of those nasty if-error-checks.
You don't need to check for nil-errors when validating the address. If there is no error, Append() will simply do nothing.

You get the following error message:

invalid input:
  - missing name
  - too young
  - 2 errors occurred:
      - missing city
      - missing street
Option 2: multiErr.Append(valErr, multierr.Titled(...))

You can get a slightly better error message by choosing your own title.
Replace the address validation with this piece of code:

err := i.Address.Validate()
valErr = multierr.Append(valErr, multierr.Titled(err, "invalid address:"))

Again - you don't need to check for errors. The Titled-function simply returns nil if there was no error.
You get the following output:

invalid input:
  - missing name
  - too young
  - invalid address:
      - missing city
      - missing street
Option 3: multierr.Merge(valErr, err)

Now, what if you don't want nested error messages? Just merge them!
Replace the address validation with this:

valErr = multierr.Merge(valErr, i.Address.Validate())

You will get the following:

invalid input:
  - missing name
  - too young
  - missing city
  - missing street
Option 4: multiErr.Append(err, fmt.Errorf(...))

And, of course, calling fmt.Errorf() instead of multierr.Append() also yields great results.

Perform the address validation as follows:

if err := i.Address.Validate(); err != nil {
	valErr = multierr.Append(valErr, fmt.Errorf("invalid address: %s", err))
}

You will get:

invalid input:
  - missing name
  - too young
  - invalid address: 2 errors occurred:
      - missing city
      - missing street
Custom error format

Sometimes, you just want to format errors differently. And that's entirely possible:

err := multierr.Append(
	errors.New("error 1"),
	errors.New("error 2"),
)
err.Formatter = func(errs []error) string {
	return fmt.Sprintf("there are %d errors", len(errs))
}

This is not feasible if you want to have a different error format globally though.
In that case, you can overwrite the default formatter:

multierr.DefaultFormatter = func(errs []error) string {
	return fmt.Sprintf("there are %d errors", len(errs))
}

Accessing the list of errors

You can access a list with all sub-errors by simply calling

errList := multierr.Inspect(multiErr)

This also works if the provided argument is not actually a multi-error.
If it's a normal error, the returned list will have the error as a single element.

Unwrapping specific sub-errors

Multi-errors support the standard library's errors.Unwrap(), errors.As() and errors.Is() methods.
It's therefore possible to inspect certain root-causes of an error.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultFormatter = ListFormatterFunc

DefaultFormatter specifies the error formatter that is used for errors that don't have a dedicated formatter function specified.

Functions

func Append

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

Append combines all errors into a single multi-error. Any nil-error will be ignored. Returns nil if there are no errors. A returned error will always be of type *Error.

If err is a multierr.Error, it will be reused (the title and error-slice are kept). Otherwise a new multierr.Error is created.

func Inspect

func Inspect(err error) []error

Inspect returns all embedded sub-errors or nil if there are no errors. If err is not a multi-error, an error-slice with one element is returned.

func ListFormatterFunc

func ListFormatterFunc(errs []error) string

ListFormatterFunc puts each sub-error in a new, indented line. The errors are titled with a generic "n errors occurred".

func Merge

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

Merge combines all errors into a single multi-error. Any nil-error will be ignored. Returns nil if there are no errors. A returned error will always be of type *Error.

If any errs is a multierr.Error, it will be flattened.

If err is a multierr.Error, it will be reused (the title and error-slice are kept). Otherwise, a new multierr.Error is created.

func Titled

func Titled(err error, title string) error

Titled sets the error formatter to a TitledListFormatter. The given title is used when calling Error.Error().

If the error is not a multierr.Error, it will be converted. Returns nil if the error is nil. Otherwise, the result is always an *Error. This is equivalent of setting Error.Formatter directly.

func Titledf

func Titledf(err error, format string, args ...interface{}) error

Titledf sets the error formatter to a TitledListFormatter. See Titled for more information.

Types

type Error

type Error struct {
	Formatter FormatterFunc
	Errors    []error
}

Error is an error type to track multiple errors. This is used to accumulate errors in cases and return them as a single "error".

func (*Error) Error

func (e *Error) Error() string

Error converts the error into a human readable string. Uses the error-specific formatter or, if none is specified, the DefaultFormatter.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the first error in Error or nil if there are no errors. By repeatedly calling Unwrap on the return value (until nil is returned), all (recursively) contained sub-errors can be obtained. Errors are unwrapped depth-first. This implements errors.Is/errors.As/errors.Unwrap methods from the standard library. Appending new errors while unwrapping has no effect (shallow copy).

type FormatterFunc

type FormatterFunc func([]error) string

FormatterFunc is called by Error.Error() to convert multi-errors into a human readable strings.

func TitledListFormatter

func TitledListFormatter(title string) FormatterFunc

TitledListFormatter returns a formatter func that puts each sub-error in a new, indented line. The errors are titled with the given text.

Jump to

Keyboard shortcuts

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