A Go (golang) package for representing a list of errors as a single error.

Overview

go-multierror

Build Status Go Documentation

go-multierror is a package for Go that provides a mechanism for representing a list of error values as a single error.

This allows a function in Go to return an error that might actually be a list of errors. If the caller knows this, they can unwrap the list and access the errors. If the caller doesn't know, the error formats to a nice human-readable format.

go-multierror is fully compatible with the Go standard library errors package, including the functions As, Is, and Unwrap. This provides a standardized approach for introspecting on error values.

Installation and Docs

Install using go get github.com/hashicorp/go-multierror.

Full documentation is available at http://godoc.org/github.com/hashicorp/go-multierror

Usage

go-multierror is easy to use and purposely built to be unobtrusive in existing Go applications/libraries that may not be aware of it.

Building a list of errors

The Append function is used to create a list of errors. This function behaves a lot like the Go built-in append function: it doesn't matter if the first argument is nil, a multierror.Error, or any other error, the function behaves as you would expect.

var result error

if err := step1(); err != nil {
	result = multierror.Append(result, err)
}
if err := step2(); err != nil {
	result = multierror.Append(result, err)
}

return result

Customizing the formatting of the errors

By specifying a custom ErrorFormat, you can customize the format of the Error() string function:

var result *multierror.Error

// ... accumulate errors here, maybe using Append

if result != nil {
	result.ErrorFormat = func([]error) string {
		return "errors!"
	}
}

Accessing the list of errors

multierror.Error implements error so if the caller doesn't know about multierror, it will work just fine. But if you're aware a multierror might be returned, you can use type switches to access the list of errors:

if err := something(); err != nil {
	if merr, ok := err.(*multierror.Error); ok {
		// Use merr.Errors
	}
}

You can also use the standard errors.Unwrap function. This will continue to unwrap into subsequent errors until none exist.

Extracting an error

The standard library errors.As function can be used directly with a multierror to extract a specific error:

// Assume err is a multierror value
err := somefunc()

// We want to know if "err" has a "RichErrorType" in it and extract it.
var errRich RichErrorType
if errors.As(err, &errRich) {
	// It has it, and now errRich is populated.
}

Checking for an exact error value

Some errors are returned as exact errors such as the ErrNotExist error in the os package. You can check if this error is present by using the standard errors.Is function.

// Assume err is a multierror value
err := somefunc()
if errors.Is(err, os.ErrNotExist) {
	// err contains os.ErrNotExist
}

Returning a multierror only if there are errors

If you build a multierror.Error, you can use the ErrorOrNil function to return an error implementation only if there are errors to return:

var result *multierror.Error

// ... accumulate errors here

