diff --git a/errortypes.go b/errortypes.go index 0911bc0e..11047c9d 100644 --- a/errortypes.go +++ b/errortypes.go @@ -102,6 +102,24 @@ func makeWrappedConstError(err error, format string, args ...interface{}) error return fmt.Errorf(strings.Join([]string{format, "%w"}, separator), append(args, err)...) } +// WithType is responsible for annotating an already existing error so that it +// also satisfies that of a ConstError. The resultant error returned should +// satisfy Is(err, errType). If err is nil then a nil error will also be returned. +// +// Now with Go's Is, As and Unwrap support it no longer makes sense to Wrap() +// 2 errors as both of those errors could be chains of errors in their own right. +// WithType aims to solve some of the usefulness of Wrap with the ability to +// make a pre-existing error also satisfy a ConstError type. +func WithType(err error, errType ConstError) error { + if err == nil { + return nil + } + return &errWithType{ + error: err, + errType: errType, + } +} + // Timeoutf returns an error which satisfies Is(err, Timeout) and the Locationer // interface. func Timeoutf(format string, args ...interface{}) error { diff --git a/errortypes_test.go b/errortypes_test.go index bba03dc3..edd20159 100644 --- a/errortypes_test.go +++ b/errortypes_test.go @@ -177,3 +177,19 @@ func (*errorTypeSuite) TestThatYouAlwaysGetError(c *gc.C) { c.Assert(err.Error(), gc.Equals, "") } } + +func (*errorTypeSuite) TestWithTypeNil(c *gc.C) { + myErr := errors.ConstError("do you feel lucky?") + c.Assert(errors.WithType(nil, myErr), gc.IsNil) +} + +func (*errorTypeSuite) TestWithType(c *gc.C) { + myErr := errors.ConstError("do you feel lucky?") + myErr2 := errors.ConstError("i don't feel lucky") + err := errors.New("yes") + + err = errors.WithType(err, myErr) + c.Assert(errors.Is(err, myErr), gc.Equals, true) + c.Assert(err.Error(), gc.Equals, "yes") + c.Assert(errors.Is(err, myErr2), gc.Equals, false) +} diff --git a/functions.go b/functions.go index 9622ae20..9ea9eaf0 100644 --- a/functions.go +++ b/functions.go @@ -355,3 +355,15 @@ func Is(err, target error) bool { func As(err error, target interface{}) bool { return stderrors.As(err, target) } + +// SetLocation takes a given error and records where in the stack SetLocation +// was called from and returns the wrapped error with the location information +// set. The returned error implements the Locationer interface. If err is nil +// then a nil error is returned. +func SetLocation(err error, callDepth int) error { + if err == nil { + return nil + } + + return newLocationError(err, callDepth) +} diff --git a/functions_test.go b/functions_test.go index efc29a62..492620d6 100644 --- a/functions_test.go +++ b/functions_test.go @@ -373,3 +373,17 @@ func (*functionSuite) TestIs(c *gc.C) { c.Check(val, gc.Equals, true) } } + +func (*functionSuite) TestSetLocationWithNilError(c *gc.C) { + c.Assert(errors.SetLocation(nil, 1), gc.IsNil) +} + +func (*functionSuite) TestSetLocation(c *gc.C) { + err := errors.New("test") + err = errors.SetLocation(err, 1) + stack := fmt.Sprintf("%s: test", errorLocationValue(c)) + _, implements := err.(errors.Locationer) + c.Assert(implements, gc.Equals, true) + + c.Check(errors.ErrorStack(err), gc.Equals, stack) +}