Skip to content
This repository has been archived by the owner on Dec 1, 2021. It is now read-only.

Commit

Permalink
Support Go 1.13 error chains in Cause
Browse files Browse the repository at this point in the history
Imagine module A imports module B and both use `pkg/errors`. A uses
`errors.Cause` to inspect wrapped errors returned from B. As-is, B
cannot migrate from `errors.Wrap` to `fmt.Errorf("%w", err)` because
that would break `errors.Cause` calls in A. With this change merged,
`errors.Cause` becomes forwards-compatible with Go 1.13 error chains.
Module B will be free to switch to `fmt.Errorf("%w", err)` and that will
not break module A (so long as the top-level project pulls in the newer
version of `pkg/errors`).
  • Loading branch information
jayschwa committed Nov 12, 2019
1 parent 7f95ac1 commit c146378
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 27 deletions.
29 changes: 29 additions & 0 deletions cause.go
@@ -0,0 +1,29 @@
// +build !go1.13

package errors

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
type causer interface {
Cause() error
}

for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}
37 changes: 37 additions & 0 deletions cause_go113.go
@@ -0,0 +1,37 @@
// +build go1.13

package errors

import "errors"

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement the Cause interface, the Cause
// function will fallback to the standard library's errors.Unwrap
// that was added in Go 1.13. This makes Cause forwards-compatible
// with Go 1.13 error chains.
//
// If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
type causer interface {
Cause() error
}

for err != nil {
if cause, ok := err.(causer); ok {
err = cause.Cause()
} else if unwrapped := errors.Unwrap(err); unwrapped != nil {
err = unwrapped
} else {
break
}
}
return err
}
26 changes: 0 additions & 26 deletions errors.go
Expand Up @@ -260,29 +260,3 @@ func (w *withMessage) Format(s fmt.State, verb rune) {
io.WriteString(s, w.Error())
}
}

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
type causer interface {
Cause() error
}

for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}
12 changes: 11 additions & 1 deletion go113_test.go
Expand Up @@ -4,10 +4,20 @@ package errors

import (
stdlib_errors "errors"
"fmt"
"testing"
)

func TestErrorChainCompat(t *testing.T) {
func TestCauseErrorChainCompat(t *testing.T) {
err := stdlib_errors.New("leaf error")
wrapped := fmt.Errorf("wrapped error: %w", err)
wrapped = fmt.Errorf("wrapped again: %w", err)
if Cause(wrapped) != err {
t.Errorf("Cause does not support Go 1.13 error chains")
}
}

func TestWrapErrorChainCompat(t *testing.T) {
err := stdlib_errors.New("error that gets wrapped")
wrapped := Wrap(err, "wrapped up")
if !stdlib_errors.Is(wrapped, err) {
Expand Down

0 comments on commit c146378

Please sign in to comment.