Code Monkey home page Code Monkey logo

go-multierror's Introduction

go-multierror

CircleCI Go Reference GitHub go.mod Go version

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 https://pkg.go.dev/github.com/hashicorp/go-multierror

Requires go version 1.13 or newer

go-multierror requires go version 1.13 or newer. Go 1.13 introduced error wrapping, which this library takes advantage of.

If you need to use an earlier version of go, you can use the v1.0.0 tag, which doesn't rely on features in go 1.13.

If you see compile errors that look like the below, it's likely that you're on an older version of go:

/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

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()

go-multierror's People

Contributors

alvin-huang avatar bflad avatar calebalbers avatar claire-labry avatar corani avatar dependabot[bot] avatar dtolnay avatar dvrkps avatar felixge avatar haoxins avatar hashicorp-copywrite[bot] avatar hc-github-team-es-release-engineering avatar jbardin avatar jeanneryan avatar maxnordlund avatar mdeggies avatar mitchellh avatar mwhooker avatar nickethier avatar pcman312 avatar phinze avatar ryanuber avatar sagikazarmark avatar sethvargo avatar shore avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-multierror's Issues

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

Can panic-recover handling add to the function Group.Go()?

When the program is processed in multiple goroutines, panic may occur, which will cause main goroutine to crash directly. Can following logic add to Group.Go() ?

// Go calls the given function in a new goroutine.
//
// If the function returns an error it is added to the group multierror which
// is returned by Wait.
func (g *Group) Go(f func() error) {
	g.wg.Add(1)

	go func() {
		defer g.wg.Done()

+		defer func() {
+			if r := recover(); r != nil {
+				g.mutex.Lock()
+				g.err = Append(g.err, fmt.Errorf("%v", r))
+				g.mutex.Unlock()
+			}
+		}()

		if err := f(); err != nil {
			g.mutex.Lock()
			g.err = Append(g.err, err)
			g.mutex.Unlock()
		}
	}()
}

Feature request: ability to set default formatter

Hi, thanks for maintaining this library

I'd like to use this library to return errors to a caller that would prefer no newlines if possible (in the event of one error). That is doable and looks like this:

func Foo() error {
	var retErr *multierror.Error
	for _, x := range getSlice() {
		if err := bar(x); err != nil {
			retErr = multierror.Append(retErr, err)
		}
	}
	return decorateFormat(retErr.ErrorOrNil())
}

func decorateFormat(err *multierror.Error) *multierror.Error {
	if err != nil {
		err.ErrorFormat = multierror.ErrorFormatFunc(func(errs []error) string {
			if len(errs) == 1 {
				return errs[0].Error()
			}
			return multierror.ListFormatFunc(errs)
		})
	}
	return err
}

Which is fine, but requires me to remember to set the listFormatFunc, I'm doing that in a separate "decorator" func because I will need to do this many times.

I would like to make it possible to specify the "default" error func so I don't have to remember to set it. This way, whenever a new multierror.Error was created, it would automatically have the format function I want. Would you accept a change like that? If so, perhaps a default global (defaults to the current ListFormatFunc) is the way to go, but would defer to you. Thanks in advance!

GitHub Actions - deprecated warnings found - action required!

Workflow Name: hashicorp/go-multierror/go-multierror
Branch: main
Run URL: https://github.com/hashicorp/go-multierror/actions/runs/4621539068

save-state deprecation warnings: 0
set-output deprecation warnings: 1
node12 deprecation warnings: 1

Please review these deprecation warnings as soon as possible and merge in the necessary updates.

GitHub will be removing support for these commands and plan to fully disable them on 31st May 2023. At this time, any workflow that still utilizes these commands will fail. See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/.

GitHub have not finalized a date for deprecating node12 yet but have indicated that this will be summer 2023. So it is advised to switch to node16 asap. See https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/.

If you need any help, please reach out to us in #team-rel-eng.

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!

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

multierror undefined errors.AS errors.Is

go get github.com/hashicorp/go-multierror

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

go version
go1.10.4 linux/amd64

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.

errors.As and errors.Is are not supported in errors package

When I update this package
go get -u github.com/hashicorp/go-multierror
It was failed.
# github.com/hashicorp/go-multierror
../../hashicorp/go-multierror/multierror.go:112:9: undefined: errors.As
../../hashicorp/go-multierror/multierror.go:117:9: undefined: errors.Is

The As and Is are not in the errors package.
Go SDK: 1.10.2

Invitation to comment on proposed standard library multierror support

Hi!

I have an in-review proposal to add support for wrapping multiple errors to the standard library. The latest version of the proposal is:
golang/go#53435 (comment)

This proposal is intended to allow interoperation between multierror implementations, as well as to simplify these implementations. It isn't intended to supplant existing implementations, except in the simplest of cases. The quick summary of the proposal is that errors.Is and errors.As would support descending into multiple errors when a type implements an Unwrap() []error method returning a list of wrapped errors; for details see the above link.

I'm opening an issue in this repo to invite your comments on this proposal, either in the proposal issue or here. Would you find this feature useful? If adopted, would you add an Unwrap() error to your error types? Do you see any problems with this proposal, or are there changes that would make it more useful to you?

Thanks!

Returning nil values from Append

At the moment, Append never returns nil. The use case I've run across is the following, where a function returns an array of errors, and it's necessary to return an error if any of them are non-nil.

result, errs := multierrorFunc()
if err := multierror.Append(nil, errs...); err != nil {
	return err
}

I think all that would be needed is to add a check for the length of the error list and update this test.

if len(err.Errors) == 0 {
	return nil
}

If this seems like non-breaking functionality that would be useful, I could submit a pull request.

Thanks all!

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.)

Please change license

Hello, thanks for a useful library. Am I correct that it doesn't appear to provide unique competitive advantage to Hashicorp?

Could it (and hashicorp/errwrap) be changed to a permissive license? That would resolve the only persistent error in our binary scanners... Thanks for your consideration!

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.

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

`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:

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:

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.

API for an unknown number of errors

There are many cases where a call to some library returns []error.

Given this use case, utilising go-multierror is problematic as the client must check for an initial error, and if there are any remaining, then use Append.

This proposal removes this requirement from the client and adds an alternative (non-BC breaking) API change to allow a list of errors to be given.

Proposed signature:

AppendList(errs ...[]error)

All functionality would be identical, except that providing no errors would return an empty Error.

Convenience Join function for array of errors

When we have an array of errors we want to combine into a single one, we need to do the following:

func Errs() error {
    errs := []errors{}
    ...
    if len(errs) > 1 {
        return multierror.Append(keyErrs[0], keyErrs[1:]...)
    }
    return errs[0]
}

Or we can create a loop to build:

func Errs() (errs error) {
    builtErrs := []errors{}
    ...
    for _, err := range builtErrs {
        errs = multierror.Append(errs, err)
    }
    return errs
}

Instead of having to check length or use ranges, might be nice to have a Join function that takes an array of errors regardless of size:

func Errs() error {
    errs := []errors{}
    ...
    return multierror.Join(errs)
}

Notably I came across this issue in a situation where I need to know the number errors before returning, hence I need to use an error slice when initially building the errors rather than use Append directly.

Version of Append that does not flatten

I realize that Append() was fixed at some point to flatten, but I believe it's valuable to have a version of it that does not flatten MultiErrors. Use case: execute a bunch of tasks in parallel, wait for them to finish, collect the errors from the failed tasks and return them to the caller as a single MultiError. I would like to be able to preserve any MutliError generated by each task rather than ending up with a flat list of errors that lost their context, the demarcation points between tasks if you will.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.