Merge pull request #14686 from fjl/hexutil-json-type-error

common/hexutil: wrap errors in json.UnmarshalTypeError
pull/14718/head
Péter Szilágyi 7 years ago committed by GitHub
commit bb366271fe
  1. 25
      common/hexutil/hexutil.go
  2. 47
      common/hexutil/json.go
  3. 64
      common/hexutil/json_test.go
  4. 15
      common/types.go
  5. 10
      common/types_test.go

@ -32,7 +32,6 @@ package hexutil
import ( import (
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"strconv" "strconv"
@ -41,17 +40,23 @@ import (
const uintBits = 32 << (uint64(^uint(0)) >> 63) const uintBits = 32 << (uint64(^uint(0)) >> 63)
var ( var (
ErrEmptyString = errors.New("empty hex string") ErrEmptyString = &decError{"empty hex string"}
ErrMissingPrefix = errors.New("missing 0x prefix for hex data") ErrSyntax = &decError{"invalid hex string"}
ErrSyntax = errors.New("invalid hex") ErrMissingPrefix = &decError{"hex string without 0x prefix"}
ErrEmptyNumber = errors.New("hex number has no digits after 0x") ErrOddLength = &decError{"hex string of odd length"}
ErrLeadingZero = errors.New("hex number has leading zero digits after 0x") ErrEmptyNumber = &decError{"hex string \"0x\""}
ErrOddLength = errors.New("hex string has odd length") ErrLeadingZero = &decError{"hex number with leading zero digits"}
ErrUint64Range = errors.New("hex number does not fit into 64 bits") ErrUint64Range = &decError{"hex number > 64 bits"}
ErrUintRange = fmt.Errorf("hex number does not fit into %d bits", uintBits) ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)}
ErrBig256Range = errors.New("hex number does not fit into 256 bits") ErrBig256Range = &decError{"hex number > 256 bits"}
) )
type decError struct{ msg string }
func (err decError) Error() string {
return string(err.msg)
}
// Decode decodes a hex string with 0x prefix. // Decode decodes a hex string with 0x prefix.
func Decode(input string) ([]byte, error) { func Decode(input string) ([]byte, error) {
if len(input) == 0 { if len(input) == 0 {

@ -18,15 +18,19 @@ package hexutil
import ( import (
"encoding/hex" "encoding/hex"
"errors" "encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"reflect"
"strconv" "strconv"
) )
var ( var (
textZero = []byte(`0x0`) textZero = []byte(`0x0`)
errNonString = errors.New("cannot unmarshal non-string as hex data") bytesT = reflect.TypeOf(Bytes(nil))
bigT = reflect.TypeOf((*Big)(nil))
uintT = reflect.TypeOf(Uint(0))
uint64T = reflect.TypeOf(Uint64(0))
) )
// Bytes marshals/unmarshals as a JSON string with 0x prefix. // Bytes marshals/unmarshals as a JSON string with 0x prefix.
@ -44,9 +48,9 @@ func (b Bytes) MarshalText() ([]byte, error) {
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (b *Bytes) UnmarshalJSON(input []byte) error { func (b *Bytes) UnmarshalJSON(input []byte) error {
if !isString(input) { if !isString(input) {
return errNonString return errNonString(bytesT)
} }
return b.UnmarshalText(input[1 : len(input)-1]) return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT)
} }
// UnmarshalText implements encoding.TextUnmarshaler. // UnmarshalText implements encoding.TextUnmarshaler.
@ -69,6 +73,16 @@ func (b Bytes) String() string {
return Encode(b) return Encode(b)
} }
// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the
// UnmarshalJSON method for fixed-size types.
func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error {
if !isString(input) {
return errNonString(typ)
}
return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ)
}
// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out // UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out
// determines the required input length. This function is commonly used to implement the // determines the required input length. This function is commonly used to implement the
// UnmarshalText method for fixed-size types. // UnmarshalText method for fixed-size types.
@ -127,9 +141,9 @@ func (b Big) MarshalText() ([]byte, error) {
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (b *Big) UnmarshalJSON(input []byte) error { func (b *Big) UnmarshalJSON(input []byte) error {
if !isString(input) { if !isString(input) {
return errNonString return errNonString(bigT)
} }
return b.UnmarshalText(input[1 : len(input)-1]) return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bigT)
} }
// UnmarshalText implements encoding.TextUnmarshaler // UnmarshalText implements encoding.TextUnmarshaler
@ -189,9 +203,9 @@ func (b Uint64) MarshalText() ([]byte, error) {
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (b *Uint64) UnmarshalJSON(input []byte) error { func (b *Uint64) UnmarshalJSON(input []byte) error {
if !isString(input) { if !isString(input) {
return errNonString return errNonString(uint64T)
} }
return b.UnmarshalText(input[1 : len(input)-1]) return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint64T)
} }
// UnmarshalText implements encoding.TextUnmarshaler // UnmarshalText implements encoding.TextUnmarshaler
@ -233,9 +247,9 @@ func (b Uint) MarshalText() ([]byte, error) {
// UnmarshalJSON implements json.Unmarshaler. // UnmarshalJSON implements json.Unmarshaler.
func (b *Uint) UnmarshalJSON(input []byte) error { func (b *Uint) UnmarshalJSON(input []byte) error {
if !isString(input) { if !isString(input) {
return errNonString return errNonString(uintT)
} }
return b.UnmarshalText(input[1 : len(input)-1]) return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uintT)
} }
// UnmarshalText implements encoding.TextUnmarshaler. // UnmarshalText implements encoding.TextUnmarshaler.
@ -295,3 +309,14 @@ func checkNumberText(input []byte) (raw []byte, err error) {
} }
return input, nil return input, nil
} }
func wrapTypeError(err error, typ reflect.Type) error {
if _, ok := err.(*decError); ok {
return &json.UnmarshalTypeError{Value: err.Error(), Type: typ}
}
return err
}
func errNonString(typ reflect.Type) error {
return &json.UnmarshalTypeError{Value: "non-string", Type: typ}
}

