chore: errors main (#23707)

This commit is contained in:
Alex | Interchain Labs 2025-02-14 12:32:53 -05:00 committed by GitHub
parent eafa0e2c9d
commit a5661db02a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 95 additions and 845 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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" }

View File

@ -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

View File

@ -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
}

View File

@ -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 <nil>"))
}
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")
)

View File

@ -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

View File

@ -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=

View File

@ -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))
}
}

View File

@ -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
}
}
}

View File

@ -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))
}
}