Skip to content

Commit

Permalink
feat: Marshal untagged variable T as rust Option<T> (#6)
Browse files Browse the repository at this point in the history
In some case, Option wrapped values are not in a struct.
Here introduce a wrapper to do the rust style Option wrapper to wrap
the target value.
  • Loading branch information
howjmay authored Jul 4, 2024
1 parent ec0fc22 commit 3f6f759
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 0 deletions.
26 changes: 26 additions & 0 deletions bcs/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,32 @@ func Marshal(v any) ([]byte, error) {
return b.Bytes(), nil
}

type Option[T any] struct {
Some T
None *struct{}
}

func (p *Option[T]) MarshalBCS() ([]byte, error) {
if p.None != nil {
return []byte{0}, nil
}
b, err := Marshal(p.Some)
return append([]byte{1}, b...), err
}

func (p *Option[T]) UnmarshalBCS(r io.Reader) (int, error) {
buf := new(bytes.Buffer)
io.Copy(buf, r)
tmp := buf.Bytes()
if len(tmp) == 1 {
var emptyStruct struct{}
p.None = &emptyStruct
return 1, nil
}
b := tmp[1:]
return Unmarshal(b, &p.Some)
}

// MustMarshal [Marshal] v, and panics if error.
func MustMarshal(v any) []byte {
result, err := Marshal(v)
Expand Down
43 changes: 43 additions & 0 deletions bcs/encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,46 @@ func TestMarshal_optional(t *testing.T) {
t.Errorf("want: %v\ngot: %v\n", optionalSetExpected, optionalSetBytes)
}
}

func TestMarshal_option(t *testing.T) {
t.Run("some", func(t *testing.T) {
var p0, p1 bcs.Option[[]byte]
input := []byte{0xC0, 0xDE}
inputExpected := []byte{1, 2, 0xC0, 0xDE}
p0.Some = input
b, err := bcs.Marshal(&p0)
if err != nil {
t.Error(err)
}
if !sliceEqual(b, inputExpected) {
t.Errorf("want: %v\ngot: %v\n", inputExpected, b)
}
_, err = bcs.Unmarshal(b, &p1)
if err != nil {
t.Error(err)
}
if !sliceEqual(input, p1.Some) {
t.Errorf("want: %v\ngot: %v\n", input, p1.Some)
}
})
t.Run("none", func(t *testing.T) {
var p0, p1 bcs.Option[[]byte]
inputExpected := []byte{0}
var emptyStruct struct{}
p0.None = &emptyStruct
b, err := bcs.Marshal(&p0)
if err != nil {
t.Error(err)
}
if !sliceEqual(b, inputExpected) {
t.Errorf("want: %v\ngot: %v\n", inputExpected, b)
}
_, err = bcs.Unmarshal(b, &p1)
if err != nil {
t.Error(err)
}
if p1.None == nil {
t.Errorf("None field should have value")
}
})
}

0 comments on commit 3f6f759

Please sign in to comment.