stackitem: rework error handling

Return good named errors everywhere, export appropriate constants, make
errors.Is() work.
This commit is contained in:
Roman Khimov 2021-07-07 00:18:00 +03:00
parent 0de949b575
commit 5a9efcc654
5 changed files with 108 additions and 67 deletions

View file

@ -53,10 +53,28 @@ type Item interface {
} }
var ( var (
errInvalidConversion = errors.New("invalid conversion type") // ErrInvalidConversion is returned on attempt to make an incorrect
errExceedingMaxComparableSize = errors.New("the operand exceeds the maximum comparable size") // conversion between item types.
ErrInvalidConversion = errors.New("invalid conversion")
// ErrTooBig is returned when item exceeds some size constraints like
// maximum allowed integer value of number of elements in array. It
// can also be returned by serialization functions if resulting
// value exceeds MaxSize.
ErrTooBig = errors.New("too big")
errTooBigComparable = fmt.Errorf("%w: uncomparable", ErrTooBig)
errTooBigInteger = fmt.Errorf("%w: integer", ErrTooBig)
errTooBigKey = fmt.Errorf("%w: map key", ErrTooBig)
errTooBigSize = fmt.Errorf("%w: size", ErrTooBig)
) )
// mkInvConversion creates conversion error with additional metadata (from and
// to types).
func mkInvConversion(from Item, to Type) error {
return fmt.Errorf("%w: %s/%s", ErrInvalidConversion, from, to)
}
// Make tries to make appropriate stack item from provided value. // Make tries to make appropriate stack item from provided value.
// It will panic if it's not possible. // It will panic if it's not possible.
func Make(v interface{}) Item { func Make(v interface{}) Item {
@ -138,7 +156,7 @@ func ToString(item Item) (string, error) {
return "", err return "", err
} }
if !utf8.Valid(bs) { if !utf8.Valid(bs) {
return "", errors.New("not a valid UTF-8") return "", fmt.Errorf("%w: not UTF-8", ErrInvalidValue)
} }
return string(bs), nil return string(bs), nil
} }
@ -174,7 +192,7 @@ func convertPrimitive(item Item, typ Type) (Item, error) {
} }
return NewBool(b), nil return NewBool(b), nil
default: default:
return nil, errInvalidConversion return nil, mkInvConversion(item, typ)
} }
} }
@ -232,12 +250,12 @@ func (i *Struct) TryBool() (bool, error) { return true, nil }
// TryBytes implements Item interface. // TryBytes implements Item interface.
func (i *Struct) TryBytes() ([]byte, error) { func (i *Struct) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Struct to ByteString") return nil, mkInvConversion(i, ByteArrayT)
} }
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (i *Struct) TryInteger() (*big.Int, error) { func (i *Struct) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Struct to Integer") return nil, mkInvConversion(i, IntegerT)
} }
// Equals implements Item interface. // Equals implements Item interface.
@ -274,7 +292,7 @@ func (i *Struct) Convert(typ Type) (Item, error) {
case BooleanT: case BooleanT:
return NewBool(true), nil return NewBool(true), nil
default: default:
return nil, errInvalidConversion return nil, mkInvConversion(i, typ)
} }
} }
@ -318,12 +336,12 @@ func (i Null) TryBool() (bool, error) { return false, nil }
// TryBytes implements Item interface. // TryBytes implements Item interface.
func (i Null) TryBytes() ([]byte, error) { func (i Null) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Null to ByteString") return nil, mkInvConversion(i, ByteArrayT)
} }
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (i Null) TryInteger() (*big.Int, error) { func (i Null) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Null to Integer") return nil, mkInvConversion(i, IntegerT)
} }
// Equals implements Item interface. // Equals implements Item interface.
@ -338,7 +356,7 @@ func (i Null) Type() Type { return AnyT }
// Convert implements Item interface. // Convert implements Item interface.
func (i Null) Convert(typ Type) (Item, error) { func (i Null) Convert(typ Type) (Item, error) {
if typ == AnyT || !typ.IsValid() { if typ == AnyT || !typ.IsValid() {
return nil, errInvalidConversion return nil, mkInvConversion(i, typ)
} }
return i, nil return i, nil
} }
@ -350,18 +368,16 @@ type BigInteger struct {
// NewBigInteger returns an new BigInteger object. // NewBigInteger returns an new BigInteger object.
func NewBigInteger(value *big.Int) *BigInteger { func NewBigInteger(value *big.Int) *BigInteger {
const tooBigErrMsg = "integer is too big"
// There are 2 cases, when `BitLen` differs from actual size: // There are 2 cases, when `BitLen` differs from actual size:
// 1. Positive integer with highest bit on byte boundary = 1. // 1. Positive integer with highest bit on byte boundary = 1.
// 2. Negative integer with highest bit on byte boundary = 1 // 2. Negative integer with highest bit on byte boundary = 1
// minus some value. (-0x80 -> 0x80, -0x7F -> 0x81, -0x81 -> 0x7FFF). // minus some value. (-0x80 -> 0x80, -0x7F -> 0x81, -0x81 -> 0x7FFF).
sz := value.BitLen() sz := value.BitLen()
if sz > MaxBigIntegerSizeBits { if sz > MaxBigIntegerSizeBits {
panic(tooBigErrMsg) panic(errTooBigInteger)
} else if sz == MaxBigIntegerSizeBits { } else if sz == MaxBigIntegerSizeBits {
if value.Sign() == 1 || value.TrailingZeroBits() != MaxBigIntegerSizeBits-1 { if value.Sign() == 1 || value.TrailingZeroBits() != MaxBigIntegerSizeBits-1 {
panic(tooBigErrMsg) panic(errTooBigInteger)
} }
} }
return &BigInteger{ return &BigInteger{
@ -531,7 +547,7 @@ func (i *ByteArray) String() string {
// TryBool implements Item interface. // TryBool implements Item interface.
func (i *ByteArray) TryBool() (bool, error) { func (i *ByteArray) TryBool() (bool, error) {
if len(i.value) > MaxBigIntegerSizeBits/8 { if len(i.value) > MaxBigIntegerSizeBits/8 {
return false, errors.New("too big byte string") return false, errTooBigInteger
} }
for _, b := range i.value { for _, b := range i.value {
if b != 0 { if b != 0 {
@ -549,7 +565,7 @@ func (i *ByteArray) TryBytes() ([]byte, error) {
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (i *ByteArray) TryInteger() (*big.Int, error) { func (i *ByteArray) TryInteger() (*big.Int, error) {
if len(i.value) > MaxBigIntegerSizeBits/8 { if len(i.value) > MaxBigIntegerSizeBits/8 {
return nil, errors.New("integer is too big") return nil, errTooBigInteger
} }
return bigint.FromBytes(i.value), nil return bigint.FromBytes(i.value), nil
} }
@ -557,7 +573,7 @@ func (i *ByteArray) TryInteger() (*big.Int, error) {
// Equals implements Item interface. // Equals implements Item interface.
func (i *ByteArray) Equals(s Item) bool { func (i *ByteArray) Equals(s Item) bool {
if len(i.value) > MaxByteArrayComparableSize { if len(i.value) > MaxByteArrayComparableSize {
panic(errExceedingMaxComparableSize) panic(errTooBigComparable)
} }
if i == s { if i == s {
return true return true
@ -569,7 +585,7 @@ func (i *ByteArray) Equals(s Item) bool {
return false return false
} }
if len(val.value) > MaxByteArrayComparableSize { if len(val.value) > MaxByteArrayComparableSize {
panic(errExceedingMaxComparableSize) panic(errTooBigComparable)
} }
return bytes.Equal(i.value, val.value) return bytes.Equal(i.value, val.value)
} }
@ -641,12 +657,12 @@ func (i *Array) TryBool() (bool, error) { return true, nil }
// TryBytes implements Item interface. // TryBytes implements Item interface.
func (i *Array) TryBytes() ([]byte, error) { func (i *Array) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Array to ByteString") return nil, mkInvConversion(i, ByteArrayT)
} }
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (i *Array) TryInteger() (*big.Int, error) { func (i *Array) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Array to Integer") return nil, mkInvConversion(i, IntegerT)
} }
// Equals implements Item interface. // Equals implements Item interface.
@ -675,7 +691,7 @@ func (i *Array) Convert(typ Type) (Item, error) {
case BooleanT: case BooleanT:
return NewBool(true), nil return NewBool(true), nil
default: default:
return nil, errInvalidConversion return nil, mkInvConversion(i, typ)
} }
} }
@ -731,12 +747,12 @@ func (i *Map) TryBool() (bool, error) { return true, nil }
// TryBytes implements Item interface. // TryBytes implements Item interface.
func (i *Map) TryBytes() ([]byte, error) { func (i *Map) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Map to ByteString") return nil, mkInvConversion(i, ByteArrayT)
} }
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (i *Map) TryInteger() (*big.Int, error) { func (i *Map) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Map to Integer") return nil, mkInvConversion(i, IntegerT)
} }
// Equals implements Item interface. // Equals implements Item interface.
@ -780,7 +796,7 @@ func (i *Map) Convert(typ Type) (Item, error) {
case BooleanT: case BooleanT:
return NewBool(true), nil return NewBool(true), nil
default: default:
return nil, errInvalidConversion return nil, mkInvConversion(i, typ)
} }
} }
@ -812,11 +828,11 @@ func IsValidMapKey(key Item) error {
case *ByteArray: case *ByteArray:
size := len(key.Value().([]byte)) size := len(key.Value().([]byte))
if size > MaxKeySize { if size > MaxKeySize {
return fmt.Errorf("invalid map key size: %d", size) return errTooBigKey
} }
return nil return nil
default: default:
return fmt.Errorf("invalid map key of type %s", key.Type()) return fmt.Errorf("%w: %s map key", ErrInvalidType, key.Type())
} }
} }
@ -853,12 +869,12 @@ func (i *Interop) TryBool() (bool, error) { return true, nil }
// TryBytes implements Item interface. // TryBytes implements Item interface.
func (i *Interop) TryBytes() ([]byte, error) { func (i *Interop) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Interop to ByteString") return nil, mkInvConversion(i, ByteArrayT)
} }
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (i *Interop) TryInteger() (*big.Int, error) { func (i *Interop) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Interop to Integer") return nil, mkInvConversion(i, IntegerT)
} }
// Equals implements Item interface. // Equals implements Item interface.
@ -883,7 +899,7 @@ func (i *Interop) Convert(typ Type) (Item, error) {
case BooleanT: case BooleanT:
return NewBool(true), nil return NewBool(true), nil
default: default:
return nil, errInvalidConversion return nil, mkInvConversion(i, typ)
} }
} }
@ -946,12 +962,12 @@ func (p *Pointer) TryBool() (bool, error) {
// TryBytes implements Item interface. // TryBytes implements Item interface.
func (p *Pointer) TryBytes() ([]byte, error) { func (p *Pointer) TryBytes() ([]byte, error) {
return nil, errors.New("can't convert Pointer to ByteString") return nil, mkInvConversion(p, ByteArrayT)
} }
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (p *Pointer) TryInteger() (*big.Int, error) { func (p *Pointer) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Pointer to Integer") return nil, mkInvConversion(p, IntegerT)
} }
// Equals implements Item interface. // Equals implements Item interface.
@ -976,7 +992,7 @@ func (p *Pointer) Convert(typ Type) (Item, error) {
case BooleanT: case BooleanT:
return NewBool(true), nil return NewBool(true), nil
default: default:
return nil, errInvalidConversion return nil, mkInvConversion(p, typ)
} }
} }
@ -1024,7 +1040,7 @@ func (i *Buffer) TryBytes() ([]byte, error) {
// TryInteger implements Item interface. // TryInteger implements Item interface.
func (i *Buffer) TryInteger() (*big.Int, error) { func (i *Buffer) TryInteger() (*big.Int, error) {
return nil, errors.New("can't convert Buffer to Integer") return nil, mkInvConversion(i, IntegerT)
} }
// Equals implements Item interface. // Equals implements Item interface.
@ -1058,11 +1074,11 @@ func (i *Buffer) Convert(typ Type) (Item, error) {
return NewByteArray(val), nil return NewByteArray(val), nil
case IntegerT: case IntegerT:
if len(i.value) > MaxBigIntegerSizeBits/8 { if len(i.value) > MaxBigIntegerSizeBits/8 {
return nil, errInvalidConversion return nil, errTooBigInteger
} }
return NewBigInteger(bigint.FromBytes(i.value)), nil return NewBigInteger(bigint.FromBytes(i.value)), nil
default: default:
return nil, errInvalidConversion return nil, mkInvConversion(i, typ)
} }
} }

