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

Support Go 1.13 error chains in Cause #215

Merged
merged 1 commit into from Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also recommend also testing a WithMessage(fmt.Errorf("wrap2: %w", WithMessage(err, "wrap1"))), "wrap3")

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