diff --git a/errors/CHANGELOG.md b/errors/CHANGELOG.md index 74f3430558..4d563a90b8 100644 --- a/errors/CHANGELOG.md +++ b/errors/CHANGELOG.md @@ -31,6 +31,21 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +## [v2.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/errors/%2Fv2.0.0) + +### API Breaking Changes + +* [#20402](https://github.com/cosmos/cosmos-sdk/pull/20402) Remove Grpc error codes from the error package. This is done in order to keep the dependency graph of errors minimal +* [#20539](https://github.com/cosmos/cosmos-sdk/pull/20539) Removes `IsOf`, `Recover`, `WithType` and wrapped error. The errors package uses the go std library errors. It provides a `Wrap` and `Wrapf` to help in the migration from v1 to v2. + +## [v1.0.1](https://github.com/cosmos/cosmos-sdk/releases/tag/errors%2Fv1.0.1) + +### Improvements + +* [#18918](https://github.com/cosmos/cosmos-sdk/pull/18918) Improve `IsOf` by returning earlier when the checked error is nil. + +## [v1.0.0](https://github.com/cosmos/cosmos-sdk/releases/tag/errors%2Fv1.0.0) + ### Features * [#15989](https://github.com/cosmos/cosmos-sdk/pull/15989) Add `ErrStopIterating` for modules to use for breaking out of iteration. diff --git a/errors/abci.go b/errors/abci.go index 603f3b36ff..4fa5b959db 100644 --- a/errors/abci.go +++ b/errors/abci.go @@ -1,6 +1,7 @@ package errors import ( + "errors" "fmt" "reflect" ) @@ -34,7 +35,8 @@ func ABCIInfo(err error, debug bool) (codespace string, code uint32, log string) encode = debugErrEncoder } - return abciCodespace(err), abciCode(err), encode(err) + code, space := abciInfo(err) + return space, code, encode(err) } // The debugErrEncoder encodes the error with a stacktrace. @@ -46,54 +48,25 @@ func defaultErrEncoder(err error) string { return err.Error() } -type coder interface { - ABCICode() uint32 -} - -// abciCode tests if given error contains an ABCI code and returns the value of +// abciInfo tests if given error contains an ABCI code and returns the value of // it if available. This function is testing for the causer interface as well // and unwraps the error. -func abciCode(err error) uint32 { +func abciInfo(err error) (code uint32, codespace string) { if errIsNil(err) { - return SuccessABCICode + return SuccessABCICode, "" } - for { - if c, ok := err.(coder); ok { - return c.ABCICode() - } + var customErr *Error - if c, ok := err.(causer); ok { - err = c.Cause() - } else { - return internalABCICode - } - } -} - -type codespacer interface { - Codespace() string -} - -// abciCodespace tests if given error contains a codespace and returns the value of -// it if available. This function is testing for the causer interface as well -// and unwraps the error. -func abciCodespace(err error) string { - if errIsNil(err) { - return "" + if errors.As(err, &customErr) { + code = customErr.ABCICode() + codespace = customErr.Codespace() + } else { + code = internalABCICode + codespace = internalABCICodespace } - for { - if c, ok := err.(codespacer); ok { - return c.Codespace() - } - - if c, ok := err.(causer); ok { - err = c.Cause() - } else { - return internalABCICodespace - } - } + return } // errIsNil returns true if value represented by the given error is nil. diff --git a/errors/abci_test.go b/errors/abci_test.go index 333731d015..54afb88726 100644 --- a/errors/abci_test.go +++ b/errors/abci_test.go @@ -3,25 +3,10 @@ package errors import ( "fmt" "io" - "strings" "testing" - - "github.com/stretchr/testify/suite" ) -type abciTestSuite struct { - suite.Suite -} - -func TestABCITestSuite(t *testing.T) { - suite.Run(t, new(abciTestSuite)) -} - -func (s *abciTestSuite) SetupSuite() { - s.T().Parallel() -} - -func (s *abciTestSuite) TestABCInfo() { +func TestABCInfo(t *testing.T) { cases := map[string]struct { err error debug bool @@ -37,7 +22,7 @@ func (s *abciTestSuite) TestABCInfo() { wantSpace: testCodespace, }, "wrapped SDK error": { - err: Wrap(Wrap(ErrUnauthorized, "foo"), "bar"), + err: fmt.Errorf("bar: %w", fmt.Errorf("foo: %w", ErrUnauthorized)), debug: false, wantLog: "bar: foo: unauthorized", wantCode: ErrUnauthorized.code, @@ -64,93 +49,29 @@ func (s *abciTestSuite) TestABCInfo() { wantCode: 1, wantSpace: UndefinedCodespace, }, - // This is hard to test because of attached stacktrace. This - // case is tested in an another test. - // "wrapped stdlib is a full message in debug mode": { - // err: Wrap(io.EOF, "cannot read file"), - // debug: true, - // wantLog: "cannot read file: EOF", - // wantCode: 1, - // }, - "custom error": { - err: customErr{}, - debug: false, - wantLog: "custom", - wantCode: 999, - wantSpace: "extern", - }, - "custom error in debug mode": { - err: customErr{}, - debug: true, - wantLog: "custom", - wantCode: 999, - wantSpace: "extern", - }, } for testName, tc := range cases { - s.T().Run(testName, func(t *testing.T) { + t.Run(testName, func(t *testing.T) { space, code, log := ABCIInfo(tc.err, tc.debug) - s.Require().Equal(tc.wantSpace, space, testName) - s.Require().Equal(tc.wantCode, code, testName) - s.Require().Equal(tc.wantLog, log, testName) - }) - } -} - -func (s *abciTestSuite) TestABCIInfoStacktrace() { - cases := map[string]struct { - err error - debug bool - wantStacktrace bool - wantErrMsg string - }{ - "wrapped SDK error in debug mode provides stacktrace": { - err: Wrap(ErrUnauthorized, "wrapped"), - debug: true, - wantStacktrace: true, - wantErrMsg: "wrapped: unauthorized", - }, - "wrapped SDK error in non-debug mode does not have stacktrace": { - err: Wrap(ErrUnauthorized, "wrapped"), - debug: false, - wantStacktrace: false, - wantErrMsg: "wrapped: unauthorized", - }, - "wrapped stdlib error in debug mode provides stacktrace": { - err: Wrap(fmt.Errorf("stdlib"), "wrapped"), - debug: true, - wantStacktrace: true, - wantErrMsg: "wrapped: stdlib", - }, - } - - const thisTestSrc = "cosmossdk.io/errors.(*abciTestSuite).TestABCIInfoStacktrace" - - for testName, tc := range cases { - s.T().Run(testName, func(t *testing.T) { - _, _, log := ABCIInfo(tc.err, tc.debug) - if !tc.wantStacktrace { - s.Require().Equal(tc.wantErrMsg, log, testName) - } else { - s.Require().True(strings.Contains(log, thisTestSrc), testName) - s.Require().True(strings.Contains(log, tc.wantErrMsg), testName) + if space != tc.wantSpace { + t.Errorf("%s: expected space %s, got %s", testName, tc.wantSpace, space) + } + if code != tc.wantCode { + t.Errorf("%s: expected code %d, got %d", testName, tc.wantCode, code) + } + if log != tc.wantLog { + t.Errorf("%s: expected log %s, got %s", testName, tc.wantLog, log) } }) } } -func (s *abciTestSuite) TestABCIInfoHidesStacktrace() { - err := Wrap(ErrUnauthorized, "wrapped") - _, _, log := ABCIInfo(err, false) - s.Require().Equal("wrapped: unauthorized", log) -} - -func (s *abciTestSuite) TestABCIInfoSerializeErr() { +func TestABCIInfoSerializeErr(t *testing.T) { var ( - // Create errors with stacktrace for equal comparison. - myErrDecode = Wrap(ErrTxDecode, "test") - myErrAddr = Wrap(ErrInvalidAddress, "tester") + // Create errors for equal comparison. + myErrDecode = fmt.Errorf("test: %w", ErrTxDecode) + myErrAddr = fmt.Errorf("tester: %w", ErrInvalidAddress) myPanic = ErrPanic ) @@ -181,18 +102,10 @@ func (s *abciTestSuite) TestABCIInfoSerializeErr() { }, } for msg, spec := range specs { - spec := spec + _, _, log := ABCIInfo(spec.src, spec.debug) - s.Require().Equal(spec.exp, log, msg) + if log != spec.exp { + t.Errorf("%s: expected log %s, got %s", msg, spec.exp, log) + } } } - -// customErr is a custom implementation of an error that provides an ABCICode -// method. -type customErr struct{} - -func (customErr) Codespace() string { return "extern" } - -func (customErr) ABCICode() uint32 { return 999 } - -func (customErr) Error() string { return "custom" } diff --git a/errors/doc.go b/errors/doc.go index ed6b9a69bf..ad904e5bf7 100644 --- a/errors/doc.go +++ b/errors/doc.go @@ -1,33 +1,31 @@ -/* -Package errors implements custom error interfaces for cosmos-sdk. - -Error declarations should be generic and cover broad range of cases. Each -returned error instance can wrap a generic error declaration to provide more -details. - -This package provides a broad range of errors declared that fits all common -cases. If an error is very specific for an extension it can be registered outside -of the errors package. If it will be needed my many extensions, please consider -registering it in the errors package. To create a new error instance use Register -function. You must provide a unique, non zero error code and a short description, for example: - - var ErrZeroDivision = errors.Register(9241, "zero division") - -When returning an error, you can attach to it an additional context -information by using Wrap function, for example: - - func safeDiv(val, div int) (int, err) { - if div == 0 { - return 0, errors.Wrapf(ErrZeroDivision, "cannot divide %d", val) - } - return val / div, nil - } - -The first time an error instance is wrapped a stacktrace is attached as well. -Stacktrace information can be printed using %+v and %v formats. - - %s is just the error message - %+v is the full stack trace - %v appends a compressed [filename:line] where the error was created -*/ +// Package errors implements custom error interfaces for cosmos-sdk. +// +// Error declarations should be generic and cover broad range of cases. Each +// returned error instance can wrap a generic error declaration to provide more +// details. +// +// This package provides a broad range of errors declared that fits all common +// cases. If an error is very specific for an extension it can be registered outside +// of the errors package. If it will be needed my many extensions, please consider +// registering it in the errors package. To create a new error instance use Register +// function. You must provide a unique, non-zero error code and a short description, for example: +// +// var ErrZeroDivision = errors.Register(9241, "zero division") +// +// When returning an error, you can attach to it an additional context +// information by using Wrap function, for example: +// +// func safeDiv(val, div int) (int, err) { +// if div == 0 { +// return 0, errors.Wrapf(ErrZeroDivision, "cannot divide %d", val) +// } +// return val / div, nil +// } +// +// The first time an error instance is wrapped a stacktrace is attached as well. +// Stacktrace information can be printed using %+v and %v formats. +// +// %s is just the error message +// %+v is the full stack trace +// %v appends a compressed [filename:line] where the error was created package errors diff --git a/errors/errors.go b/errors/errors.go index 2e5140d728..88de804660 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -2,24 +2,17 @@ package errors import ( "fmt" - "reflect" - - "github.com/pkg/errors" - grpccodes "google.golang.org/grpc/codes" - grpcstatus "google.golang.org/grpc/status" ) // UndefinedCodespace when we explicitly declare no codespace const UndefinedCodespace = "undefined" var ( - // errInternal should never be exposed, but we reserve this code for non-specified errors - errInternal = Register(UndefinedCodespace, 1, "internal") // ErrStopIterating is used to break out of an iteration ErrStopIterating = Register(UndefinedCodespace, 2, "stop iterating") - // ErrPanic should only be set when we recovering from a panic + // ErrPanic should only be set when we recover from a panic ErrPanic = Register(UndefinedCodespace, 111222, "panic") ) @@ -32,18 +25,11 @@ var ( // // Use this function only during a program startup phase. func Register(codespace string, code uint32, description string) *Error { - return RegisterWithGRPCCode(codespace, code, grpccodes.Unknown, description) -} - -// RegisterWithGRPCCode is a version of Register that associates a gRPC error -// code with a registered error. -func RegisterWithGRPCCode(codespace string, code uint32, grpcCode grpccodes.Code, description string) *Error { - // TODO - uniqueness is (codespace, code) combo if e := getUsed(codespace, code); e != nil { panic(fmt.Sprintf("error with code %d is already registered: %q", code, e.desc)) } - err := &Error{codespace: codespace, code: code, desc: description, grpcCode: grpcCode} + err := &Error{codespace: codespace, code: code, desc: description} setUsed(err) return err @@ -74,11 +60,11 @@ func setUsed(err *Error) { // The server (abci app / blockchain) should only refer to registered errors func ABCIError(codespace string, code uint32, log string) error { if e := getUsed(codespace, code); e != nil { - return Wrap(e, log) + return fmt.Errorf("%s: %w", log, e) } // This is a unique error, will never match on .Is() // Use Wrap here to get a stack trace - return Wrap(&Error{codespace: codespace, code: code, desc: "unknown"}, log) + return fmt.Errorf("%s: %w", log, &Error{codespace: codespace, code: code, desc: "unknown"}) } // Error represents a root error. @@ -94,7 +80,6 @@ type Error struct { codespace string code uint32 desc string - grpcCode grpccodes.Code } // New is an alias for Register. @@ -114,88 +99,22 @@ func (e Error) Codespace() string { return e.codespace } -// Is check if given error instance is of a given kind/type. This involves -// unwrapping given error using the Cause method if available. -func (e *Error) Is(err error) bool { - // Reflect usage is necessary to correctly compare with - // a nil implementation of an error. - if e == nil { - return isNilErr(err) - } - - for { - if err == e { - return true - } - - // If this is a collection of errors, this function must return - // true if at least one from the group match. - if u, ok := err.(unpacker); ok { - for _, er := range u.Unpack() { - if e.Is(er) { - return true - } - } - } - - if c, ok := err.(causer); ok { - err = c.Cause() - } else { - return false - } - } -} - -// Wrap extends this error with an additional information. -// It's a handy function to call Wrap with sdk errors. -func (e *Error) Wrap(desc string) error { return Wrap(e, desc) } - -// Wrapf extends this error with an additional information. -// It's a handy function to call Wrapf with sdk errors. -func (e *Error) Wrapf(desc string, args ...interface{}) error { return Wrapf(e, desc, args...) } - -func (e *Error) GRPCStatus() *grpcstatus.Status { - return grpcstatus.Newf(e.grpcCode, "codespace %s code %d: %s", e.codespace, e.code, e.desc) -} - -func isNilErr(err error) bool { - // Reflect usage is necessary to correctly compare with - // a nil implementation of an error. - if err == nil { - return true - } - if reflect.ValueOf(err).Kind() == reflect.Struct { - return false - } - return reflect.ValueOf(err).IsNil() -} - -// Wrap extends given error with an additional information. +// Wrap extends given error with additional information. // -// If the wrapped error does not provide ABCICode method (ie. stdlib errors), +// If the wrapped error does not provide ABCICode method (i.e. stdlib errors), // it will be labeled as internal error. // // If err is nil, this returns nil, avoiding the need for an if statement when -// wrapping a error returned at the end of a function +// wrapping an error returned at the end of a function func Wrap(err error, description string) error { if err == nil { return nil } - // If this error does not carry the stacktrace information yet, attach - // one. This should be done only once per error at the lowest frame - // possible (most inner wrap). - if stackTrace(err) == nil { - err = errors.WithStack(err) - } - - return &wrappedError{ - parent: err, - msg: description, - } + return fmt.Errorf("%s: %w", description, err) } -// Wrapf extends given error with an additional information. +// Wrapf extends given error with additional information. // // This function works like Wrap function with additional functionality of // formatting the input as specified. @@ -203,101 +122,3 @@ func Wrapf(err error, format string, args ...interface{}) error { desc := fmt.Sprintf(format, args...) return Wrap(err, desc) } - -type wrappedError struct { - // This error layer description. - msg string - // The underlying error that triggered this one. - parent error -} - -func (e *wrappedError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.parent.Error()) -} - -func (e *wrappedError) Cause() error { - return e.parent -} - -// Is reports whether any error in e's chain matches a target. -func (e *wrappedError) Is(target error) bool { - if e == target { - return true - } - - w := e.Cause() - for { - if w == target { - return true - } - - x, ok := w.(causer) - if ok { - w = x.Cause() - } - if x == nil { - return false - } - } -} - -// Unwrap implements the built-in errors.Unwrap -func (e *wrappedError) Unwrap() error { - return e.parent -} - -// GRPCStatus gets the gRPC status from the wrapped error or returns an unknown gRPC status. -func (e *wrappedError) GRPCStatus() *grpcstatus.Status { - w := e.Cause() - for { - if hasStatus, ok := w.(interface { - GRPCStatus() *grpcstatus.Status - }); ok { - status := hasStatus.GRPCStatus() - return grpcstatus.New(status.Code(), fmt.Sprintf("%s: %s", status.Message(), e.msg)) - } - - x, ok := w.(causer) - if ok { - w = x.Cause() - } - if x == nil { - return grpcstatus.New(grpccodes.Unknown, e.msg) - } - } -} - -// Recover captures a panic and stop its propagation. If panic happens it is -// transformed into a ErrPanic instance and assigned to given error. Call this -// function using defer in order to work as expected. -func Recover(err *error) { - if r := recover(); r != nil { - *err = Wrapf(ErrPanic, "%v", r) - } -} - -// WithType is a helper to augment an error with a corresponding type message -func WithType(err error, obj interface{}) error { - return Wrap(err, fmt.Sprintf("%T", obj)) -} - -// IsOf checks if a received error is caused by one of the target errors. -// It extends the errors.Is functionality to a list of errors. -func IsOf(received error, targets ...error) bool { - for _, t := range targets { - if errors.Is(received, t) { - return true - } - } - return false -} - -// causer is an interface implemented by an error that supports wrapping. Use -// it to test if an error wraps another error instance. -type causer interface { - Cause() error -} - -type unpacker interface { - Unpack() []error -} diff --git a/errors/errors_test.go b/errors/errors_test.go index 48a978c8f3..ce146ea412 100644 --- a/errors/errors_test.go +++ b/errors/errors_test.go @@ -1,229 +1,18 @@ package errors import ( - stdlib "errors" - "fmt" "testing" - - "github.com/pkg/errors" - "github.com/stretchr/testify/suite" - "google.golang.org/grpc/codes" - grpcstatus "google.golang.org/grpc/status" ) -type errorsTestSuite struct { - suite.Suite -} - -func TestErrorsTestSuite(t *testing.T) { - suite.Run(t, new(errorsTestSuite)) -} - -func (s *errorsTestSuite) SetupSuite() { - s.T().Parallel() -} - -func (s *errorsTestSuite) TestCause() { - std := stdlib.New("this is a stdlib error") - - cases := map[string]struct { - err error - root error - }{ - "Errors are self-causing": { - err: ErrUnauthorized, - root: ErrUnauthorized, - }, - "Wrap reveals root cause": { - err: Wrap(ErrUnauthorized, "foo"), - root: ErrUnauthorized, - }, - "Cause works for stderr as root": { - err: Wrap(std, "Some helpful text"), - root: std, - }, +func TestABCIError(t *testing.T) { + if err := ABCIError(testCodespace, 2, "custom"); err.Error() != "custom: tx parse error" { + t.Errorf("expected error message: custom: tx parse error, got: %v", err.Error()) } - - for testName, tc := range cases { - s.Require().Equal(tc.root, errors.Cause(tc.err), testName) + if err := ABCIError("unknown", 1, "custom"); err.Error() != "custom: unknown" { + t.Errorf("expected error message: custom: unknown, got: %v", err.Error()) } } -func (s *errorsTestSuite) TestErrorIs() { - cases := map[string]struct { - a *Error - b error - wantIs bool - }{ - "instance of the same error": { - a: ErrUnauthorized, - b: ErrUnauthorized, - wantIs: true, - }, - "two different coded errors": { - a: ErrUnauthorized, - b: ErrOutOfGas, - wantIs: false, - }, - "successful comparison to a wrapped error": { - a: ErrUnauthorized, - b: Wrap(ErrUnauthorized, "gone"), - wantIs: true, - }, - "unsuccessful comparison to a wrapped error": { - a: ErrUnauthorized, - b: Wrap(ErrInsufficientFee, "too big"), - wantIs: false, - }, - "not equal to stdlib error": { - a: ErrUnauthorized, - b: fmt.Errorf("stdlib error"), - wantIs: false, - }, - "not equal to a wrapped stdlib error": { - a: ErrUnauthorized, - b: Wrap(fmt.Errorf("stdlib error"), "wrapped"), - wantIs: false, - }, - "nil is nil": { - a: nil, - b: nil, - wantIs: true, - }, - "nil is any error nil": { - a: nil, - b: (*customError)(nil), - wantIs: true, - }, - "nil is not not-nil": { - a: nil, - b: ErrUnauthorized, - wantIs: false, - }, - "not-nil is not nil": { - a: ErrUnauthorized, - b: nil, - wantIs: false, - }, - } - for testName, tc := range cases { - s.Require().Equal(tc.wantIs, tc.a.Is(tc.b), testName) - } -} - -func (s *errorsTestSuite) TestIsOf() { - require := s.Require() - - var errNil *Error - err := ErrInvalidAddress - errW := Wrap(ErrLogic, "more info") - - require.False(IsOf(errNil), "nil error should always have no causer") - require.False(IsOf(errNil, err), "nil error should always have no causer") - - require.False(IsOf(err)) - require.False(IsOf(err, nil)) - require.False(IsOf(err, ErrLogic)) - - require.True(IsOf(errW, ErrLogic)) - require.True(IsOf(errW, err, ErrLogic)) - require.True(IsOf(errW, nil, errW), "error should much itself") - err2 := errors.New("other error") - require.True(IsOf(err2, nil, err2), "error should much itself") -} - -type customError struct{} - -func (customError) Error() string { - return "custom error" -} - -func (s *errorsTestSuite) TestWrapEmpty() { - s.Require().Nil(Wrap(nil, "wrapping ")) -} - -func (s *errorsTestSuite) TestWrappedIs() { - require := s.Require() - err := Wrap(ErrTxTooLarge, "context") - require.True(stdlib.Is(err, ErrTxTooLarge)) - - err = Wrap(err, "more context") - require.True(stdlib.Is(err, ErrTxTooLarge)) - - err = Wrap(err, "even more context") - require.True(stdlib.Is(err, ErrTxTooLarge)) - - err = Wrap(ErrInsufficientFee, "...") - require.False(stdlib.Is(err, ErrTxTooLarge)) - - errs := stdlib.New("other") - require.True(stdlib.Is(errs, errs)) - - errw := &wrappedError{"msg", errs} - require.True(errw.Is(errw), "should match itself") - - require.True(stdlib.Is(ErrInsufficientFee.Wrap("wrapped"), ErrInsufficientFee)) - require.True(IsOf(ErrInsufficientFee.Wrap("wrapped"), ErrInsufficientFee)) - require.True(stdlib.Is(ErrInsufficientFee.Wrapf("wrapped"), ErrInsufficientFee)) - require.True(IsOf(ErrInsufficientFee.Wrapf("wrapped"), ErrInsufficientFee)) -} - -func (s *errorsTestSuite) TestWrappedIsMultiple() { - errTest := errors.New("test error") - errTest2 := errors.New("test error 2") - err := Wrap(errTest2, Wrap(errTest, "some random description").Error()) - s.Require().True(stdlib.Is(err, errTest2)) -} - -func (s *errorsTestSuite) TestWrappedIsFail() { - errTest := errors.New("test error") - errTest2 := errors.New("test error 2") - err := Wrap(errTest2, Wrap(errTest, "some random description").Error()) - s.Require().False(stdlib.Is(err, errTest)) -} - -func (s *errorsTestSuite) TestWrappedUnwrap() { - errTest := errors.New("test error") - err := Wrap(errTest, "some random description") - s.Require().Equal(errTest, stdlib.Unwrap(err)) -} - -func (s *errorsTestSuite) TestWrappedUnwrapMultiple() { - errTest := errors.New("test error") - errTest2 := errors.New("test error 2") - err := Wrap(errTest2, Wrap(errTest, "some random description").Error()) - s.Require().Equal(errTest2, stdlib.Unwrap(err)) -} - -func (s *errorsTestSuite) TestWrappedUnwrapFail() { - errTest := errors.New("test error") - errTest2 := errors.New("test error 2") - err := Wrap(errTest2, Wrap(errTest, "some random description").Error()) - s.Require().NotEqual(errTest, stdlib.Unwrap(err)) -} - -func (s *errorsTestSuite) TestABCIError() { - s.Require().Equal("custom: tx parse error", ABCIError(testCodespace, 2, "custom").Error()) - s.Require().Equal("custom: unknown", ABCIError("unknown", 1, "custom").Error()) -} - -func (s *errorsTestSuite) TestGRPCStatus() { - s.Require().Equal(codes.Unknown, grpcstatus.Code(errInternal)) - s.Require().Equal(codes.NotFound, grpcstatus.Code(ErrNotFound)) - - status, ok := grpcstatus.FromError(ErrNotFound) - s.Require().True(ok) - s.Require().Equal("codespace testtesttest code 38: not found", status.Message()) - - // test wrapping - s.Require().Equal(codes.Unimplemented, grpcstatus.Code(ErrNotSupported.Wrap("test"))) - s.Require().Equal(codes.FailedPrecondition, grpcstatus.Code(ErrConflict.Wrapf("test %s", "foo"))) - - status, ok = grpcstatus.FromError(ErrNotFound.Wrap("test")) - s.Require().True(ok) - s.Require().Equal("codespace testtesttest code 38: not found: test", status.Message()) -} - const testCodespace = "testtesttest" var ( @@ -252,8 +41,5 @@ var ( ErrUnknownExtensionOptions = Register(testCodespace, 31, "unknown extension options") ErrPackAny = Register(testCodespace, 33, "failed packing protobuf message to Any") ErrLogic = Register(testCodespace, 35, "internal logic error") - ErrConflict = RegisterWithGRPCCode(testCodespace, 36, codes.FailedPrecondition, "conflict") - ErrNotSupported = RegisterWithGRPCCode(testCodespace, 37, codes.Unimplemented, "feature not supported") - ErrNotFound = RegisterWithGRPCCode(testCodespace, 38, codes.NotFound, "not found") ErrIO = Register(testCodespace, 39, "Internal IO error") ) diff --git a/errors/go.mod b/errors/go.mod index caed8d337d..24e095e7af 100644 --- a/errors/go.mod +++ b/errors/go.mod @@ -1,23 +1,5 @@ -module cosmossdk.io/errors +module cosmossdk.io/errors/v2 -go 1.20 +// NOTE: this go.mod should have zero dependencies. -require ( - github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.8.4 - google.golang.org/grpc v1.55.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.8.1 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sys v0.8.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) +go 1.22 diff --git a/errors/go.sum b/errors/go.sum index 88ae026e38..e69de29bb2 100644 --- a/errors/go.sum +++ b/errors/go.sum @@ -1,42 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/errors/handle.go b/errors/handle.go deleted file mode 100644 index 33c3fbfdea..0000000000 --- a/errors/handle.go +++ /dev/null @@ -1,12 +0,0 @@ -package errors - -import "fmt" - -// AssertNil panics on error -// Should be only used with interface methods, which require return error, but the -// error is always nil -func AssertNil(err error) { - if err != nil { - panic(fmt.Errorf("logic error - this should never happen. %w", err)) - } -} diff --git a/errors/stacktrace.go b/errors/stacktrace.go deleted file mode 100644 index 7453284dd3..0000000000 --- a/errors/stacktrace.go +++ /dev/null @@ -1,122 +0,0 @@ -package errors - -import ( - "fmt" - "io" - "runtime" - "strings" - - "github.com/pkg/errors" -) - -func matchesFunc(f errors.Frame, prefixes ...string) bool { - fn := funcName(f) - for _, prefix := range prefixes { - if strings.HasPrefix(fn, prefix) { - return true - } - } - return false -} - -// funcName returns the name of this function, if known. -func funcName(f errors.Frame) string { - // this looks a bit like magic, but follows example here: - // https://github.com/pkg/errors/blob/v0.8.1/stack.go#L43-L50 - pc := uintptr(f) - 1 - fn := runtime.FuncForPC(pc) - if fn == nil { - return "unknown" - } - return fn.Name() -} - -func fileLine(f errors.Frame) (string, int) { - // this looks a bit like magic, but follows example here: - // https://github.com/pkg/errors/blob/v0.8.1/stack.go#L14-L27 - // as this is where we get the Frames - pc := uintptr(f) - 1 - fn := runtime.FuncForPC(pc) - if fn == nil { - return "unknown", 0 - } - return fn.FileLine(pc) -} - -func trimInternal(st errors.StackTrace) errors.StackTrace { - // trim our internal parts here - // manual error creation, or runtime for caught panics - for matchesFunc(st[0], - // where we create errors - "cosmossdk.io/errors.Wrap", - "cosmossdk.io/errors.Wrapf", - "cosmossdk.io/errors.WithType", - // runtime are added on panics - "runtime.", - // _test is defined in coverage tests, causing failure - // "/_test/" - ) { - st = st[1:] - } - // trim out outer wrappers (runtime.goexit and test library if present) - for l := len(st) - 1; l > 0 && matchesFunc(st[l], "runtime.", "testing."); l-- { - st = st[:l] - } - return st -} - -func writeSimpleFrame(s io.Writer, f errors.Frame) { - file, line := fileLine(f) - // cut file at "github.com/" - // TODO: generalize better for other hosts? - chunks := strings.SplitN(file, "github.com/", 2) - if len(chunks) == 2 { - file = chunks[1] - } - _, _ = fmt.Fprintf(s, " [%s:%d]", file, line) -} - -// Format works like pkg/errors, with additions. -// %s is just the error message -// %+v is the full stack trace -// %v appends a compressed [filename:line] where the error -// -// was created -// -// Inspired by https://github.com/pkg/errors/blob/v0.8.1/errors.go#L162-L176 -func (e *wrappedError) Format(s fmt.State, verb rune) { - // normal output here.... - if verb != 'v' { - _, _ = fmt.Fprint(s, e.Error()) - return - } - // work with the stack trace... whole or part - stack := trimInternal(stackTrace(e)) - if s.Flag('+') { - _, _ = fmt.Fprintf(s, "%+v\n", stack) - _, _ = fmt.Fprint(s, e.Error()) - } else { - _, _ = fmt.Fprint(s, e.Error()) - writeSimpleFrame(s, stack[0]) - } -} - -// stackTrace returns the first found stack trace frame carried by given error -// or any wrapped error. It returns nil if no stack trace is found. -func stackTrace(err error) errors.StackTrace { - type stackTracer interface { - StackTrace() errors.StackTrace - } - - for { - if st, ok := err.(stackTracer); ok { - return st.StackTrace() - } - - if c, ok := err.(causer); ok { - err = c.Cause() - } else { - return nil - } - } -} diff --git a/errors/stacktrace_test.go b/errors/stacktrace_test.go deleted file mode 100644 index c0a8d6141c..0000000000 --- a/errors/stacktrace_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package errors - -import ( - "errors" - "fmt" - "reflect" - "strings" -) - -func (s *errorsTestSuite) TestStackTrace() { - cases := map[string]struct { - err error - wantError string - }{ - "New gives us a stacktrace": { - err: Wrap(ErrNoSignatures, "name"), - wantError: "name: no signatures supplied", - }, - "Wrapping stderr gives us a stacktrace": { - err: Wrap(fmt.Errorf("foo"), "standard"), - wantError: "standard: foo", - }, - "Wrapping pkg/errors gives us clean stacktrace": { - err: Wrap(errors.New("bar"), "pkg"), - wantError: "pkg: bar", - }, - "Wrapping inside another function is still clean": { - err: Wrap(fmt.Errorf("indirect"), "do the do"), - wantError: "do the do: indirect", - }, - } - - // Wrapping code is unwanted in the errors stack trace. - unwantedSrc := []string{ - "cosmossdk.io/errors.Wrap\n", - "cosmossdk.io/errors.Wrapf\n", - "runtime.goexit\n", - } - const thisTestSrc = "errors/stacktrace_test.go" - - for _, tc := range cases { - s.Require().True(reflect.DeepEqual(tc.err.Error(), tc.wantError)) - s.Require().NotNil(stackTrace(tc.err)) - fullStack := fmt.Sprintf("%+v", tc.err) - s.Require().True(strings.Contains(fullStack, thisTestSrc)) - s.Require().True(strings.Contains(fullStack, tc.wantError)) - - for _, src := range unwantedSrc { - if strings.Contains(fullStack, src) { - s.T().Logf("Stack trace below\n----%s\n----", fullStack) - s.T().Logf("full stack contains unwanted source file path: %q", src) - } - } - - tinyStack := fmt.Sprintf("%v", tc.err) - s.Require().True(strings.HasPrefix(tinyStack, tc.wantError)) - s.Require().False(strings.Contains(tinyStack, "\n")) - // contains a link to where it was created, which must - // be here, not the Wrap() function - s.Require().True(strings.Contains(tinyStack, thisTestSrc)) - } -}