feat: standalone errors go.mod (#10779)
## Description This PR: * moves all of the `types/errors` code to a new `errors` go module, except: * the `RootCodespace` errors in `types/errors` stay there * ABCI stuff that depends on tendermint stays in `types/errors * adds aliases to everything in `types/errors` referencing `errors` so **this is not a breaking change** This will allow standalone go modules to use the same error types as the SDK. In particular, I want the `orm` to reference `errors` and then the SDK will be able to import `orm` and it can stay standalone. The same could apply to the `db` module. After this PR the plan is to: * tag `github.com/cosmos/cosmos-sdk/errors` as `v1.0` 🎉 * remove the `replace` directive for `errors` in the main SDK `go.mod` --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
b4fe191b85
commit
4d0d376c4b
|
@ -55,3 +55,5 @@ dependency-graph.png
|
||||||
*.aux
|
*.aux
|
||||||
*.out
|
*.out
|
||||||
*.synctex.gz
|
*.synctex.gz
|
||||||
|
/x/genutil/config/priv_validator_key.json
|
||||||
|
/x/genutil/data/priv_validator_state.json
|
||||||
|
|
|
@ -150,6 +150,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||||
* (types) [\#10630](https://github.com/cosmos/cosmos-sdk/pull/10630) Add an `Events` field to the `TxResponse` type that captures _all_ events emitted by a transaction, unlike `Logs` which only contains events emitted during message execution.
|
* (types) [\#10630](https://github.com/cosmos/cosmos-sdk/pull/10630) Add an `Events` field to the `TxResponse` type that captures _all_ events emitted by a transaction, unlike `Logs` which only contains events emitted during message execution.
|
||||||
* (deps) [\#10706](https://github.com/cosmos/cosmos-sdk/issues/10706) Bump rosetta-sdk-go to v0.7.2 and rosetta-cli to v0.7.3
|
* (deps) [\#10706](https://github.com/cosmos/cosmos-sdk/issues/10706) Bump rosetta-sdk-go to v0.7.2 and rosetta-cli to v0.7.3
|
||||||
* (module) [\#10711](https://github.com/cosmos/cosmos-sdk/pull/10711) Panic at startup if the app developer forgot to add modules in the `SetOrder{BeginBlocker, EndBlocker, InitGenesis, ExportGenesis}` functions. This means that all modules, even those who have empty implementations for those methods, need to be added to `SetOrder*`.
|
* (module) [\#10711](https://github.com/cosmos/cosmos-sdk/pull/10711) Panic at startup if the app developer forgot to add modules in the `SetOrder{BeginBlocker, EndBlocker, InitGenesis, ExportGenesis}` functions. This means that all modules, even those who have empty implementations for those methods, need to be added to `SetOrder*`.
|
||||||
|
* (types/errors) [\#10779](https://github.com/cosmos/cosmos-sdk/pull/10779) Move most functionality in `types/errors` to a standalone `errors` go module, except the `RootCodespace` errors and ABCI response helpers. All functions and types that used to live in `types/errors` are now aliased so this is not a breaking change.
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!--
|
||||||
|
Guiding Principles:
|
||||||
|
|
||||||
|
Changelogs are for humans, not machines.
|
||||||
|
There should be an entry for every single version.
|
||||||
|
The same types of changes should be grouped.
|
||||||
|
Versions and sections should be linkable.
|
||||||
|
The latest version comes first.
|
||||||
|
The release date of each version is displayed.
|
||||||
|
Mention whether you follow Semantic Versioning.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
Change log entries are to be added to the Unreleased section under the
|
||||||
|
appropriate stanza (see below). Each entry should ideally include a tag and
|
||||||
|
the Github issue reference in the following format:
|
||||||
|
|
||||||
|
* (<tag>) \#<issue-number> message
|
||||||
|
|
||||||
|
The issue numbers will later be link-ified during the release process so you do
|
||||||
|
not have to worry about including a link manually, but you can if you wish.
|
||||||
|
|
||||||
|
Types of changes (Stanzas):
|
||||||
|
|
||||||
|
"Features" for new features.
|
||||||
|
"Improvements" for changes in existing functionality.
|
||||||
|
"Deprecated" for soon-to-be removed features.
|
||||||
|
"Bug Fixes" for any bug fixes.
|
||||||
|
"Client Breaking" for breaking Protobuf, gRPC and REST routes used by end-users.
|
||||||
|
"CLI Breaking" for breaking CLI commands.
|
||||||
|
"API Breaking" for breaking exported APIs used by developers building on SDK.
|
||||||
|
Ref: https://keepachangelog.com/en/1.0.0/
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
### Features
|
||||||
|
* [\#10779](https://github.com/cosmos/cosmos-sdk/pull/10779) Import code from the `github.com/cosmos/cosmos-sdk/types/errors` package.
|
|
@ -0,0 +1,129 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SuccessABCICode declares an ABCI response use 0 to signal that the
|
||||||
|
// processing was successful and no error is returned.
|
||||||
|
SuccessABCICode uint32 = 0
|
||||||
|
|
||||||
|
// All unclassified errors that do not provide an ABCI code are clubbed
|
||||||
|
// under an internal error code and a generic message instead of
|
||||||
|
// detailed error string.
|
||||||
|
internalABCICodespace = UndefinedCodespace
|
||||||
|
internalABCICode uint32 = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// ABCIInfo returns the ABCI error information as consumed by the tendermint
|
||||||
|
// client. Returned codespace, code, and log message should be used as a ABCI response.
|
||||||
|
// Any error that does not provide ABCICode information is categorized as error
|
||||||
|
// with code 1, codespace UndefinedCodespace
|
||||||
|
// When not running in a debug mode all messages of errors that do not provide
|
||||||
|
// ABCICode information are replaced with generic "internal error". Errors
|
||||||
|
// without an ABCICode information as considered internal.
|
||||||
|
func ABCIInfo(err error, debug bool) (codespace string, code uint32, log string) {
|
||||||
|
if errIsNil(err) {
|
||||||
|
return "", SuccessABCICode, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
encode := defaultErrEncoder
|
||||||
|
if debug {
|
||||||
|
encode = debugErrEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
return abciCodespace(err), abciCode(err), encode(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The debugErrEncoder encodes the error with a stacktrace.
|
||||||
|
func debugErrEncoder(err error) string {
|
||||||
|
return fmt.Sprintf("%+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The defaultErrEncoder applies Redact on the error before encoding it with its internal error message.
|
||||||
|
func defaultErrEncoder(err error) string {
|
||||||
|
return Redact(err).Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
type coder interface {
|
||||||
|
ABCICode() uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// abciCode 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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if c, ok := err.(codespacer); ok {
|
||||||
|
return c.Codespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c, ok := err.(causer); ok {
|
||||||
|
err = c.Cause()
|
||||||
|
} else {
|
||||||
|
return internalABCICodespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errIsNil returns true if value represented by the given error is nil.
|
||||||
|
//
|
||||||
|
// Most of the time a simple == check is enough. There is a very narrowed
|
||||||
|
// spectrum of cases (mostly in tests) where a more sophisticated check is
|
||||||
|
// required.
|
||||||
|
func errIsNil(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if val := reflect.ValueOf(err); val.Kind() == reflect.Ptr {
|
||||||
|
return val.IsNil()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var errPanicWithMsg = Wrapf(ErrPanic, "panic message redacted to hide potentially sensitive system info")
|
||||||
|
|
||||||
|
// Redact replaces an error that is not initialized as an ABCI Error with a
|
||||||
|
// generic internal error instance. If the error is an ABCI Error, that error is
|
||||||
|
// simply returned.
|
||||||
|
func Redact(err error) error {
|
||||||
|
if ErrPanic.Is(err) {
|
||||||
|
return errPanicWithMsg
|
||||||
|
}
|
||||||
|
if abciCode(err) == internalABCICode {
|
||||||
|
return errInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -34,14 +34,14 @@ func (s *abciTestSuite) TestABCInfo() {
|
||||||
debug: false,
|
debug: false,
|
||||||
wantLog: "unauthorized",
|
wantLog: "unauthorized",
|
||||||
wantCode: ErrUnauthorized.code,
|
wantCode: ErrUnauthorized.code,
|
||||||
wantSpace: RootCodespace,
|
wantSpace: testCodespace,
|
||||||
},
|
},
|
||||||
"wrapped SDK error": {
|
"wrapped SDK error": {
|
||||||
err: Wrap(Wrap(ErrUnauthorized, "foo"), "bar"),
|
err: Wrap(Wrap(ErrUnauthorized, "foo"), "bar"),
|
||||||
debug: false,
|
debug: false,
|
||||||
wantLog: "bar: foo: unauthorized",
|
wantLog: "bar: foo: unauthorized",
|
||||||
wantCode: ErrUnauthorized.code,
|
wantCode: ErrUnauthorized.code,
|
||||||
wantSpace: RootCodespace,
|
wantSpace: testCodespace,
|
||||||
},
|
},
|
||||||
"nil is empty message": {
|
"nil is empty message": {
|
||||||
err: nil,
|
err: nil,
|
|
@ -0,0 +1,266 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// ErrPanic is only set when we recover from a panic, so we know to
|
||||||
|
// redact potentially sensitive system info
|
||||||
|
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register returns an error instance that should be used as the base for
|
||||||
|
// creating error instances during runtime.
|
||||||
|
//
|
||||||
|
// Popular root errors are declared in this package, but extensions may want to
|
||||||
|
// declare custom codes. This function ensures that no error code is used
|
||||||
|
// twice. Attempt to reuse an error code results in panic.
|
||||||
|
//
|
||||||
|
// Use this function only during a program startup phase.
|
||||||
|
func Register(codespace string, code uint32, 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 := New(codespace, code, description)
|
||||||
|
setUsed(err)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// usedCodes is keeping track of used codes to ensure their uniqueness. No two
|
||||||
|
// error instances should share the same (codespace, code) tuple.
|
||||||
|
var usedCodes = map[string]*Error{}
|
||||||
|
|
||||||
|
func errorID(codespace string, code uint32) string {
|
||||||
|
return fmt.Sprintf("%s:%d", codespace, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUsed(codespace string, code uint32) *Error {
|
||||||
|
return usedCodes[errorID(codespace, code)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUsed(err *Error) {
|
||||||
|
usedCodes[errorID(err.codespace, err.code)] = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ABCIError will resolve an error code/log from an abci result into
|
||||||
|
// an error message. If the code is registered, it will map it back to
|
||||||
|
// the canonical error, so we can do eg. ErrNotFound.Is(err) on something
|
||||||
|
// we get back from an external API.
|
||||||
|
//
|
||||||
|
// This should *only* be used in clients, not in the server side.
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
// This is a unique error, will never match on .Is()
|
||||||
|
// Use Wrap here to get a stack trace
|
||||||
|
return Wrap(New(codespace, code, "unknown"), log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error represents a root error.
|
||||||
|
//
|
||||||
|
// Weave framework is using root error to categorize issues. Each instance
|
||||||
|
// created during the runtime should wrap one of the declared root errors. This
|
||||||
|
// allows error tests and returning all errors to the client in a safe manner.
|
||||||
|
//
|
||||||
|
// All popular root errors are declared in this package. If an extension has to
|
||||||
|
// declare a custom root error, always use Register function to ensure
|
||||||
|
// error code uniqueness.
|
||||||
|
type Error struct {
|
||||||
|
codespace string
|
||||||
|
code uint32
|
||||||
|
desc string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(codespace string, code uint32, desc string) *Error {
|
||||||
|
return &Error{codespace: codespace, code: code, desc: desc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return e.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) ABCICode() uint32 {
|
||||||
|
return e.code
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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.
|
||||||
|
//
|
||||||
|
// If the wrapped error does not provide ABCICode method (ie. 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
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf extends given error with an additional information.
|
||||||
|
//
|
||||||
|
// This function works like Wrap function with additional functionality of
|
||||||
|
// formatting the input as specified.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -103,46 +103,6 @@ func (s *errorsTestSuite) TestErrorIs() {
|
||||||
b: nil,
|
b: nil,
|
||||||
wantIs: false,
|
wantIs: false,
|
||||||
},
|
},
|
||||||
// "multierr with the same error": {
|
|
||||||
// a: ErrUnauthorized,
|
|
||||||
// b: Append(ErrUnauthorized, ErrState),
|
|
||||||
// wantIs: true,
|
|
||||||
// },
|
|
||||||
// "multierr with random order": {
|
|
||||||
// a: ErrUnauthorized,
|
|
||||||
// b: Append(ErrState, ErrUnauthorized),
|
|
||||||
// wantIs: true,
|
|
||||||
// },
|
|
||||||
// "multierr with wrapped err": {
|
|
||||||
// a: ErrUnauthorized,
|
|
||||||
// b: Append(ErrState, Wrap(ErrUnauthorized, "test")),
|
|
||||||
// wantIs: true,
|
|
||||||
// },
|
|
||||||
// "multierr with nil error": {
|
|
||||||
// a: ErrUnauthorized,
|
|
||||||
// b: Append(nil, nil),
|
|
||||||
// wantIs: false,
|
|
||||||
// },
|
|
||||||
// "multierr with different error": {
|
|
||||||
// a: ErrUnauthorized,
|
|
||||||
// b: Append(ErrState, nil),
|
|
||||||
// wantIs: false,
|
|
||||||
// },
|
|
||||||
// "multierr from nil": {
|
|
||||||
// a: nil,
|
|
||||||
// b: Append(ErrState, ErrUnauthorized),
|
|
||||||
// wantIs: false,
|
|
||||||
// },
|
|
||||||
// "field error wrapper": {
|
|
||||||
// a: ErrEmpty,
|
|
||||||
// b: Field("name", ErrEmpty, "name is required"),
|
|
||||||
// wantIs: true,
|
|
||||||
// },
|
|
||||||
// "nil field error wrapper": {
|
|
||||||
// a: nil,
|
|
||||||
// b: Field("name", nil, "name is required"),
|
|
||||||
// wantIs: true,
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
for testName, tc := range cases {
|
for testName, tc := range cases {
|
||||||
s.Require().Equal(tc.wantIs, tc.a.Is(tc.b), testName)
|
s.Require().Equal(tc.wantIs, tc.a.Is(tc.b), testName)
|
||||||
|
@ -242,7 +202,7 @@ func (s *errorsTestSuite) TestWrappedUnwrapFail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *errorsTestSuite) TestABCIError() {
|
func (s *errorsTestSuite) TestABCIError() {
|
||||||
s.Require().Equal("custom: tx parse error", ABCIError(RootCodespace, 2, "custom").Error())
|
s.Require().Equal("custom: tx parse error", ABCIError(testCodespace, 2, "custom").Error())
|
||||||
s.Require().Equal("custom: unknown", ABCIError("unknown", 1, "custom").Error())
|
s.Require().Equal("custom: unknown", ABCIError("unknown", 1, "custom").Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,3 +225,37 @@ func ExampleWrapf() {
|
||||||
// 90 is smaller than 100: insufficient funds
|
// 90 is smaller than 100: insufficient funds
|
||||||
// 90 is smaller than 100: insufficient funds
|
// 90 is smaller than 100: insufficient funds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testCodespace = "testtesttest"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTxDecode = Register(testCodespace, 2, "tx parse error")
|
||||||
|
ErrInvalidSequence = Register(testCodespace, 3, "invalid sequence")
|
||||||
|
ErrUnauthorized = Register(testCodespace, 4, "unauthorized")
|
||||||
|
ErrInsufficientFunds = Register(testCodespace, 5, "insufficient funds")
|
||||||
|
ErrUnknownRequest = Register(testCodespace, 6, "unknown request")
|
||||||
|
ErrInvalidAddress = Register(testCodespace, 7, "invalid address")
|
||||||
|
ErrInvalidPubKey = Register(testCodespace, 8, "invalid pubkey")
|
||||||
|
ErrUnknownAddress = Register(testCodespace, 9, "unknown address")
|
||||||
|
ErrInvalidCoins = Register(testCodespace, 10, "invalid coins")
|
||||||
|
ErrOutOfGas = Register(testCodespace, 11, "out of gas")
|
||||||
|
ErrInsufficientFee = Register(testCodespace, 13, "insufficient fee")
|
||||||
|
ErrTooManySignatures = Register(testCodespace, 14, "maximum number of signatures exceeded")
|
||||||
|
ErrNoSignatures = Register(testCodespace, 15, "no signatures supplied")
|
||||||
|
ErrJSONMarshal = Register(testCodespace, 16, "failed to marshal JSON bytes")
|
||||||
|
ErrJSONUnmarshal = Register(testCodespace, 17, "failed to unmarshal JSON bytes")
|
||||||
|
ErrInvalidRequest = Register(testCodespace, 18, "invalid request")
|
||||||
|
ErrMempoolIsFull = Register(testCodespace, 20, "mempool is full")
|
||||||
|
ErrTxTooLarge = Register(testCodespace, 21, "tx too large")
|
||||||
|
ErrKeyNotFound = Register(testCodespace, 22, "key not found")
|
||||||
|
ErrorInvalidSigner = Register(testCodespace, 24, "tx intended signer does not match the given signer")
|
||||||
|
ErrInvalidChainID = Register(testCodespace, 28, "invalid chain-id")
|
||||||
|
ErrInvalidType = Register(testCodespace, 29, "invalid type")
|
||||||
|
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 = Register(testCodespace, 36, "conflict")
|
||||||
|
ErrNotSupported = Register(testCodespace, 37, "feature not supported")
|
||||||
|
ErrNotFound = Register(testCodespace, 38, "not found")
|
||||||
|
ErrIO = Register(testCodespace, 39, "Internal IO error")
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
module errors
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -73,7 +73,7 @@ func writeSimpleFrame(s io.Writer, f errors.Frame) {
|
||||||
if len(chunks) == 2 {
|
if len(chunks) == 2 {
|
||||||
file = chunks[1]
|
file = chunks[1]
|
||||||
}
|
}
|
||||||
fmt.Fprintf(s, " [%s:%d]", file, line)
|
_, _ = fmt.Fprintf(s, " [%s:%d]", file, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format works like pkg/errors, with additions.
|
// Format works like pkg/errors, with additions.
|
||||||
|
@ -86,16 +86,16 @@ func writeSimpleFrame(s io.Writer, f errors.Frame) {
|
||||||
func (e *wrappedError) Format(s fmt.State, verb rune) {
|
func (e *wrappedError) Format(s fmt.State, verb rune) {
|
||||||
// normal output here....
|
// normal output here....
|
||||||
if verb != 'v' {
|
if verb != 'v' {
|
||||||
fmt.Fprint(s, e.Error())
|
_, _ = fmt.Fprint(s, e.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// work with the stack trace... whole or part
|
// work with the stack trace... whole or part
|
||||||
stack := trimInternal(stackTrace(e))
|
stack := trimInternal(stackTrace(e))
|
||||||
if s.Flag('+') {
|
if s.Flag('+') {
|
||||||
fmt.Fprintf(s, "%+v\n", stack)
|
_, _ = fmt.Fprintf(s, "%+v\n", stack)
|
||||||
fmt.Fprint(s, e.Error())
|
_, _ = fmt.Fprint(s, e.Error())
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprint(s, e.Error())
|
_, _ = fmt.Fprint(s, e.Error())
|
||||||
writeSimpleFrame(s, stack[0])
|
writeSimpleFrame(s, stack[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
3
go.mod
3
go.mod
|
@ -12,6 +12,7 @@ require (
|
||||||
github.com/cosmos/btcutil v1.0.4
|
github.com/cosmos/btcutil v1.0.4
|
||||||
github.com/cosmos/cosmos-proto v0.0.0-20211123144845-528f5002c9f8
|
github.com/cosmos/cosmos-proto v0.0.0-20211123144845-528f5002c9f8
|
||||||
github.com/cosmos/cosmos-sdk/db v0.0.0
|
github.com/cosmos/cosmos-sdk/db v0.0.0
|
||||||
|
github.com/cosmos/cosmos-sdk/errors v0.0.0
|
||||||
github.com/cosmos/cosmos-sdk/x/group v0.0.0-00010101000000-000000000000
|
github.com/cosmos/cosmos-sdk/x/group v0.0.0-00010101000000-000000000000
|
||||||
github.com/cosmos/go-bip39 v1.0.0
|
github.com/cosmos/go-bip39 v1.0.0
|
||||||
github.com/cosmos/iavl v0.17.3
|
github.com/cosmos/iavl v0.17.3
|
||||||
|
@ -156,3 +157,5 @@ replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0
|
||||||
replace github.com/cosmos/cosmos-sdk/db => ./db
|
replace github.com/cosmos/cosmos-sdk/db => ./db
|
||||||
|
|
||||||
replace github.com/cosmos/cosmos-sdk/x/group => ./x/group
|
replace github.com/cosmos/cosmos-sdk/x/group => ./x/group
|
||||||
|
|
||||||
|
replace github.com/cosmos/cosmos-sdk/errors => ./errors
|
||||||
|
|
|
@ -1,44 +1,9 @@
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// SuccessABCICode declares an ABCI response use 0 to signal that the
|
|
||||||
// processing was successful and no error is returned.
|
|
||||||
SuccessABCICode uint32 = 0
|
|
||||||
|
|
||||||
// All unclassified errors that do not provide an ABCI code are clubbed
|
|
||||||
// under an internal error code and a generic message instead of
|
|
||||||
// detailed error string.
|
|
||||||
internalABCICodespace = UndefinedCodespace
|
|
||||||
internalABCICode uint32 = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// ABCIInfo returns the ABCI error information as consumed by the tendermint
|
|
||||||
// client. Returned codespace, code, and log message should be used as a ABCI response.
|
|
||||||
// Any error that does not provide ABCICode information is categorized as error
|
|
||||||
// with code 1, codespace UndefinedCodespace
|
|
||||||
// When not running in a debug mode all messages of errors that do not provide
|
|
||||||
// ABCICode information are replaced with generic "internal error". Errors
|
|
||||||
// without an ABCICode information as considered internal.
|
|
||||||
func ABCIInfo(err error, debug bool) (codespace string, code uint32, log string) {
|
|
||||||
if errIsNil(err) {
|
|
||||||
return "", SuccessABCICode, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
encode := defaultErrEncoder
|
|
||||||
if debug {
|
|
||||||
encode = debugErrEncoder
|
|
||||||
}
|
|
||||||
|
|
||||||
return abciCodespace(err), abciCode(err), encode(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseCheckTx returns an ABCI ResponseCheckTx object with fields filled in
|
// ResponseCheckTx returns an ABCI ResponseCheckTx object with fields filled in
|
||||||
// from the given error and gas values.
|
// from the given error and gas values.
|
||||||
func ResponseCheckTx(err error, gw, gu uint64, debug bool) abci.ResponseCheckTx {
|
func ResponseCheckTx(err error, gw, gu uint64, debug bool) abci.ResponseCheckTx {
|
||||||
|
@ -75,94 +40,3 @@ func QueryResult(err error, debug bool) abci.ResponseQuery {
|
||||||
Log: log,
|
Log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The debugErrEncoder encodes the error with a stacktrace.
|
|
||||||
func debugErrEncoder(err error) string {
|
|
||||||
return fmt.Sprintf("%+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The defaultErrEncoder applies Redact on the error before encoding it with its internal error message.
|
|
||||||
func defaultErrEncoder(err error) string {
|
|
||||||
return Redact(err).Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
type coder interface {
|
|
||||||
ABCICode() uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// abciCode test 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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ""
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if c, ok := err.(codespacer); ok {
|
|
||||||
return c.Codespace()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c, ok := err.(causer); ok {
|
|
||||||
err = c.Cause()
|
|
||||||
} else {
|
|
||||||
return internalABCICodespace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// errIsNil returns true if value represented by the given error is nil.
|
|
||||||
//
|
|
||||||
// Most of the time a simple == check is enough. There is a very narrowed
|
|
||||||
// spectrum of cases (mostly in tests) where a more sophisticated check is
|
|
||||||
// required.
|
|
||||||
func errIsNil(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if val := reflect.ValueOf(err); val.Kind() == reflect.Ptr {
|
|
||||||
return val.IsNil()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var errPanicWithMsg = Wrapf(ErrPanic, "panic message redacted to hide potentially sensitive system info")
|
|
||||||
|
|
||||||
// Redact replaces an error that is not initialized as an ABCI Error with a
|
|
||||||
// generic internal error instance. If the error is an ABCI Error, that error is
|
|
||||||
// simply returned.
|
|
||||||
func Redact(err error) error {
|
|
||||||
if ErrPanic.Is(err) {
|
|
||||||
return errPanicWithMsg
|
|
||||||
}
|
|
||||||
if abciCode(err) == internalABCICode {
|
|
||||||
return errInternal
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,22 +1,41 @@
|
||||||
|
//Package errors provides a shared set of errors for use in the SDK,
|
||||||
|
//aliases functionality in the github.com/cosmos/cosmos-sdk/errors module
|
||||||
|
//that used to be in this package, and provides some helpers for converting
|
||||||
|
//errors to ABCI response code.
|
||||||
|
//
|
||||||
|
//New code should generally import github.com/cosmos/cosmos-sdk/errors directly
|
||||||
|
//and define a custom set of errors in custom codespace, rather than importing
|
||||||
|
//this package.
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
errorsmod "github.com/cosmos/cosmos-sdk/errors"
|
||||||
"reflect"
|
)
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
var (
|
||||||
|
SuccessABCICode = errorsmod.SuccessABCICode
|
||||||
|
ABCIInfo = errorsmod.ABCIInfo
|
||||||
|
Redact = errorsmod.Redact
|
||||||
|
UndefinedCodespace = errorsmod.UndefinedCodespace
|
||||||
|
Register = errorsmod.Register
|
||||||
|
ABCIError = errorsmod.ABCIError
|
||||||
|
New = errorsmod.New
|
||||||
|
Wrap = errorsmod.Wrap
|
||||||
|
Wrapf = errorsmod.Wrapf
|
||||||
|
Recover = errorsmod.Recover
|
||||||
|
WithType = errorsmod.WithType
|
||||||
|
IsOf = errorsmod.IsOf
|
||||||
|
AssertNil = errorsmod.AssertNil
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Error = errorsmod.Error
|
||||||
)
|
)
|
||||||
|
|
||||||
// RootCodespace is the codespace for all errors defined in this package
|
// RootCodespace is the codespace for all errors defined in this package
|
||||||
const RootCodespace = "sdk"
|
const RootCodespace = "sdk"
|
||||||
|
|
||||||
// UndefinedCodespace when we explicitly declare no codespace
|
|
||||||
const UndefinedCodespace = "undefined"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// errInternal should never be exposed, but we reserve this code for non-specified errors
|
|
||||||
errInternal = Register(UndefinedCodespace, 1, "internal")
|
|
||||||
|
|
||||||
// ErrTxDecode is returned if we cannot parse a transaction
|
// ErrTxDecode is returned if we cannot parse a transaction
|
||||||
ErrTxDecode = Register(RootCodespace, 2, "tx parse error")
|
ErrTxDecode = Register(RootCodespace, 2, "tx parse error")
|
||||||
|
|
||||||
|
@ -143,254 +162,8 @@ var (
|
||||||
|
|
||||||
// ErrPanic is only set when we recover from a panic, so we know to
|
// ErrPanic is only set when we recover from a panic, so we know to
|
||||||
// redact potentially sensitive system info
|
// redact potentially sensitive system info
|
||||||
ErrPanic = Register(UndefinedCodespace, 111222, "panic")
|
ErrPanic = errorsmod.ErrPanic
|
||||||
|
|
||||||
// ErrAppConfig defines an error occurred if min-gas-prices field in BaseConfig is empty.
|
// ErrAppConfig defines an error occurred if min-gas-prices field in BaseConfig is empty.
|
||||||
ErrAppConfig = Register(RootCodespace, 40, "error in app.toml")
|
ErrAppConfig = Register(RootCodespace, 40, "error in app.toml")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Register returns an error instance that should be used as the base for
|
|
||||||
// creating error instances during runtime.
|
|
||||||
//
|
|
||||||
// Popular root errors are declared in this package, but extensions may want to
|
|
||||||
// declare custom codes. This function ensures that no error code is used
|
|
||||||
// twice. Attempt to reuse an error code results in panic.
|
|
||||||
//
|
|
||||||
// Use this function only during a program startup phase.
|
|
||||||
func Register(codespace string, code uint32, 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 := New(codespace, code, description)
|
|
||||||
setUsed(err)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// usedCodes is keeping track of used codes to ensure their uniqueness. No two
|
|
||||||
// error instances should share the same (codespace, code) tuple.
|
|
||||||
var usedCodes = map[string]*Error{}
|
|
||||||
|
|
||||||
func errorID(codespace string, code uint32) string {
|
|
||||||
return fmt.Sprintf("%s:%d", codespace, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUsed(codespace string, code uint32) *Error {
|
|
||||||
return usedCodes[errorID(codespace, code)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func setUsed(err *Error) {
|
|
||||||
usedCodes[errorID(err.codespace, err.code)] = err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ABCIError will resolve an error code/log from an abci result into
|
|
||||||
// an error message. If the code is registered, it will map it back to
|
|
||||||
// the canonical error, so we can do eg. ErrNotFound.Is(err) on something
|
|
||||||
// we get back from an external API.
|
|
||||||
//
|
|
||||||
// This should *only* be used in clients, not in the server side.
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
// This is a unique error, will never match on .Is()
|
|
||||||
// Use Wrap here to get a stack trace
|
|
||||||
return Wrap(New(codespace, code, "unknown"), log)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error represents a root error.
|
|
||||||
//
|
|
||||||
// Weave framework is using root error to categorize issues. Each instance
|
|
||||||
// created during the runtime should wrap one of the declared root errors. This
|
|
||||||
// allows error tests and returning all errors to the client in a safe manner.
|
|
||||||
//
|
|
||||||
// All popular root errors are declared in this package. If an extension has to
|
|
||||||
// declare a custom root error, always use Register function to ensure
|
|
||||||
// error code uniqueness.
|
|
||||||
type Error struct {
|
|
||||||
codespace string
|
|
||||||
code uint32
|
|
||||||
desc string
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(codespace string, code uint32, desc string) *Error {
|
|
||||||
return &Error{codespace: codespace, code: code, desc: desc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) Error() string {
|
|
||||||
return e.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) ABCICode() uint32 {
|
|
||||||
return e.code
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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.
|
|
||||||
//
|
|
||||||
// If the wrapped error does not provide ABCICode method (ie. 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
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapf extends given error with an additional information.
|
|
||||||
//
|
|
||||||
// This function works like Wrap function with additional functionality of
|
|
||||||
// formatting the input as specified.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ require (
|
||||||
github.com/confio/ics23/go v0.6.6 // indirect
|
github.com/confio/ics23/go v0.6.6 // indirect
|
||||||
github.com/cosmos/btcutil v1.0.4 // indirect
|
github.com/cosmos/btcutil v1.0.4 // indirect
|
||||||
github.com/cosmos/cosmos-sdk/db v0.0.0 // indirect
|
github.com/cosmos/cosmos-sdk/db v0.0.0 // indirect
|
||||||
|
github.com/cosmos/cosmos-sdk/errors v0.0.0 // indirect
|
||||||
github.com/cosmos/go-bip39 v1.0.0 // indirect
|
github.com/cosmos/go-bip39 v1.0.0 // indirect
|
||||||
github.com/cosmos/iavl v0.17.3 // indirect
|
github.com/cosmos/iavl v0.17.3 // indirect
|
||||||
github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect
|
github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect
|
||||||
|
@ -150,3 +151,5 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp
|
||||||
replace github.com/cosmos/cosmos-sdk => ../../
|
replace github.com/cosmos/cosmos-sdk => ../../
|
||||||
|
|
||||||
replace github.com/cosmos/cosmos-sdk/db => ../../db
|
replace github.com/cosmos/cosmos-sdk/db => ../../db
|
||||||
|
|
||||||
|
replace github.com/cosmos/cosmos-sdk/errors => ../../errors
|
||||||
|
|
Loading…
Reference in New Issue