Skip to content

Commit

Permalink
feat: prep for errors v2 (#20539)
Browse files Browse the repository at this point in the history
Co-authored-by: unknown unknown <unknown@unknown>
  • Loading branch information
tac0turtle and unknown unknown authored Jun 11, 2024
1 parent 76a28ad commit 62aa118
Show file tree
Hide file tree
Showing 10 changed files with 42 additions and 730 deletions.
1 change: 1 addition & 0 deletions errors/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking

* [#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) v2 errors 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)

Expand Down
55 changes: 14 additions & 41 deletions errors/abci.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package errors

import (
"errors"
"fmt"
"reflect"
)
Expand Down Expand Up @@ -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.
Expand All @@ -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
}

for {
if c, ok := err.(coder); ok {
return c.ABCICode()
}

if c, ok := err.(causer); ok {
err = c.Cause()
} else {
return internalABCICode
}
return SuccessABCICode, ""
}
}

type codespacer interface {
Codespace() string
}
var customErr *Error

// 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.
Expand Down
123 changes: 18 additions & 105 deletions errors/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
)

Expand Down Expand Up @@ -183,16 +104,8 @@ 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" }
Loading

0 comments on commit 62aa118

Please sign in to comment.