@ -62,12 +62,12 @@ var errJSONEOF = errors.New("unexpected end of JSON input")
var unmarshalBytesTests = []unmarshalTest{ var unmarshalBytesTests = []unmarshalTest{
// invalid encoding // invalid encoding
{input: "", wantErr: errJSONEOF}, {input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString}, {input: "null", wantErr: errNonString(bytesT)},
{input: "10", wantErr: errNonString}, {input: "10", wantErr: errNonString(bytesT)},
{input: `"0"`, wantErr: ErrMissingPrefix}, {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bytesT)},
{input: `"0x0"`, wantErr: ErrOddLength}, {input: `"0x0"`, wantErr: wrapTypeError(ErrOddLength, bytesT)},
{input: `"0xxx"`, wantErr: ErrSyntax}, {input: `"0xxx"`, wantErr: wrapTypeError(ErrSyntax, bytesT)},
{input: `"0x01zz01"`, wantErr: ErrSyntax}, {input: `"0x01zz01"`, wantErr: wrapTypeError(ErrSyntax, bytesT)},
// valid encoding // valid encoding
{input: `""`, want: referenceBytes("")}, {input: `""`, want: referenceBytes("")},
@ -127,16 +127,16 @@ func TestMarshalBytes(t *testing.T) {
var unmarshalBigTests = []unmarshalTest{ var unmarshalBigTests = []unmarshalTest{
// invalid encoding // invalid encoding
{input: "", wantErr: errJSONEOF}, {input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString}, {input: "null", wantErr: errNonString(bigT)},
{input: "10", wantErr: errNonString}, {input: "10", wantErr: errNonString(bigT)},
{input: `"0"`, wantErr: ErrMissingPrefix}, {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bigT)},
{input: `"0x"`, wantErr: ErrEmptyNumber}, {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, bigT)},
{input: `"0x01"`, wantErr: ErrLeadingZero}, {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, bigT)},
{input: `"0xx"`, wantErr: ErrSyntax}, {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, bigT)},
{input: `"0x1zz01"`, wantErr: ErrSyntax}, {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, bigT)},
{ {
input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`, input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`,
wantErr: ErrBig256Range, wantErr: wrapTypeError(ErrBig256Range, bigT),
}, },
// valid encoding // valid encoding
@ -208,14 +208,14 @@ func TestMarshalBig(t *testing.T) {
var unmarshalUint64Tests = []unmarshalTest{ var unmarshalUint64Tests = []unmarshalTest{
// invalid encoding // invalid encoding
{input: "", wantErr: errJSONEOF}, {input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString}, {input: "null", wantErr: errNonString(uint64T)},
{input: "10", wantErr: errNonString}, {input: "10", wantErr: errNonString(uint64T)},
{input: `"0"`, wantErr: ErrMissingPrefix}, {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uint64T)},
{input: `"0x"`, wantErr: ErrEmptyNumber}, {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uint64T)},
{input: `"0x01"`, wantErr: ErrLeadingZero}, {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uint64T)},
{input: `"0xfffffffffffffffff"`, wantErr: ErrUint64Range}, {input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUint64Range, uint64T)},
{input: `"0xx"`, wantErr: ErrSyntax}, {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uint64T)},
{input: `"0x1zz01"`, wantErr: ErrSyntax}, {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uint64T)},
// valid encoding // valid encoding
{input: `""`, want: uint64(0)}, {input: `""`, want: uint64(0)},
@ -298,15 +298,15 @@ var (
var unmarshalUintTests = []unmarshalTest{ var unmarshalUintTests = []unmarshalTest{
// invalid encoding // invalid encoding
{input: "", wantErr: errJSONEOF}, {input: "", wantErr: errJSONEOF},
{input: "null", wantErr: errNonString}, {input: "null", wantErr: errNonString(uintT)},
{input: "10", wantErr: errNonString}, {input: "10", wantErr: errNonString(uintT)},
{input: `"0"`, wantErr: ErrMissingPrefix}, {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, uintT)},
{input: `"0x"`, wantErr: ErrEmptyNumber}, {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, uintT)},
{input: `"0x01"`, wantErr: ErrLeadingZero}, {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, uintT)},
{input: `"0x100000000"`, want: uint(maxUint33bits), wantErr32bit: ErrUintRange}, {input: `"0x100000000"`, want: uint(maxUint33bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)},
{input: `"0xfffffffffffffffff"`, wantErr: ErrUintRange}, {input: `"0xfffffffffffffffff"`, wantErr: wrapTypeError(ErrUintRange, uintT)},
{input: `"0xx"`, wantErr: ErrSyntax}, {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, uintT)},
{input: `"0x1zz01"`, wantErr: ErrSyntax}, {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, uintT)},
// valid encoding // valid encoding
{input: `""`, want: uint(0)}, {input: `""`, want: uint(0)},
@ -317,7 +317,7 @@ var unmarshalUintTests = []unmarshalTest{
{input: `"0x1122aaff"`, want: uint(0x1122aaff)}, {input: `"0x1122aaff"`, want: uint(0x1122aaff)},
{input: `"0xbbb"`, want: uint(0xbbb)}, {input: `"0xbbb"`, want: uint(0xbbb)},
{input: `"0xffffffff"`, want: uint(0xffffffff)}, {input: `"0xffffffff"`, want: uint(0xffffffff)},
{input: `"0xffffffffffffffff"`, want: uint(maxUint64bits), wantErr32bit: ErrUintRange}, {input: `"0xffffffffffffffff"`, want: uint(maxUint64bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)},
} }
func TestUnmarshalUint(t *testing.T) { func TestUnmarshalUint(t *testing.T) {

@ -31,6 +31,11 @@ const (
AddressLength = 20 AddressLength = 20
) )
var (
hashT = reflect.TypeOf(Hash{})
addressT = reflect.TypeOf(Address{})
)
// Hash represents the 32 byte Keccak256 hash of arbitrary data. // Hash represents the 32 byte Keccak256 hash of arbitrary data.
type Hash [HashLength]byte type Hash [HashLength]byte
@ -72,6 +77,11 @@ func (h *Hash) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("Hash", input, h[:]) return hexutil.UnmarshalFixedText("Hash", input, h[:])
} }
// UnmarshalJSON parses a hash in hex syntax.
func (h *Hash) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(hashT, input, h[:])
}
// MarshalText returns the hex representation of h. // MarshalText returns the hex representation of h.
func (h Hash) MarshalText() ([]byte, error) { func (h Hash) MarshalText() ([]byte, error) {
return hexutil.Bytes(h[:]).MarshalText() return hexutil.Bytes(h[:]).MarshalText()
@ -194,6 +204,11 @@ func (a *Address) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("Address", input, a[:]) return hexutil.UnmarshalFixedText("Address", input, a[:])
} }
// UnmarshalJSON parses a hash in hex syntax.
func (a *Address) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(addressT, input, a[:])
}
// UnprefixedHash allows marshaling an Address without 0x prefix. // UnprefixedHash allows marshaling an Address without 0x prefix.
type UnprefixedAddress Address type UnprefixedAddress Address

@ -21,8 +21,6 @@ import (
"math/big" "math/big"
"strings" "strings"
"testing" "testing"
"github.com/ethereum/go-ethereum/common/hexutil"
) )
func TestBytesConversion(t *testing.T) { func TestBytesConversion(t *testing.T) {
@ -43,10 +41,10 @@ func TestHashJsonValidation(t *testing.T) {
Size int Size int
Error string Error string
}{ }{
{"", 62, hexutil.ErrMissingPrefix.Error()}, {"", 62, "json: cannot unmarshal hex string without 0x prefix into Go value of type common.Hash"},
{"0x", 66, "hex string has length 66, want 64 for Hash"}, {"0x", 66, "hex string has length 66, want 64 for common.Hash"},
{"0x", 63, hexutil.ErrOddLength.Error()}, {"0x", 63, "json: cannot unmarshal hex string of odd length into Go value of type common.Hash"},
{"0x", 0, "hex string has length 0, want 64 for Hash"}, {"0x", 0, "hex string has length 0, want 64 for common.Hash"},
{"0x", 64, ""}, {"0x", 64, ""},
{"0X", 64, ""}, {"0X", 64, ""},
} }

Loading…
Cancel
Save