Skip to content

Commit

Permalink
Extra helpers for hex
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Broadhurst <[email protected]>
  • Loading branch information
peterbroadhurst committed Jul 28, 2024
1 parent 61a5a5a commit 5a9cec1
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 1 deletion.
11 changes: 10 additions & 1 deletion pkg/ethtypes/hexbytes.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Kaleido, Inc.
// Copyright © 2024 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand All @@ -17,6 +17,7 @@
package ethtypes

import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
Expand All @@ -29,6 +30,10 @@ type HexBytesPlain []byte
// HexBytes0xPrefix are serialized to JSON as hex with an `0x` prefix
type HexBytes0xPrefix []byte

func (h HexBytesPlain) Equal(h2 HexBytesPlain) bool {
return bytes.Equal(h, h2)
}

func (h *HexBytesPlain) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
Expand All @@ -54,6 +59,10 @@ func (h *HexBytes0xPrefix) UnmarshalJSON(b []byte) error {
return ((*HexBytesPlain)(h)).UnmarshalJSON(b)
}

func (h HexBytes0xPrefix) Equal(h2 HexBytes0xPrefix) bool {
return bytes.Equal(h, h2)
}

func (h HexBytes0xPrefix) String() string {
return "0x" + hex.EncodeToString(h)
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/ethtypes/hexbytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,15 @@ func TestHexByteConstructors(t *testing.T) {
MustNewHexBytes0xPrefix("!wrong")
})
}

func TestHexByteEqual(t *testing.T) {
assert.True(t, HexBytesPlain(nil).Equal(nil))
assert.False(t, HexBytesPlain(nil).Equal(HexBytesPlain{0x00}))
assert.False(t, (HexBytesPlain{0x00}).Equal(nil))
assert.True(t, (HexBytesPlain{0x00}).Equal(HexBytesPlain{0x00}))

assert.True(t, HexBytes0xPrefix(nil).Equal(nil))
assert.False(t, HexBytes0xPrefix(nil).Equal(HexBytes0xPrefix{0x00}))
assert.False(t, (HexBytes0xPrefix{0x00}).Equal(nil))
assert.True(t, (HexBytes0xPrefix{0x00}).Equal(HexBytes0xPrefix{0x00}))
}
85 changes: 85 additions & 0 deletions pkg/ethtypes/hexuint64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright © 2024 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ethtypes

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/hyperledger/firefly-common/pkg/i18n"
)

// HexUint64 is a positive integer - serializes to JSON as an 0x hex string (no leading zeros), and parses flexibly depending on the prefix (so 0x for hex, or base 10 for plain string / float64)
type HexUint64 uint64

func (h *HexUint64) String() string {
if h == nil {
return "0x0"
}
return "0x" + strconv.FormatUint(uint64(*h), 16)
}

func (h HexUint64) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, h.String())), nil
}

func (h *HexUint64) UnmarshalJSON(b []byte) error {
var i interface{}
_ = json.Unmarshal(b, &i)
switch i := i.(type) {
case float64:
*h = HexUint64(i)
return nil
case string:
i64, err := strconv.ParseUint(i, 0, 64)
if err != nil {
return fmt.Errorf("unable to parse integer: %s", i)
}
*h = HexUint64(i64)
return nil
default:
return fmt.Errorf("unable to parse integer from type %T", i)
}
}

func (h HexUint64) Uint64() uint64 {
return uint64(h)
}

func (h *HexUint64) Uint64OrZero() uint64 {
if h == nil {
return 0
}
return uint64(*h)
}

func (h *HexUint64) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case int64:
*h = HexUint64(src)
return nil
case uint64:
*h = HexUint64(src)
return nil
default:
return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, h)
}
}
136 changes: 136 additions & 0 deletions pkg/ethtypes/hexuint64_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright © 2022 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ethtypes

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHexUint64Ok(t *testing.T) {

testStruct := struct {
I1 *HexUint64 `json:"i1"`
I2 *HexUint64 `json:"i2"`
I3 *HexUint64 `json:"i3"`
I4 *HexUint64 `json:"i4"`
I5 *HexUint64 `json:"i5,omitempty"`
}{}

testData := `{
"i1": "0xabcd1234",
"i2": "54321",
"i3": 12345
}`

err := json.Unmarshal([]byte(testData), &testStruct)
assert.NoError(t, err)

assert.Equal(t, uint64(0xabcd1234), testStruct.I1.Uint64())
assert.Equal(t, uint64(0xabcd1234), testStruct.I1.Uint64OrZero())
assert.Equal(t, uint64(54321), testStruct.I2.Uint64())
assert.Equal(t, uint64(12345), testStruct.I3.Uint64())
assert.Nil(t, testStruct.I4)
assert.Equal(t, uint64(0), testStruct.I4.Uint64OrZero()) // BigInt() safe on nils
assert.Equal(t, "0x0", testStruct.I4.String())
assert.Nil(t, testStruct.I5)
assert.Equal(t, uint64(12345), testStruct.I3.Uint64())

jsonSerialized, err := json.Marshal(&testStruct)
assert.JSONEq(t, `{
"i1": "0xabcd1234",
"i2": "0xd431",
"i3": "0x3039",
"i4": null
}`, string(jsonSerialized))

}

func TestHexUint64MissingBytes(t *testing.T) {

testStruct := struct {
I1 HexUint64 `json:"i1"`
}{}

testData := `{
"i1": "0x"
}`

err := json.Unmarshal([]byte(testData), &testStruct)
assert.Regexp(t, "unable to parse integer", err)
}

func TestHexUint64BadType(t *testing.T) {

testStruct := struct {
I1 HexUint64 `json:"i1"`
}{}

testData := `{
"i1": {}
}`

err := json.Unmarshal([]byte(testData), &testStruct)
assert.Regexp(t, "unable to parse integer", err)
}

func TestHexUint64BadJSON(t *testing.T) {

testStruct := struct {
I1 HexUint64 `json:"i1"`
}{}

testData := `{
"i1": null
}`

err := json.Unmarshal([]byte(testData), &testStruct)
assert.Error(t, err)
}

func TestHexUint64BadNegative(t *testing.T) {

testStruct := struct {
I1 HexUint64 `json:"i1"`
}{}

testData := `{
"i1": "-12345"
}`

err := json.Unmarshal([]byte(testData), &testStruct)
assert.Regexp(t, "parse", err)
}

func TestHexUint64Constructor(t *testing.T) {
assert.Equal(t, uint64(12345), HexUint64(12345).Uint64())
}

func TestScanUint64(t *testing.T) {
var i HexUint64
pI := &i
err := pI.Scan(false)
err = pI.Scan(nil)
assert.NoError(t, err)
assert.Equal(t, "0x0", pI.String())
pI.Scan(int64(5555))
assert.Equal(t, "0x15b3", pI.String())
pI.Scan(uint64(9999))
assert.Equal(t, "0x270f", pI.String())
}

0 comments on commit 5a9cec1

Please sign in to comment.