Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new API (from errgroup and multierror) so eris can become ultimate error package. #101

Open
krhubert opened this issue Oct 14, 2021 · 4 comments

Comments

@krhubert
Copy link

Is your feature request related to a problem? Please describe.

Implement new API to have only one error package to manage all common errors related cases/problems.
With this change, I don't have to import other errors packages and eris would become the ultimate solution for almost
all error handling cases.

Describe the solution you'd like

Add/Import more functions and functionalities to this pkg. I've prepared a go docs:

  // Wraps adds additional context to all error types while maintaining the type of the original error.
  //
  // This is a convenience method for wrapping errors with stack trace and is otherwise the same as Wrap(err, "").
  func Wraps(err error) error

  // Append is a helper function that will append more errors onto an Error in order to create a larger multi-error.
  func Append(err error, errs ...error) error

  // WrappedErrors returns the list of errors that this Error is wrapping.
  func WrappedErrors(err error) []error

  // Group is a collection of goroutines that returns errors that need to be coalesced.
  type Group struct {
    // if true group will continue execution until the last goroutine is finished.
    // It will return multierror in case of more than one error, which can be 
    // turn into the slice with WrappedErrors.
    ContinueOnError bool
  }

  // WithContext returns a new Group and an associated Context derived from ctx.
  func WithContext(ctx context.Context) (*Group, context.Context)

  // Go calls the given function in a new goroutine. It can work in two modes
  //
  // 1. The first call to return a non-nil error cancels the group
  //
  // 2. If the function returns an error it is added to the
  // group multierror which is returned by Wait.
  //
  // You can control this behavour by setting ContinueOnError in Group.
  func (g *Group) Go(f func() error)

  // Wait blocks until all function calls from the Go method have returned, then returns either
  // the first non-nill error (if any) or multierror. This depends on ContinueOnError setting.
  func (g *Group) Wait() error

Describe alternatives you've considered

Still using other packages like github.com/hashicorp/go-multierror or golang.org/x/sync/errgroup

Additional context

I know maybe this Issue should be split into smaller ones but I thought it would be easier to gather all this into one Issue.

One real example that shows how multerror and Wraps can be used.

// This is a version with comments. Below you can find two versions to compare readability.
func (p *Postgres) ExecuteInTx(ctx context.Context, fn func(tx *Tx) error) error {
    tx, err := p.BeginTx(ctx)
    if err != nil {
        // just save a stacktrace without any message
        // it would be nice to have a function like pkgerros WithStack()
        // maybe with shorter name (eris.Stack() or eris.Wraps()),
        // to tell that I want to capture only a stack without any message.
        // right now I use Wrap for this.
        return eris.Wrap(err, "")
    }

    if err := fn(tx); err != nil {
        if rerr := tx.Rollback(ctx); rerr != nil {
            // "github.com/hashicorp/go-multierror"
            // It would be great to have api to combine
            // multiple errors into one. Ofc I can use
            // eris.Wrap here but it seems a bit overkill
            // to have two calls in stacktrace when in fact
            // I just need two error messages combined into one.
            // This can be used also in many other places
            err = multierror.Append(err, rerr)
        }

        return eris.Wrap(err, "") // eris.Wraps()
    }

    return eris.Wrap(tx.Commit(ctx), "") // eris.Wraps())
}

// This is version with external package and Wrap
func (p *Postgres) ExecuteInTx(ctx context.Context, fn func(tx *Tx) error) error {
    tx, err := p.BeginTx(ctx)
    if err != nil {
        return eris.Wrap(err, "")
    }

    if err := fn(tx); err != nil {
        if rerr := tx.Rollback(ctx); rerr != nil {
            err = multierror.Append(err, rerr)
        }

        return eris.Wrap(err, "")
    }

    return eris.Wrap(tx.Commit(ctx), "")
}

// This is version with eris only.
func (p *Postgres) ExecuteInTx(ctx context.Context, fn func(tx *Tx) error) error {
    tx, err := p.BeginTx(ctx)
    if err != nil {
        return eris.Wraps(err)
    }

    if err := fn(tx); err != nil {
        if rerr := tx.Rollback(ctx); rerr != nil {
            err = eris.Append(err, rerr)
        }

        return eris.Wraps(err)
    }

    return eris.Wraps(tx.Commit(ctx))
}
@morningvera
Copy link
Member

hey @krhubert, thanks for submitting this! it's going to take a while to consider all of this but at first glance, i'm hesitant about some of these suggestions. will write up a more detailed response and break this into separate issues once i get a chance. thanks again!

@sum2000
Copy link
Member

sum2000 commented Oct 18, 2021 via email

@TN1ck
Copy link

TN1ck commented Oct 14, 2022

+1 For the append functionality, we ran into this exact use case, where we'd like to wrap an error with another error of ours so we can deal with it ourselves up the stack in a more generic fashion.

i.e. we want to map our errors to an http response at some point, so it would be very useful if one could do things like this:

object, err := library.GetObject(someID)
if errors.Is(err, library.SpecificNotFoundErr) {
  return eris.Append(err, GenericNotFoundErr)
}

Which would make it very easy to do things like this.

@r2k1
Copy link

r2k1 commented Feb 1, 2023

Go 1.20 added new method Join to the errors package: https://pkg.go.dev/errors#Join

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants