diff --git a/errors/cause.go b/errors/cause.go deleted file mode 100644 index 92fe4f6a..00000000 --- a/errors/cause.go +++ /dev/null @@ -1,103 +0,0 @@ -package errors - -import ( - "errors" - "reflect" - - "gopkg.in/errgo.v1" -) - -// Is checks if any error of the stack matches the error value expectedError -// API machting the standard library but allowing to wrap errors with ErrCtx + errgo or pkg/errors -func Is(receivedErr, expectedError error) bool { - if errors.Is(receivedErr, expectedError) { - return true - } - for receivedErr != nil { - receivedErr = UnwrapError(receivedErr) - if errors.Is(receivedErr, expectedError) { - return true - } - } - return false -} - -// As checks if any error of the stack matches the expectedType -// API machting the standard library but allowing to wrap errors with ErrCtx + errgo or pkg/errors -func As(receivedErr error, expectedType any) bool { - if errors.As(receivedErr, expectedType) { - return true - } - for receivedErr != nil { - receivedErr = UnwrapError(receivedErr) - if errors.As(receivedErr, expectedType) { - return true - } - } - return false -} - -// RootCause returns the cause of an errors stack, whatever the method they used -// to be stacked: either errgo.Notef or errors.Wrapf. -// -// Deprecated: Use `Is(err, expectedErr)` instead of `if RootCause(err) == expectedErr` to match go standard libraries practices -func RootCause(err error) error { - errCause := errorCause(err) - if errCause == nil { - errCause = errgoRoot(err) - } - return errCause -} - -// IsRootCause return true if the cause of the given error is the same type as -// mytype. -// This function takes the cause of an error if the errors stack has been -// wrapped with errors.Wrapf or errgo.Notef or errgo.NoteMask or errgo.Mask. -// -// Example: -// -// errors.IsRootCause(err, &ValidationErrors{}) -// -// Deprecated: Use `As(err, mytype)` instead to match go standard libraries practices -func IsRootCause(err error, mytype interface{}) bool { - t := reflect.TypeOf(mytype) - errCause := errorCause(err) - errRoot := errgoRoot(err) - return reflect.TypeOf(errCause) == t || reflect.TypeOf(errRoot) == t -} - -// UnwrapError tries to unwrap `err`. It unwraps any causer type, errgo and ErrCtx errors. -// It returns nil if no err found. This provide the possibility to loop on UnwrapError -// by checking the return value. -// E.g.: -// -// for unwrappedErr := err; unwrappedErr != nil; unwrappedErr = UnwrapError(unwrappedErr) { -// ... -// } -func UnwrapError(err error) error { - if err == nil { - return nil - } - - type causer interface { - Cause() error - } - - // if err is type of `ErrCtx` unwrap it by getting errCtx.err - if ctxerr, ok := err.(ErrCtx); ok { - return ctxerr.err - } - - // Check if the err is type of `*errgo.Err` to be able to call `Underlying()` - // method. Both `*errgo.Err` and `*errors.Err` are implementing a causer interface. - // Cause() method from errgo skip all underlying errors, so we may skip a context between. - // So the order matter, we need to call `Cause()` after `Underlying()`. - if errgoErr, ok := err.(*errgo.Err); ok { - return errgoErr.Underlying() - } - - if cause, ok := err.(causer); ok { - return cause.Cause() - } - return nil -} diff --git a/errors/cause_test.go b/errors/cause_test.go deleted file mode 100644 index 1ac86a90..00000000 --- a/errors/cause_test.go +++ /dev/null @@ -1,322 +0,0 @@ -package errors - -import ( - "context" - "io" - "net" - "testing" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - errgo "gopkg.in/errgo.v1" -) - -type customError struct { - WrappedError error - CustomValue string -} - -func (err *customError) Error() string { - return "custom error " + err.CustomValue -} - -func (err *customError) Unwrap() error { - return err.WrappedError -} - -func Test_As(t *testing.T) { - var expectedErrorType *ValidationErrors - var unexpectedErrorType *net.OpError - t.Run("given an error stack with errgo.Notef", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = errgo.Notef(err, "biniou") - - assert.True(t, As(err, &expectedErrorType)) - assert.False(t, As(err, &unexpectedErrorType)) - }) - - t.Run("given an error stack with errors.Wrap", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = errors.Wrap(err, "biniou") - - assert.True(t, As(err, &expectedErrorType)) - assert.False(t, As(err, &unexpectedErrorType)) - }) - - t.Run("given an error stack with errors.Wrapf", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = errors.Wrapf(err, "biniou") - - assert.True(t, As(err, &expectedErrorType)) - assert.False(t, As(err, &unexpectedErrorType)) - }) - - t.Run("given an error stack with Wrap from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = Wrap(context.Background(), err, "biniou") - - assert.True(t, As(err, &expectedErrorType)) - assert.False(t, As(err, &unexpectedErrorType)) - }) - - t.Run("given an error stack with Wrapf from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = Wrapf(context.Background(), err, "biniou") - - assert.True(t, As(err, &expectedErrorType)) - assert.False(t, As(err, &unexpectedErrorType)) - }) - - t.Run("given an error stack with Notef from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = Notef(context.Background(), err, "biniou") - - assert.True(t, As(err, &expectedErrorType)) - assert.False(t, As(err, &unexpectedErrorType)) - }) - - t.Run("given an error in the middle of the stack stack with Notef from ErrCtx", func(t *testing.T) { - var err error - err = io.EOF - err = &customError{WrappedError: err, CustomValue: "value"} - err = Notef(context.Background(), err, "biniou") - - var expectedErrorType *customError - assert.True(t, As(err, &expectedErrorType)) - assert.False(t, As(err, &unexpectedErrorType)) - }) -} - -func Test_Is(t *testing.T) { - t.Run("given an error stack with errgo.Mask", func(t *testing.T) { - expectedError := io.EOF - err := errgo.Mask(expectedError, errgo.Any) - - assert.True(t, Is(err, expectedError)) - }) - - t.Run("given an error stack with errgo.Notef", func(t *testing.T) { - expectedError := io.EOF - err := errgo.Notef(expectedError, "pouet") - - assert.True(t, Is(err, expectedError)) - }) - - t.Run("given an error stack with errors.Wrap", func(t *testing.T) { - expectedError := io.EOF - err := errors.Wrap(expectedError, "pouet") - - assert.True(t, Is(err, expectedError)) - }) - - t.Run("given an error stack with Wrap from ErrCtx", func(t *testing.T) { - expectedError := io.EOF - err := Wrap(context.Background(), expectedError, "pouet") - - assert.True(t, Is(err, expectedError)) - }) - - t.Run("given an error stack with Wrapf from ErrCtx", func(t *testing.T) { - expectedError := io.EOF - err := Wrapf(context.Background(), expectedError, "pouet") - - assert.True(t, Is(err, expectedError)) - }) - - t.Run("given an error stack with mixed types", func(t *testing.T) { - expectedError := io.EOF - err := Notef(context.Background(), expectedError, "pouet") - err = errgo.Notef(err, "pouet") - err = errors.Wrap(err, "pouet") - - assert.True(t, Is(err, expectedError)) - }) -} - -func Test_IsRootCause(t *testing.T) { - t.Run("given an error stack with errgo.Notef", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = errgo.Notef(err, "biniou") - - assert.True(t, IsRootCause(err, &ValidationErrors{})) - assert.False(t, IsRootCause(err, ValidationErrors{})) - }) - - t.Run("given an error stack with errors.Wrap", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = errors.Wrap(err, "biniou") - - assert.True(t, IsRootCause(err, &ValidationErrors{})) - assert.False(t, IsRootCause(err, ValidationErrors{})) - }) - - t.Run("given an error stack with errors.Wrapf", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = errors.Wrapf(err, "biniou") - - assert.True(t, IsRootCause(err, &ValidationErrors{})) - assert.False(t, IsRootCause(err, ValidationErrors{})) - }) - - t.Run("given an error stack with Wrap from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = Wrap(context.Background(), err, "biniou") - - assert.True(t, IsRootCause(err, &ValidationErrors{})) - assert.False(t, IsRootCause(err, ValidationErrors{})) - }) - - t.Run("given an error stack with Wrapf from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = Wrapf(context.Background(), err, "biniou") - - assert.True(t, IsRootCause(err, &ValidationErrors{})) - assert.False(t, IsRootCause(err, ValidationErrors{})) - }) - - t.Run("given an error stack with Notef from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{}) - err = Notef(context.Background(), err, "biniou") - - assert.True(t, IsRootCause(err, &ValidationErrors{})) - assert.False(t, IsRootCause(err, ValidationErrors{})) - }) - -} - -func Test_RootCause(t *testing.T) { - t.Run("given an error stack with errgo.Mask", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = errgo.Mask(err, errgo.Any) - - assert.Equal(t, "test=biniou", RootCause(err).Error()) - }) - - t.Run("given an error stack with errgo.Notef", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = errgo.Notef(err, "pouet") - - assert.Equal(t, "test=biniou", RootCause(err).Error()) - }) - - t.Run("given an error stack with errors.Wrap", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = errors.Wrap(err, "pouet") - - assert.Equal(t, "test=biniou", RootCause(err).Error()) - }) - - t.Run("given an error stack with Wrap from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = Wrap(context.Background(), err, "pouet") - - assert.Equal(t, "test=biniou", RootCause(err).Error()) - }) - - t.Run("given an error stack with Wrapf from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = Wrapf(context.Background(), err, "pouet") - - assert.Equal(t, "test=biniou", RootCause(err).Error()) - }) - - t.Run("given an error stack with Notef from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = Notef(context.Background(), err, "pouet") - - assert.Equal(t, "test=biniou", RootCause(err).Error()) - }) -} - -func Test_UnwrapError(t *testing.T) { - t.Run("given an error stack with errgo.Mask", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = errgo.Mask(err, errgo.Any) - - assert.Equal(t, "test=biniou", UnwrapError(err).Error()) - }) - - t.Run("given an error stack multiple times with errors.Wrap", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = errors.Wrap(err, "pouet") - err = errors.Wrap(err, "pouet") - err = errors.Wrap(err, "pouet") - err = errors.Wrap(err, "pouet") - err = errors.Wrap(err, "pouet") - - var lastErr error - for unwrappedErr := err; unwrappedErr != nil; unwrappedErr = UnwrapError(unwrappedErr) { - lastErr = unwrappedErr - } - - assert.Equal(t, "test=biniou", lastErr.Error()) - }) - - t.Run("given an error stack with Notef from ErrCtx", func(t *testing.T) { - var err error - err = (&ValidationErrors{ - Errors: map[string][]string{ - "test": {"biniou"}, - }, - }) - err = Notef(context.Background(), err, "pouet") - - assert.Equal(t, "pouet: test=biniou", UnwrapError(err).Error()) - }) - t.Run("given an error nil", func(t *testing.T) { - var err error - assert.Nil(t, UnwrapError(err)) - }) -} diff --git a/errors/errctx.go b/errors/errctx.go index e50a5bb9..7451a794 100644 --- a/errors/errctx.go +++ b/errors/errctx.go @@ -4,7 +4,6 @@ import ( "context" "github.com/pkg/errors" - "gopkg.in/errgo.v1" ) type ErrCtx struct { @@ -47,7 +46,7 @@ func Errorf(ctx context.Context, format string, args ...interface{}) error { // Notef is wrapping an error with the underneath errgo library func Notef(ctx context.Context, err error, format string, args ...interface{}) error { - return ErrCtx{ctx: ctx, err: errgo.Notef(err, format, args...)} + return ErrCtx{ctx: ctx, err: errors.Wrapf(err, format, args...)} } // Wrap is wrapping an error with the underneath errgo library @@ -65,43 +64,19 @@ func Wrapf(ctx context.Context, err error, format string, args ...interface{}) e func RootCtxOrFallback(ctx context.Context, err error) context.Context { var lastCtx context.Context - type causer interface { - Cause() error - } - // Unwrap each error to get the deepest context for err != nil { - // First check if the err is type of `*errgo.Err` to be able to call `Underlying()` - // method. Both `*errgo.Err` and `*errors.Err` are implementing a causer interface. - // Cause() method from errgo skip all underlying errors, so we may skip a context between. - // So the order matter, we need to call `Cause()` after `Underlying()`. - errgoErr, ok := err.(*errgo.Err) - if ok { - err = errgoErr.Underlying() - continue - } - - cause, ok := err.(causer) - if ok { - err = cause.Cause() - continue - } - // if err is type of `ErrCtx` unwrap it by getting errCtx.err ctxerr, ok := err.(ErrCtx) if ok { err = ctxerr.err lastCtx = ctxerr.Ctx() - continue } - - break + err = errors.Unwrap(err) } - if lastCtx == nil { return ctx } - return lastCtx } diff --git a/errors/errctx_test.go b/errors/errctx_test.go index c76ce8da..aa5310dd 100644 --- a/errors/errctx_test.go +++ b/errors/errctx_test.go @@ -10,14 +10,13 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "gopkg.in/errgo.v1" ) func TestErrCtx_RootCtxOrFallback(t *testing.T) { t.Run("It should return an error and the given context if the error does not contains any ErrCtx", func(t *testing.T) { // Given err := stdErrors.New("main error") - err = errgo.Notef(err, "wrapping error in func2") + err = errors.Wrapf(err, "wrapping error in func2") ctx := context.WithValue(context.Background(), "test", "test") // When @@ -32,9 +31,9 @@ func TestErrCtx_RootCtxOrFallback(t *testing.T) { // Given ctx := context.WithValue(context.Background(), "field0", "value0") err := funcThrowingError(ctx) - err = Notef(ctx, err, "wrapping error in func2") - err = Notef(ctx, err, "wrapping error in func3") - err = Notef(ctx, err, "wrapping error in func4") + err = Wrapf(ctx, err, "wrapping error in func2") + err = Wrapf(ctx, err, "wrapping error in func3") + err = Wrapf(ctx, err, "wrapping error in func4") // When rootCtx := RootCtxOrFallback(ctx, err) @@ -53,8 +52,8 @@ func TestErrCtx_RootCtxOrFallback(t *testing.T) { // Given ctx := context.WithValue(context.Background(), "field0", "value0") err := funcWrappingAnError(ctx) - err = Notef(ctx, err, "wrapping error in func3") - err = Notef(ctx, err, "wrapping error in func4") + err = Wrapf(ctx, err, "wrapping error in func3") + err = Wrapf(ctx, err, "wrapping error in func4") // When rootCtx := RootCtxOrFallback(ctx, err) @@ -74,8 +73,8 @@ func TestErrCtx_RootCtxOrFallback(t *testing.T) { ctx := context.WithValue(context.Background(), "field0", "value0") // Simulate non ErrCtx error in middle of error path err := funcWrappingAnErrorWithoutErrCtx(ctx) - err = Notef(ctx, err, "wrapping error in func2") - err = Notef(ctx, err, "wrapping error in func3") + err = Wrapf(ctx, err, "wrapping error in func2") + err = Wrapf(ctx, err, "wrapping error in func3") // When rootCtx := RootCtxOrFallback(ctx, err) @@ -101,9 +100,9 @@ func TestErrCtx_RootCtxOrFallback(t *testing.T) { // Simulate non returning error ctx = context.WithValue(ctx, "field2", "value2") err = Newf(ctx, "new error from func2") - err = Notef(ctx, err, "wrapping error in func2") - err = Notef(ctx, err, "wrapping error in func3") - err = Notef(ctx, err, "wrapping error in func4") + err = Wrapf(ctx, err, "wrapping error in func2") + err = Wrapf(ctx, err, "wrapping error in func3") + err = Wrapf(ctx, err, "wrapping error in func4") // When rootCtx := RootCtxOrFallback(ctx, err) @@ -133,7 +132,7 @@ func funcWrappingAnError(ctx context.Context) error { err := funcThrowingError(ctx) if err != nil { - return Notef(ctx, err, "wrapping error from funcWrappingAnError") + return Wrapf(ctx, err, "wrapping error from funcWrappingAnError") } return nil } diff --git a/errors/errgo.go b/errors/errgo.go deleted file mode 100644 index 5a05a5fc..00000000 --- a/errors/errgo.go +++ /dev/null @@ -1,22 +0,0 @@ -package errors - -import ( - "gopkg.in/errgo.v1" -) - -func errgoRoot(err error) error { - for { - e, ok := err.(ErrCtx) - if ok { - err = e.err - } - errgoErr, ok := err.(*errgo.Err) - if !ok { - return err - } - if errgoErr.Underlying() == nil { - return err - } - err = errgoErr.Underlying() - } -} diff --git a/errors/errors.go b/errors/errors.go index 0300414b..5e88803b 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -1,21 +1,40 @@ package errors -func errorCause(err error) error { - type causer interface { - Cause() error - } +import ( + "errors" +) - for err != nil { - e, ok := err.(ErrCtx) - if ok { - err = e.err +// Is checks if any error of the stack matches the error value expectedError +// API machting the standard library but allowing to wrap errors with ErrCtx + errgo or pkg/errors +func Is(receivedErr, expectedError error) bool { + if errors.Is(receivedErr, expectedError) { + return true + } + for receivedErr != nil { + receivedErr = Unwrap(receivedErr) + if errors.Is(receivedErr, expectedError) { + return true } + } + return false +} - cause, ok := err.(causer) - if !ok { - break +// As checks if any error of the stack matches the expectedType +// API machting the standard library but allowing to wrap errors with ErrCtx + errgo or pkg/errors +func As(receivedErr error, expectedType any) bool { + if errors.As(receivedErr, expectedType) { + return true + } + for receivedErr != nil { + receivedErr = Unwrap(receivedErr) + if errors.As(receivedErr, expectedType) { + return true } - err = cause.Cause() } - return err + return false +} + +// Unwrap tries to unwrap `err`, getting the wrapped error or nil. +func Unwrap(err error) error { + return errors.Unwrap(err) } diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 00000000..a6c23be8 --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,115 @@ +package errors + +import ( + "context" + "io" + "net" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +type customError struct { + WrappedError error + CustomValue string +} + +func (err *customError) Error() string { + return "custom error " + err.CustomValue +} + +func (err *customError) Unwrap() error { + return err.WrappedError +} + +func Test_As(t *testing.T) { + var expectedErrorType *ValidationErrors + var unexpectedErrorType *net.OpError + t.Run("given an error stack with errors.Wrap", func(t *testing.T) { + var err error + err = (&ValidationErrors{}) + err = errors.Wrap(err, "biniou") + + assert.True(t, As(err, &expectedErrorType)) + assert.False(t, As(err, &unexpectedErrorType)) + }) + + t.Run("given an error stack with errors.Wrapf", func(t *testing.T) { + var err error + err = (&ValidationErrors{}) + err = errors.Wrapf(err, "biniou") + + assert.True(t, As(err, &expectedErrorType)) + assert.False(t, As(err, &unexpectedErrorType)) + }) + + t.Run("given an error stack with Wrap from ErrCtx", func(t *testing.T) { + var err error + err = (&ValidationErrors{}) + err = Wrap(context.Background(), err, "biniou") + + assert.True(t, As(err, &expectedErrorType)) + assert.False(t, As(err, &unexpectedErrorType)) + }) + + t.Run("given an error stack with Wrapf from ErrCtx", func(t *testing.T) { + var err error + err = (&ValidationErrors{}) + err = Wrapf(context.Background(), err, "biniou") + + assert.True(t, As(err, &expectedErrorType)) + assert.False(t, As(err, &unexpectedErrorType)) + }) +} + +func Test_Is(t *testing.T) { + t.Run("given an error stack with errors.Wrap", func(t *testing.T) { + expectedError := io.EOF + err := errors.Wrap(expectedError, "pouet") + + assert.True(t, Is(err, expectedError)) + }) + + t.Run("given an error stack with Wrap from ErrCtx", func(t *testing.T) { + expectedError := io.EOF + err := Wrap(context.Background(), expectedError, "pouet") + + assert.True(t, Is(err, expectedError)) + }) + + t.Run("given an error stack with Wrapf from ErrCtx", func(t *testing.T) { + expectedError := io.EOF + err := Wrapf(context.Background(), expectedError, "pouet") + + assert.True(t, Is(err, expectedError)) + }) +} + +func Test_Unwrap(t *testing.T) { + t.Run("given an error stack multiple times with errors.Wrap", func(t *testing.T) { + var err error + err = (&ValidationErrors{ + Errors: map[string][]string{ + "test": {"biniou"}, + }, + }) + err = errors.Wrap(err, "pouet") + err = errors.Wrap(err, "pouet") + err = errors.Wrap(err, "pouet") + err = errors.Wrap(err, "pouet") + err = errors.Wrap(err, "pouet") + + var lastErr error + for unwrappedErr := err; unwrappedErr != nil; unwrappedErr = Unwrap(unwrappedErr) { + lastErr = unwrappedErr + } + + assert.Equal(t, "test=biniou", lastErr.Error()) + }) + + t.Run("given an error nil", func(t *testing.T) { + var err error + assert.Nil(t, Unwrap(err)) + }) +} diff --git a/errors/go.mod b/errors/go.mod index acc9d3fd..76bec509 100644 --- a/errors/go.mod +++ b/errors/go.mod @@ -1,11 +1,10 @@ -module github.com/Scalingo/go-utils/errors/v2 +module github.com/Scalingo/go-utils/errors/v3 go 1.20 require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.2 - gopkg.in/errgo.v1 v1.0.1 ) require (