errors icon indicating copy to clipboard operation
errors copied to clipboard

Proposal: errors.Pop()

Open bobbytables opened this issue 9 years ago • 5 comments

Hello, we are wrapping our errors in our grpc service, and we came across a situation where we wanted to return the top of the wrapped errors in the return of our endpoints.

For reference, a typical grpc endpoint signature looks like this:

func (s *Server) Hello(ctx context.Context, req *pb.HelloReq) (*pb.HelloResp, error) {
  // ...
}

The method signature has an error type as the second return value. We've written grpc middleware that receives the return values of this function, and does the proper logging, metrics, etc, before ultimately returning to the requesting client.

In the middleware, we log the entire error chain, but what we'd like to return to the grpc client in the error return param is the top error in the chain. So I'm proposing the ability to do this:

first := errors.New("first")
second := errors.Wrap(first, "second")
third := errors.Wrap(second, "third")

// prints the entire chain:
third.Error() // [ third: second: first ]

// proposed option:
errors.Pop(third) // returns an error type with ONLY "third" and no chain attached

Currently there's no easy (that I know of 😄 ) way to get the top most of an error chain. This would allow us to do so.

Thoughts?

bobbytables avatar Sep 09 '16 18:09 bobbytables

Thank you for this suggestion. I understand what you want to implement, but I do not understand why. Can you show me how a caller of Pop would use the result?

davecheney avatar Sep 09 '16 23:09 davecheney

Hi! I'm also agree with @bobbytables. In my case a Pop() function is useful to isolate my library errors form others library. Sometimes I wrap errors inside my function (which returns error), other times no.

var (
    ErrNetwork = errors.New("network erorr")
    ErrDB      = errors.New("database erorr")
)

func main() {

    err := funcWithError()

    switch errors.Pop(err) {
    case ErrNetwork:
        // do stuff
    case ErrDB:
        // do stuff
    default:
        // do other stuff
    }

...
}

tux-eithel avatar Oct 04 '16 12:10 tux-eithel

@tux-eithel thanks for your comment. I don't think your sample code works because the errors returned from this package cannot be compared for equality. This is by design.

davecheney avatar Oct 23 '16 07:10 davecheney

@davecheney My example it too much simplified. The Pop() may returns an errors and the switch could be done on after call Error() function.

var (
    ErrNetwork = errors.New("network erorr")
    ErrDB      = errors.New("database erorr")
)

func main() {

    err := funcWithError()

    pop := errors.Pop(err)

    switch pop.Error() {
    case ErrNetwork:
        // do stuff
    case ErrDB:
        // do stuff
    default:
        // some error from other libraries
    }

...
}

cris-hart avatar Oct 26 '16 10:10 cris-hart

You can write errors.Pop yourself

if err, ok := err.(interface{Cause() error}); ok {
      cause := err.Cause()
      ///
}

davecheney avatar Oct 29 '16 20:10 davecheney