// Return the `error` only if errors were added to the multierror, otherwise
// return nil since there are no errors.
return result.ErrorOrNil()
Issues
  • Fix travis setup to work with go modules

    Fix travis setup to work with go modules

    Just to get the build passing again!

    I followed: https://dave.cheney.net/2018/07/16/using-go-modules-with-travis-ci and: https://arslan.io/2018/08/26/using-go-modules-with-vendor-support-on-travis-ci/

    opened by joac 11
  • Update error output to be more readable

    Update error output to be more readable

    • Don't space out the error but append the next one
    • During a test, type the expected result instead of parsing it

    Current error output looks like:

    Error applying plan:
    
    3 error(s) occurred:
    
    * azurerm_network_interface.machine1: 1 error(s) occurred:
    
    * azurerm_network_interface.machine1: <some error string goes here>
    * azurerm_network_interface.machine1: <some error string goes here>
    * azurerm_network_interface.machine2: 1 error(s) occurred:
    
    * azurerm_network_interface.machine: <some error string goes here>
    

    This changes it to be:

    Error applying plan:
    
    3 error(s) occurred:
    * azurerm_network_interface.machine1: 2 error(s) occurred:
    	* azurerm_network_interface.machine1: <some error string goes here>
    	* azurerm_network_interface.machine1: <some error string goes here>
    
    * azurerm_network_interface.machine2: 1 error(s) occurred:
    	* azurerm_network_interface.machine2: <some error string goes here>
    
    

    Which imho makes things more readable.

    opened by FrenchBen 6
  • Go version missing from documentations.

    Go version missing from documentations.

    I'm using golang v1.12 but this package is not supported because of errors.Is and errors.As functions which are not present in v1.12 and was added in v1.13. And there is no version requirement mentioned in the documentation. for this package

    opened by MonishAkram 4
  • Appending nil values results in a non-nil error

    Appending nil values results in a non-nil error

    Loving this library. One thing surprised me though:

    var err error
    var err2 error
    err = multierror.Append(err, err2)
    // err.Error() "0 errors occurred:"
    

    Practically speaking, instead of doing this:

    var err error
    for _, chrome := range m.chromes {
    	err = multierror.Append(err, chrome.Stop())
    }
    return err
    

    You have to do this:

    var err error
    for _, chrome := range m.chromes {
    	e := chrome.Stop()
    	if e != nil {
    		err = multierror.Append(err, e)
    	}
    }
    return err
    

    Not terrible, just a bit unexpected. Would you accept a PR fixing this? Thanks!

    opened by matthewmueller 4
  • Return the stdlib error interface

    Return the stdlib error interface

    Thanks for an excellent library!

    I'm opening this pull request to propose a several very small changes which are intended to make the Append function "behave as you would expect" (as is suggested in the README).

    Changes:

    • Return the standard library error interface from Append (this is a change to the function signature)

      Note the following advice from the golang FAQ

      It's a good idea for functions that return errors always to use the error type in their signature (as we did above) rather than a concrete type such as *MyError, to help guarantee the error is created correctly. As an example, os.Open returns an error even though, if not nil, it's always of concrete type *os.PathError.

    • Return the standard library error interface from (g *Group) Wait() (this is a change to the function signature)

    • Return nil from Append when there is no error

    • Return the original error from Append when there is only one error (i.e. appending to nil)

    • Introduce a new Cast method to allow older code which requires a *multierror.Error to be easily updated

    • Change the Group.err field from *Error to error

    opened by astrolox 3
  • Build error triggered from the mxpv/podsync makefile

    Build error triggered from the mxpv/podsync makefile

    I am trying to build mxpv/podsync using its makefile, and I am getting this error:

    # github.com/hashicorp/go-multierror ../go/src/github.com/hashicorp/go-multierror/multierror.go:112:9: undefined: errors.As ../go/src/github.com/hashicorp/go-multierror/multierror.go:117:9: undefined: errors.Is

    I know basically nothing about go, but as far as I can tell this shouldn't be happening. Is this a bug in the podsync makefile, or is it a bug in multierror.go?

    I would appreciate the attention of someone who understands these things better than I. Thanks!

    (For further context, see my issue on the podsync repository here.)

    opened by elsiehupp 3
  • Support Go 1.13 errors.As/Is/Unwrap functionality

    Support Go 1.13 errors.As/Is/Unwrap functionality

    The primary mechanism that enables this functionality is making Unwrap on the top-level Error return a new "chain" structure that uses state to keep track of the current error.

    The chain implements errors.Is/As so that it compares to that current underlying error. And it implements Unwrap to move on to the next error.

    A well-formed program using errors.Is/As/Unwrap exclusively will behave correctly with go-multierror in this case without dropping any errors. Direct comparisons such as Unwrap() == myErr will not work because we wrap in a chain. The user has to do errors.Is(err, myErr) which is the right thing to do anyways.

    When Unwrap is called on a top-level *Error, we create a shallow copy of the errors so that you can continue using *Error (modifying it, potentially in-place). There is a slight cost to this but it felt weird that calling Unwrap would share the same underlying data and cause potential data races. I think this is unlikely, but its also very unlikely the performance cost of the shallow copy of errors will matter either.

    opened by mitchellh 3
  • fix subtle interface bug on Group.Wait()

    fix subtle interface bug on Group.Wait()

    Returning the concrete type *Error causes any assignments to an error type to result in a non-nil value, since the error interface would actually have (*Error, nil) rather than (nil, nil). This matches the sync/errgroup interface.

    cc: @nickethier

    opened by mightyguava 3
  • Marshalling multierror

    Marshalling multierror

    Hey,

    I am using your multierror package in my project however I need to wrap it and add a MarshalJSON as it doesn't Marshal out of the box (because of the Func field). Would it be ok to add it to your project? In my case it would just marshal the error array. I don't know if you would want to do something more elaborate

    opened by malpiatko 3
  • Check if multierror is nil in WrappedErrors

    Check if multierror is nil in WrappedErrors

    This adds a check similar to ErrorsOrNil that ensures the multierror pointer is not nil before returning the Errors field. I believe this is fully backwards-compatible, because the former behavior is a runtime panic.

    opened by sethvargo 2
  • A new release with Group

    A new release with Group

    Can you make a new release, including Group in order to make it available in go modules? It is already present in your documentation, so I think, that it is reasonable to include it in a new release.

    opened by Flygrounder 2
  • replace hashicorp/errwrap with go1.13 native error wrapping

    replace hashicorp/errwrap with go1.13 native error wrapping

    The errwrap.Wrapf() function was deprecated in favor of native go 1.13 error wrapping in github.com/hashicorp/errwrap v1.1.0 (https://github.com/hashicorp/errwrap/pull/9).

    This updates the Prefix() function to use native error wrapping, which removes the dependency on github.com/hashicorp/errwrap.

    opened by thaJeztah 2
  • Use `error` instead of `*Error` in `Group`

    Use `error` instead of `*Error` in `Group`

    Changes the type of Group.err to error instead of *Error, and also changes the return value of Group.Wait to error instead of *Error. The *Error concrete type can lead to unintuitive, subtle bugs around nil checks (see: https://golang.org/doc/faq#nil_error). Returning the error interface instead eliminates this possibility. Note that because this changes a public API signature, it should be considered a breaking change.

    Fixes #57

    opened by ccampo133 1
  • `error` nil check weirdness with `Group.Wait`

    `error` nil check weirdness with `Group.Wait`

    It's not uncommon to see code like this:

    package main
    
    import (
    	"github.com/hashicorp/go-multierror"
    )
    
    type server struct{}
    
    func (s *server) Run() error {
    	g := new(multierror.Group)
    	g.Go(
    		func() error {
    			// ...code to do some work here...
    			return nil
    		},
    	)
    	g.Go(
    		func() error {
    			// ...code to do some MORE work here...
    			return nil
    		},
    	)
    	return g.Wait()
    }
    
    func main() {
    	s := server{}
    	if err := s.Run(); err != nil {
    		panic("error while running")
    	}
    }
    

    However, there is a subtle bug here. The main function will always panic, despite no error being returned by any of the Goroutines managed by the group g. This is due to the fact that Go treats interfaces as "fat pointers". See:

    • https://golang.org/doc/faq#nil_error
    • https://tour.golang.org/methods/12
    • https://guihao-liang.github.io/2020/07/05/interface-type-value
    • https://glucn.medium.com/golang-an-interface-holding-a-nil-value-is-not-nil-bb151f472cc7
    • https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/#nil-interface-values

    A "fix" is to add the following nil check to the Run method above:

    func (s *server) Run() error {
    	g := new(multierror.Group)
    	g.Go(
    		func() error {
    			// ...code to do some work here...
    			return nil
    		},
    	)
    	g.Go(
    		func() error {
    			// ...code to do some MORE work here...
    			return nil
    		},
    	)
    	if err := g.Wait(); err != nil {
    		return err
    	}
    	return nil
    
    	//... or alternatively: return g.Wait().ErrorOrNil()
    }
    

    This is certainly not intuitive, and relies on type-inference to solve the problem. I've added a test to my fork of this repo to clearly demonstrate the issue:

    • https://github.com/ccampo133/go-multierror/pull/1/files

    My question - is there a good reason why Group.Wait returns *Error instead of just error, which is more idiomatic? Returning error would eliminate this weirdness seen here.

    opened by ccampo133 5
  • errors.Is() does not work if multierror.Prefix() is used

    errors.Is() does not work if multierror.Prefix() is used

    It does not work since go.mod specifies [email protected] and it does not have Unwrap(), but Unwrap() was added in [email protected]

    I could obviously fix it explicilty by updating versions but fixing it in go.mod wouldn't hurt either.

    opened by qqshka 0
  • Len() with nil multierror should return 0 instead of panic

    Len() with nil multierror should return 0 instead of panic

    As of now the method can be called only on a not nil Error.

    I stumbled upon the situation where I wanted handle how many errors a multierror variable contained, however Len can't be called on a nil multierror (which would cause a runtime panic).

    This implies that a nil multierror and zero length multierror are treated differently. I would expect the example in the playground to return 0 instead of throwing a panic. Is this an intentional design choice or maybe there is room for improvement?

    https://play.golang.org/p/n7fwsjBybhs

    The Len() method could be changed as the following without breaking changes (I think):

    func (err *Error) Len() int {
    	if err != nil {
    		return len(err.Errors)
    	}
    
    	return 0
    }
    

    Otherwise one would need to go through the ErrOrNil method with little, although not needed, overhead.

             var merr *multierror.Error
    	if merr.ErrOrNil != nil {
    		fmt.Println(merr.Len())
    	} else {
    		fmt.Println("0")
    	}
    

    I should also add that his condition happens when a multierror is initialized but nothing has been appended to it, in fact appending nil errors correctly fills it with zeroes. https://play.golang.org/p/B3P_aNnms2W

    opened by maxiride 0
  • Question: chain.Unwrap behaviour

    Question: chain.Unwrap behaviour

    chain.Unwrap() has a test to see whether the end of the chain has been reached. I don't understand this; is it correct?

    multierror.go:

    // Unwrap implements errors.Unwrap by returning the next error in the
    // chain or nil if there are no more errors.
    func (e chain) Unwrap() error {
    	if len(e) == 1 {  // line 106
    		return nil
    	}
    
    	return e[1:]
    }
    

    It seems to me that line 106 might have a mistake and should be if len(e) < 1 {.

    Have I misunderstood this?

    I am using v1.1.1.

    opened by rickb777 0
Owner
HashiCorp
Consistent workflows to provision, secure, connect, and run any infrastructure for any application.
HashiCorp
Drop-in replacement for the standard library errors package and github.com/pkg/errors

Emperror: Errors Drop-in replacement for the standard library errors package and github.com/pkg/errors. This is a single, lightweight library merging

Emperror 136 May 10, 2022
This structured Error package wraps errors with context and other info

RErr package This structured Error package wraps errors with context and other info. It can be used to enrich logging, for example with a structured l

Rohan Allison 0 Jan 21, 2022
Common juju errors and functions to annotate errors. Based on juju/errgo

errors import "github.com/juju/errors" The juju/errors provides an easy way to annotate errors without losing the original error context. The exporte

Juju 1.3k May 12, 2022
Linter for errors.Is and errors.As

erris erris is a program for checking that errors are compared or type asserted using go1.13 errors.Is and errors.As functions. Install go get -u gith

Roman Budnikov 51 Mar 3, 2022
A drop-in replacement for Go errors, with some added sugar! Unwrap user-friendly messages, HTTP status code, easy wrapping with multiple error types.

Errors Errors package is a drop-in replacement of the built-in Go errors package with no external dependencies. It lets you create errors of 11 differ

Kamaleshwar 41 May 10, 2022
Errors - A lib for handling error gracefully in Go

?? Errors Errors 是一个用于优雅地处理 Go 中错误的库。 Read me in English ??‍ 功能特性 优雅地处理 error,嗯,

水不要鱼 1 Jan 17, 2022
Go error library with error portability over the network

cockroachdb/errors: Go errors with network portability This library aims to be used as a drop-in replacement to github.com/pkg/errors and Go's standar

CockroachDB 1.3k May 16, 2022
Wraps the normal error and provides an error that is easy to use with net/http.

Go HTTP Error Wraps the normal error and provides an error that is easy to use with net/http. Install go get -u github.com/cateiru/go-http-error Usage

Yuto Watanabe 0 Dec 20, 2021
A simple errors package that dynamically prepends the package name.

errors ?? Buy me a cookie What is this? A simple errors package that dynamically prepends the package name. How to install Open a terminal and run the

Miles Whittaker 0 Jan 16, 2022
Package semerr helps to work with errors in Golang.

semerr Package semerr helps to work with errors in Golang. Const error An error that can be defined as const. var errMutable error = errors.New("mutab

Maxim Krivchun 3 Mar 27, 2022
A Nostress Errors Package For Golang

A Nostress Errors Package For Golang

null 0 Nov 2, 2021
Go package for errors with chained stack traces

errstack: Go errors with chained stack traces errstack is a Go package for creating errors with stack traces. It is heavily inspired by github.com/pkg

Greg Ward 0 Nov 27, 2021
A Go package providing errors with a stack trace Read-only

Errors with a stack trace A Go package providing errors with a stack trace. Features: Based of github.com/pkg/errors with similar API, addressing many

null 0 Jan 7, 2022
Golang errors with stack trace and source fragments.

Golang Errors with Stack Trace and Source Fragments Tired of uninformative error output? Probably this will be more convenient: Example package main

null 710 May 13, 2022
Hierarchical errors reporting done right in Golang

Hierarchical errors made right Hate seeing error: exit status 128 in the output of programs without actual explanation what is going wrong? Or, maybe,

reconquest 74 Nov 9, 2021
Golang errors with stacktrace and context

merry Add context to errors, including automatic stack capture, cause chains, HTTP status code, user messages, and arbitrary values. The package is la

Russ Egan 248 Apr 5, 2022
A powerful, custom error package for Go

custom-error-go A powerful, custom error package for Go Detailed explanation: https://medium.com/codealchemist/error-handling-in-go-made-more-powerful

Abhinav Bhardwaj 12 Apr 19, 2022
The Emperor takes care of all errors personally

The Emperor takes care of all errors personally. Go's philosophy encourages to gracefully handle errors whenever possible, but some times recovering f

Emperror 248 Apr 17, 2022
eris provides a better way to handle, trace, and log errors in Go 🎆

eris Package eris provides a better way to handle, trace, and log errors in Go. go get github.com/rotisserie/eris Why you'll want to switch to eris Us

null 1k May 15, 2022