From 95ff7d6aaad6cdfdd747f07a76a49cf9ce88bc64 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Tue, 15 Mar 2022 17:06:11 +1300 Subject: [PATCH 1/6] Add `Unwrap` method & function --- error.go | 6 ++++++ functions.go | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/error.go b/error.go index 2796b9f2..38bde41a 100644 --- a/error.go +++ b/error.go @@ -90,6 +90,12 @@ func (e *Err) Underlying() error { return e.previous } +// Unwrap is a synonym for Underlying, provided only to match the functionality +// of Go's standard `errors` library. +func (e *Err) Unwrap() error { + return e.previous +} + // Cause returns the most recent error in the error stack that // meets one of these criteria: the original error that was raised; the new // error that was passed into the Wrap function; the most recently masked diff --git a/functions.go b/functions.go index 1bf7c9e2..de3dd46b 100644 --- a/functions.go +++ b/functions.go @@ -328,3 +328,23 @@ func errorStack(err error) []string { } return result } + +// Unwrap returns the previous error in the stack. It calls the Unwrap method +// on its argument if possible, and otherwise returns nil. +// +// This method is provided to match the functionality of Go's `errors` library +// (pkg.go.dev/errors). In most cases, the error's *cause* will be more +// relevant, so you should use the `Cause` method instead. +func Unwrap(err error) error { + u, hasUnwrap := err.(unwrappable) + + if hasUnwrap { + return u.Unwrap() + } else { + return nil + } +} + +type unwrappable interface { + Unwrap() error +} From dd6e3c77648be491737f902c6674e3a7a7a603a1 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Wed, 16 Mar 2022 10:43:35 +1300 Subject: [PATCH 2/6] Change functions.go/Unwrap to alias standard lib --- error.go | 4 ++-- functions.go | 23 ++++------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/error.go b/error.go index 38bde41a..d210f42c 100644 --- a/error.go +++ b/error.go @@ -90,8 +90,8 @@ func (e *Err) Underlying() error { return e.previous } -// Unwrap is a synonym for Underlying, provided only to match the functionality -// of Go's standard `errors` library. +// Unwrap is a synonym for Underlying, which allows Err to be used with the +// Unwrap, Is and As functions in Go's standard `errors` library. func (e *Err) Unwrap() error { return e.previous } diff --git a/functions.go b/functions.go index de3dd46b..72145632 100644 --- a/functions.go +++ b/functions.go @@ -4,6 +4,7 @@ package errors import ( + goerrors "errors" "fmt" "strings" ) @@ -329,22 +330,6 @@ func errorStack(err error) []string { return result } -// Unwrap returns the previous error in the stack. It calls the Unwrap method -// on its argument if possible, and otherwise returns nil. -// -// This method is provided to match the functionality of Go's `errors` library -// (pkg.go.dev/errors). In most cases, the error's *cause* will be more -// relevant, so you should use the `Cause` method instead. -func Unwrap(err error) error { - u, hasUnwrap := err.(unwrappable) - - if hasUnwrap { - return u.Unwrap() - } else { - return nil - } -} - -type unwrappable interface { - Unwrap() error -} +// Unwrap is an alias for the Unwrap function in Go's standard `errors` library +// (pkg.go.dev/errors). +var Unwrap = goerrors.Unwrap From 4411fa5fa2a156ab54f7a713bdef1fd645ab03d7 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Wed, 16 Mar 2022 11:35:11 +1300 Subject: [PATCH 3/6] Add Unwrap, Is, As proxies for std lib functions --- error.go | 12 ++++++------ functions.go | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/error.go b/error.go index d210f42c..495158d8 100644 --- a/error.go +++ b/error.go @@ -90,12 +90,6 @@ func (e *Err) Underlying() error { return e.previous } -// Unwrap is a synonym for Underlying, which allows Err to be used with the -// Unwrap, Is and As functions in Go's standard `errors` library. -func (e *Err) Unwrap() error { - return e.previous -} - // Cause returns the most recent error in the error stack that // meets one of these criteria: the original error that was raised; the new // error that was passed into the Wrap function; the most recently masked @@ -180,3 +174,9 @@ func (e *Err) StackTrace() []string { func sameError(e1, e2 error) bool { return reflect.DeepEqual(e1, e2) } + +// Unwrap is a synonym for Underlying, which allows Err to be used with the +// Unwrap, Is and As functions in Go's standard `errors` library. +func (e *Err) Unwrap() error { + return e.previous +} diff --git a/functions.go b/functions.go index 72145632..ad1c28cc 100644 --- a/functions.go +++ b/functions.go @@ -4,7 +4,7 @@ package errors import ( - goerrors "errors" + stderrors "errors" "fmt" "strings" ) @@ -330,6 +330,16 @@ func errorStack(err error) []string { return result } -// Unwrap is an alias for the Unwrap function in Go's standard `errors` library -// (pkg.go.dev/errors). -var Unwrap = goerrors.Unwrap +// Unwrap, Is and As are proxies for the corresponding functions in Go's +// standard `errors` library (pkg.go.dev/errors). +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} + +func Is(err, target error) bool { + return stderrors.Is(err, target) +} + +func As(err error, target interface{}) bool { + return stderrors.As(err, target) +} From d67ae0e5820c9c14724209b92dcef711e5c57a29 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Wed, 16 Mar 2022 15:21:01 +1300 Subject: [PATCH 4/6] Add unit tests --- error_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/error_test.go b/error_test.go index ca69da68..7cfdb7ff 100644 --- a/error_test.go +++ b/error_test.go @@ -96,6 +96,38 @@ func (*errorsSuite) TestErrorString(c *gc.C) { return errors.Trace(err) }, expected: "more context: masked: some context: first error", + }, { + message: "error traced then unwrapped", + generator: func() error { + err := errors.New("inner error") + err = errors.Trace(err) + return errors.Unwrap(err) + }, + expected: "inner error", + }, { + message: "error annotated then unwrapped", + generator: func() error { + err := errors.New("inner error") + err = errors.Annotate(err, "annotation") + return errors.Unwrap(err) + }, + expected: "inner error", + }, { + message: "error wrapped then unwrapped", + generator: func() error { + err := errors.New("inner error") + err = errors.Wrap(err, errors.New("cause")) + return errors.Unwrap(err) + }, + expected: "inner error", + }, { + message: "error masked then unwrapped", + generator: func() error { + err := errors.New("inner error") + err = errors.Mask(err) + return errors.Unwrap(err) + }, + expected: "inner error", }, } { c.Logf("%v: %s", i, test.message) @@ -144,6 +176,11 @@ func (*errorsSuite) TestNewErrWithCause(c *gc.C) { c.Assert(errors.Details(err), Contains, tagToLocation["embedCause"].String()) } +func (*errorsSuite) TestUnwrapNewErrGivesNil(c *gc.C) { + err := errors.New("test error") + c.Assert(errors.Unwrap(err), gc.Equals, nil) +} + var _ error = (*embed)(nil) // This is an uncomparable error type, as it is a struct that supports the From b7b0f73d64bf5e6a5619d3e95b5df99444a5d4d7 Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Wed, 16 Mar 2022 15:52:03 +1300 Subject: [PATCH 5/6] fixes per comments --- error_test.go | 2 +- functions.go | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/error_test.go b/error_test.go index 7cfdb7ff..974329eb 100644 --- a/error_test.go +++ b/error_test.go @@ -178,7 +178,7 @@ func (*errorsSuite) TestNewErrWithCause(c *gc.C) { func (*errorsSuite) TestUnwrapNewErrGivesNil(c *gc.C) { err := errors.New("test error") - c.Assert(errors.Unwrap(err), gc.Equals, nil) + c.Assert(errors.Unwrap(err), gc.IsNil) } var _ error = (*embed)(nil) diff --git a/functions.go b/functions.go index ad1c28cc..438e696e 100644 --- a/functions.go +++ b/functions.go @@ -330,16 +330,20 @@ func errorStack(err error) []string { return result } -// Unwrap, Is and As are proxies for the corresponding functions in Go's -// standard `errors` library (pkg.go.dev/errors). +// Unwrap is a proxy for the Unwrap function in Go's standard `errors` library +// (pkg.go.dev/errors). func Unwrap(err error) error { return stderrors.Unwrap(err) } +// Is is a proxy for the Is function in Go's standard `errors` library +// (pkg.go.dev/errors). func Is(err, target error) bool { return stderrors.Is(err, target) } +// As is a proxy for the As function in Go's standard `errors` library +// (pkg.go.dev/errors). func As(err error, target interface{}) bool { return stderrors.As(err, target) } From a1ac65d7041cbd5b50d61eef4967558de192b5ea Mon Sep 17 00:00:00 2001 From: Jordan Barrett Date: Wed, 16 Mar 2022 16:32:52 +1300 Subject: [PATCH 6/6] Add tests for `Is` and `As` --- functions_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/functions_test.go b/functions_test.go index da27b31d..9cb19891 100644 --- a/functions_test.go +++ b/functions_test.go @@ -331,3 +331,43 @@ func (*functionSuite) TestFormat(c *gc.C) { c.Check(s, gc.Equals, expect) } } + +type basicError struct { + Reason string +} + +func (b *basicError) Error() string { + return b.Reason +} + +func (*functionSuite) TestAs(c *gc.C) { + baseError := &basicError{"I'm an error"} + testErrors := []error{ + errors.Trace(baseError), + errors.Annotate(baseError, "annotation"), + errors.Wrap(baseError, errors.New("wrapper")), + errors.Mask(baseError), + } + + for _, err := range testErrors { + bError := &basicError{} + val := errors.As(err, &bError) + c.Check(val, gc.Equals, true) + c.Check(bError.Reason, gc.Equals, "I'm an error") + } +} + +func (*functionSuite) TestIs(c *gc.C) { + baseError := &basicError{"I'm an error"} + testErrors := []error{ + errors.Trace(baseError), + errors.Annotate(baseError, "annotation"), + errors.Wrap(baseError, errors.New("wrapper")), + errors.Mask(baseError), + } + + for _, err := range testErrors { + val := errors.Is(err, baseError) + c.Check(val, gc.Equals, true) + } +}