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 (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
jayschwa authored and aperezg committed Jan 7, 2020
1 parent 004deef commit 49f8f61
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 27 deletions.
29 changes: 29 additions & 0 deletions cause.go
@@ -0,0 +1,29 @@
// +build !go1.13

package errors

// Cause recursively unwraps an error chain and 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
}
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
}
33 changes: 33 additions & 0 deletions go113.go
Expand Up @@ -36,3 +36,36 @@ func As(err error, target interface{}) bool { return stderrors.As(err, target) }
func Unwrap(err error) error {
return stderrors.Unwrap(err)
}

// Cause recursively unwraps an error chain and returns the underlying cause of
// the error, if possible. There are two ways that an error value may provide a
// cause. First, the error may implement the following interface:
//
// type causer interface {
// Cause() error
// }
//
// Second, the error may return a non-nil value when passed as an argument to
// the Unwrap function. This makes Cause forwards-compatible with Go 1.13 error
// chains.
//
// If an error value satisfies both methods of unwrapping, Cause will use the
// causer interface.
//
// 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 := Unwrap(err); unwrapped != nil {
err = unwrapped
} else {
break
}
}
return err
}
24 changes: 23 additions & 1 deletion go113_test.go
Expand Up @@ -9,7 +9,29 @@ import (
"testing"
)

func TestErrorChainCompat(t *testing.T) {
func TestCauseErrorChainCompat(t *testing.T) {
err := stderrors.New("the cause!")

// Wrap error using the standard library
wrapped := fmt.Errorf("wrapped with stdlib: %w", err)
if Cause(wrapped) != err {
t.Errorf("Cause does not support Go 1.13 error chains")
}

// Wrap in another layer using pkg/errors
wrapped = WithMessage(wrapped, "wrapped with pkg/errors")
if Cause(wrapped) != err {
t.Errorf("Cause does not support Go 1.13 error chains")
}

// Wrap in another layer using the standard library
wrapped = fmt.Errorf("wrapped with stdlib: %w", wrapped)
if Cause(wrapped) != err {
t.Errorf("Cause does not support Go 1.13 error chains")
}
}

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

0 comments on commit 49f8f61

Please sign in to comment.