View file

@ -23,8 +23,16 @@ type decoder struct {
// MaxAllowedInteger is the maximum integer allowed to be encoded. // MaxAllowedInteger is the maximum integer allowed to be encoded.
const MaxAllowedInteger = 2<<53 - 1 const MaxAllowedInteger = 2<<53 - 1
// maxJSONDepth is a maximum allowed depth-level of decoded JSON. // MaxJSONDepth is the maximum allowed nesting level of encoded/decoded JSON.
const maxJSONDepth = 10 const MaxJSONDepth = 10
// ErrInvalidValue is returned when item value doesn't fit some constraints
// during serialization or deserialization.
var ErrInvalidValue = errors.New("invalid value")
// ErrTooDeep is returned when JSON encoder/decoder goes beyond MaxJSONDepth in
// its processing.
var ErrTooDeep = errors.New("too deep")
// ToJSON encodes Item to JSON. // ToJSON encodes Item to JSON.
// It behaves as following: // It behaves as following:
@ -48,7 +56,7 @@ func toJSON(buf *io.BufBinWriter, item Item) {
if w.Err != nil { if w.Err != nil {
return return
} else if buf.Len() > MaxSize { } else if buf.Len() > MaxSize {
w.Err = errors.New("item is too big") w.Err = errTooBigSize
} }
switch it := item.(type) { switch it := item.(type) {
case *Array, *Struct: case *Array, *Struct:
@ -76,7 +84,7 @@ func toJSON(buf *io.BufBinWriter, item Item) {
w.WriteB('}') w.WriteB('}')
case *BigInteger: case *BigInteger:
if it.value.CmpAbs(big.NewInt(MaxAllowedInteger)) == 1 { if it.value.CmpAbs(big.NewInt(MaxAllowedInteger)) == 1 {
w.Err = errors.New("too big integer") w.Err = fmt.Errorf("%w (MaxAllowedInteger)", ErrInvalidValue)
return return
} }
w.WriteBytes([]byte(it.value.String())) w.WriteBytes([]byte(it.value.String()))
@ -91,11 +99,11 @@ func toJSON(buf *io.BufBinWriter, item Item) {
case Null: case Null:
w.WriteBytes([]byte("null")) w.WriteBytes([]byte("null"))
default: default:
w.Err = fmt.Errorf("invalid item: %s", it.String()) w.Err = fmt.Errorf("%w: %s", ErrUnserializable, it.String())
return return
} }
if w.Err == nil && buf.Len() > MaxSize { if w.Err == nil && buf.Len() > MaxSize {
w.Err = errors.New("item is too big") w.Err = errTooBigSize
} }
} }
@ -131,7 +139,7 @@ func FromJSON(data []byte) (Item, error) {
if item, err := d.decode(); err != nil { if item, err := d.decode(); err != nil {
return nil, err return nil, err
} else if _, err := d.Token(); err != gio.EOF { } else if _, err := d.Token(); err != gio.EOF {
return nil, errors.New("unexpected items") return nil, fmt.Errorf("%w: unexpected items", ErrInvalidValue)
} else { } else {
return item, nil return item, nil
} }
@ -146,8 +154,8 @@ func (d *decoder) decode() (Item, error) {
case json.Delim: case json.Delim:
switch t { switch t {
case json.Delim('{'), json.Delim('['): case json.Delim('{'), json.Delim('['):
if d.depth == maxJSONDepth { if d.depth == MaxJSONDepth {
return nil, errors.New("JSON depth limit exceeded") return nil, ErrTooDeep
} }
d.depth++ d.depth++
var item Item var item Item
@ -167,7 +175,7 @@ func (d *decoder) decode() (Item, error) {
return NewByteArray([]byte(t)), nil return NewByteArray([]byte(t)), nil
case float64: case float64:
if math.Floor(t) != t { if math.Floor(t) != t {
return nil, fmt.Errorf("real value is not allowed: %v", t) return nil, fmt.Errorf("%w (real value for int)", ErrInvalidValue)
} }
return NewBigInteger(big.NewInt(int64(t))), nil return NewBigInteger(big.NewInt(int64(t))), nil
case bool: case bool:
@ -221,8 +229,8 @@ func ToJSONWithTypes(item Item) ([]byte, error) {
} }
func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) { func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) {
if len(seen) > maxJSONDepth { if len(seen) > MaxJSONDepth {
return "", errors.New("too deep structure") return "", ErrTooDeep
} }
typ := item.Type() typ := item.Type()
result := map[string]interface{}{ result := map[string]interface{}{
@ -232,7 +240,7 @@ func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) {
switch it := item.(type) { switch it := item.(type) {
case *Array, *Struct: case *Array, *Struct:
if seen[item] { if seen[item] {
return "", errors.New("recursive structures can't be serialized to json") return "", ErrRecursive
} }
seen[item] = true seen[item] = true
arr := []interface{}{} arr := []interface{}{}
@ -253,7 +261,7 @@ func toJSONWithTypes(item Item, seen map[Item]bool) (interface{}, error) {
value = it.value.String() value = it.value.String()
case *Map: case *Map:
if seen[item] { if seen[item] {
return "", errors.New("recursive structures can't be serialized to json") return "", ErrRecursive
} }
seen[item] = true seen[item] = true
arr := []interface{}{} arr := []interface{}{}
@ -292,6 +300,10 @@ type (
} }
) )
func mkErrValue(err error) error {
return fmt.Errorf("%w: %v", ErrInvalidValue, err)
}
// FromJSONWithTypes deserializes an item from typed-json representation. // FromJSONWithTypes deserializes an item from typed-json representation.
func FromJSONWithTypes(data []byte) (Item, error) { func FromJSONWithTypes(data []byte) (Item, error) {
raw := new(rawItem) raw := new(rawItem)
@ -300,7 +312,7 @@ func FromJSONWithTypes(data []byte) (Item, error) {
} }
typ, err := FromString(raw.Type) typ, err := FromString(raw.Type)
if err != nil { if err != nil {
return nil, errors.New("invalid type") return nil, fmt.Errorf("%w: %v", ErrInvalidType, raw.Type)
} }
switch typ { switch typ {
case AnyT: case AnyT:
@ -308,33 +320,33 @@ func FromJSONWithTypes(data []byte) (Item, error) {
case PointerT: case PointerT:
var pos int var pos int
if err := json.Unmarshal(raw.Value, &pos); err != nil { if err := json.Unmarshal(raw.Value, &pos); err != nil {
return nil, err return nil, mkErrValue(err)
} }
return NewPointer(pos, nil), nil return NewPointer(pos, nil), nil
case BooleanT: case BooleanT:
var b bool var b bool
if err := json.Unmarshal(raw.Value, &b); err != nil { if err := json.Unmarshal(raw.Value, &b); err != nil {
return nil, err return nil, mkErrValue(err)
} }
return NewBool(b), nil return NewBool(b), nil
case IntegerT: case IntegerT:
var s string var s string
if err := json.Unmarshal(raw.Value, &s); err != nil { if err := json.Unmarshal(raw.Value, &s); err != nil {
return nil, err return nil, mkErrValue(err)
} }
val, ok := new(big.Int).SetString(s, 10) val, ok := new(big.Int).SetString(s, 10)
if !ok { if !ok {
return nil, errors.New("invalid integer") return nil, mkErrValue(errors.New("not an integer"))
} }
return NewBigInteger(val), nil return NewBigInteger(val), nil
case ByteArrayT, BufferT: case ByteArrayT, BufferT:
var s string var s string
if err := json.Unmarshal(raw.Value, &s); err != nil { if err := json.Unmarshal(raw.Value, &s); err != nil {
return nil, err return nil, mkErrValue(err)
} }
val, err := base64.StdEncoding.DecodeString(s) val, err := base64.StdEncoding.DecodeString(s)
if err != nil { if err != nil {
return nil, err return nil, mkErrValue(err)
} }
if typ == ByteArrayT { if typ == ByteArrayT {
return NewByteArray(val), nil return NewByteArray(val), nil
@ -343,7 +355,7 @@ func FromJSONWithTypes(data []byte) (Item, error) {
case ArrayT, StructT: case ArrayT, StructT:
var arr []json.RawMessage var arr []json.RawMessage
if err := json.Unmarshal(raw.Value, &arr); err != nil { if err := json.Unmarshal(raw.Value, &arr); err != nil {
return nil, err return nil, mkErrValue(err)
} }
items := make([]Item, len(arr)) items := make([]Item, len(arr))
for i := range arr { for i := range arr {
@ -360,7 +372,7 @@ func FromJSONWithTypes(data []byte) (Item, error) {
case MapT: case MapT:
var arr []rawMapElement var arr []rawMapElement
if err := json.Unmarshal(raw.Value, &arr); err != nil { if err := json.Unmarshal(raw.Value, &arr); err != nil {
return nil, err return nil, mkErrValue(err)
} }
m := NewMap() m := NewMap()
for i := range arr { for i := range arr {
@ -380,6 +392,6 @@ func FromJSONWithTypes(data []byte) (Item, error) {
case InteropT: case InteropT:
return NewInterop(nil), nil return NewInterop(nil), nil
default: default:
return nil, errors.New("unexpected type") return nil, fmt.Errorf("%w: %v", ErrInvalidType, typ)
} }
} }

View file

@ -9,6 +9,14 @@ import (
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
) )
// ErrRecursive is returned on attempts to serialize some recursive stack item
// (like array including an item with reference to the same array).
var ErrRecursive = errors.New("recursive item")
// ErrUnserializable is returned on attempt to serialize some item that can't
// be serialized (like Interop item or Pointer).
var ErrUnserializable = errors.New("unserializable")
// serContext is an internal serialization context. // serContext is an internal serialization context.
type serContext struct { type serContext struct {
*io.BinWriter *io.BinWriter
@ -71,7 +79,7 @@ func (w *serContext) serialize(item Item) {
return return
} }
if w.seen[item] { if w.seen[item] {
w.Err = errors.New("recursive structures can't be serialized") w.Err = ErrRecursive
return return
} }
@ -92,7 +100,7 @@ func (w *serContext) serialize(item Item) {
if w.allowInvalid { if w.allowInvalid {
w.WriteBytes([]byte{byte(InteropT)}) w.WriteBytes([]byte{byte(InteropT)})
} else { } else {
w.Err = errors.New("interop item can't be serialized") w.Err = fmt.Errorf("%w: Interop", ErrUnserializable)
} }
case *Array, *Struct: case *Array, *Struct:
w.seen[item] = true w.seen[item] = true
@ -126,12 +134,12 @@ func (w *serContext) serialize(item Item) {
if w.allowInvalid { if w.allowInvalid {
w.WriteBytes([]byte{byte(InvalidT)}) w.WriteBytes([]byte{byte(InvalidT)})
} else { } else {
w.Err = errors.New("invalid stack item") w.Err = fmt.Errorf("%w: nil", ErrUnserializable)
} }
} }
if w.Err == nil && w.buf != nil && w.buf.Len() > MaxSize { if w.Err == nil && w.buf != nil && w.buf.Len() > MaxSize {
w.Err = errors.New("too big item") w.Err = errTooBigSize
} }
} }
@ -213,7 +221,7 @@ func decodeBinary(r *io.BinReader, allowInvalid bool) Item {
if t == InvalidT && allowInvalid { if t == InvalidT && allowInvalid {
return nil return nil
} }
r.Err = fmt.Errorf("unknown type: %v", t) r.Err = fmt.Errorf("%w: %v", ErrInvalidType, t)
return nil return nil
} }
} }

View file

@ -1,6 +1,7 @@
package stackitem package stackitem
import ( import (
"errors"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -10,12 +11,13 @@ func TestSerializationMaxErr(t *testing.T) {
base := make([]byte, MaxSize/2+1) base := make([]byte, MaxSize/2+1)
item := Make(base) item := Make(base)
arr := []Item{item, item.Dup()} // Pointer is unserializable, but we specifically want to catch ErrTooBig.
arr := []Item{item, item.Dup(), NewPointer(0, []byte{})}
aitem := Make(arr) aitem := Make(arr)
_, err := Serialize(item) _, err := Serialize(item)
require.NoError(t, err) require.NoError(t, err)
_, err = Serialize(aitem) _, err = Serialize(aitem)
require.Error(t, err) require.True(t, errors.Is(err, ErrTooBig), err)
} }

View file

@ -2,6 +2,9 @@ package stackitem
import "errors" import "errors"
// ErrInvalidType is returned on attempts to deserialize some unknown item type.
var ErrInvalidType = errors.New("invalid type")
// Type represents type of the stack item. // Type represents type of the stack item.
type Type byte type Type byte
@ -82,6 +85,6 @@ func FromString(s string) (Type, error) {
case "Interop": case "Interop":
return InteropT, nil return InteropT, nil
default: default:
return 0xFF, errors.New("invalid type") return 0xFF, ErrInvalidType
} }
} }