diff --git a/spec/Specification.md b/spec/Specification.md index ab858e9b1..b2edd2a32 100644 --- a/spec/Specification.md +++ b/spec/Specification.md @@ -81,8 +81,8 @@ type Params struct { type BlockInfo struct { // block height this transaction is executed Height uint64 `json:"height"` - // time in nanoseconds since unix epoch. Uses string to ensure JavaScript compatibility. - Time uint64 `json:"time,string"` + // time in nanoseconds since unix epoch. Uses Uint64 to ensure JavaScript compatibility. + Time Uint64 `json:"time"` ChainID string `json:"chain_id"` } diff --git a/types/env.go b/types/env.go index 9dc882b06..8ad0bbbe1 100644 --- a/types/env.go +++ b/types/env.go @@ -16,8 +16,8 @@ type Env struct { type BlockInfo struct { // block height this transaction is executed Height uint64 `json:"height"` - // time in nanoseconds since unix epoch. Uses string to ensure JavaScript compatibility. - Time uint64 `json:"time,string"` + // time in nanoseconds since unix epoch. Uses Uint64 to ensure JavaScript compatibility. + Time Uint64 `json:"time"` ChainID string `json:"chain_id"` } diff --git a/types/env_test.go b/types/env_test.go index c460e345e..2cc092cab 100644 --- a/types/env_test.go +++ b/types/env_test.go @@ -92,5 +92,5 @@ func TestBlockInfoDeserialization(t *testing.T) { // Empty string is not a valid uint64 string err = json.Unmarshal([]byte(`{"height":0,"time":"","chain_id":""}`), &block) - require.ErrorContains(t, err, "invalid use of ,string struct tag, trying to unmarshal \"\" into uint64") + require.ErrorContains(t, err, "cannot unmarshal \"\" into Uint64, failed to parse integer") } diff --git a/types/types.go b/types/types.go index a8a8548c9..bd00713cf 100644 --- a/types/types.go +++ b/types/types.go @@ -2,9 +2,50 @@ package types import ( "encoding/json" + "fmt" "strconv" ) +// Uint64 is a wrapper for uint64, but it is marshalled to and from JSON as a string +type Uint64 uint64 + +func (u Uint64) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.FormatUint(uint64(u), 10)) +} + +func (u *Uint64) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("cannot unmarshal %s into Uint64, expected string-encoded integer", data) + } + v, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return fmt.Errorf("cannot unmarshal %s into Uint64, failed to parse integer", data) + } + *u = Uint64(v) + return nil +} + +// Int64 is a wrapper for int64, but it is marshalled to and from JSON as a string +type Int64 int64 + +func (i Int64) MarshalJSON() ([]byte, error) { + return json.Marshal(strconv.FormatInt(int64(i), 10)) +} + +func (i *Int64) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("cannot unmarshal %s into Int64, expected string-encoded integer", data) + } + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return fmt.Errorf("cannot unmarshal %s into Int64, failed to parse integer", data) + } + *i = Int64(v) + return nil +} + // HumanAddress is a printable (typically bech32 encoded) address string. Just use it as a label for developers. type HumanAddress = string diff --git a/types/types_test.go b/types/types_test.go new file mode 100644 index 000000000..af1d472e5 --- /dev/null +++ b/types/types_test.go @@ -0,0 +1,86 @@ +package types + +import ( + "encoding/json" + "math" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUint64JSON(t *testing.T) { + var u Uint64 + + // test unmarshal + err := json.Unmarshal([]byte(`"123"`), &u) + require.NoError(t, err) + require.Equal(t, uint64(123), uint64(u)) + // test marshal + bz, err := json.Marshal(u) + require.NoError(t, err) + require.Equal(t, `"123"`, string(bz)) + + // test max value unmarshal + err = json.Unmarshal([]byte(`"18446744073709551615"`), &u) + require.NoError(t, err) + require.Equal(t, uint64(math.MaxUint64), uint64(u)) + // test max value marshal + bz, err = json.Marshal(Uint64(uint64(math.MaxUint64))) + require.NoError(t, err) + require.Equal(t, `"18446744073709551615"`, string(bz)) + + // test max value + 1 + err = json.Unmarshal([]byte(`"18446744073709551616"`), &u) + require.Error(t, err) + + // test unquoted unmarshal + err = json.Unmarshal([]byte(`123`), &u) + require.EqualError(t, err, "cannot unmarshal 123 into Uint64, expected string-encoded integer") + + // test empty string + err = json.Unmarshal([]byte(`""`), &u) + require.EqualError(t, err, "cannot unmarshal \"\" into Uint64, failed to parse integer") +} + +func TestInt64JSON(t *testing.T) { + var i Int64 + + // test unmarshal + err := json.Unmarshal([]byte(`"-123"`), &i) + require.NoError(t, err) + require.Equal(t, int64(-123), int64(i)) + // test marshal + bz, err := json.Marshal(i) + require.NoError(t, err) + require.Equal(t, `"-123"`, string(bz)) + + // test max value unmarshal + err = json.Unmarshal([]byte(`"9223372036854775807"`), &i) + require.NoError(t, err) + require.Equal(t, int64(math.MaxInt64), int64(i)) + // test max value marshal + bz, err = json.Marshal(Int64(int64(math.MaxInt64))) + require.NoError(t, err) + require.Equal(t, `"9223372036854775807"`, string(bz)) + + // test max value + 1 + err = json.Unmarshal([]byte(`"9223372036854775808"`), &i) + require.Error(t, err) + + // test min value unmarshal + err = json.Unmarshal([]byte(`"-9223372036854775808"`), &i) + require.NoError(t, err) + require.Equal(t, int64(math.MinInt64), int64(i)) + // test min value marshal + bz, err = json.Marshal(Int64(int64(math.MinInt64))) + require.NoError(t, err) + require.Equal(t, `"-9223372036854775808"`, string(bz)) + + // test unquoted unmarshal + err = json.Unmarshal([]byte(`-123`), &i) + require.EqualError(t, err, "cannot unmarshal -123 into Int64, expected string-encoded integer") + + // test empty string + err = json.Unmarshal([]byte(`""`), &i) + require.EqualError(t, err, "cannot unmarshal \"\" into Int64, failed to parse integer") +}