diff --git a/pkg/scale/README.md b/pkg/scale/README.md index 6e97e6b1eb..283f3492c8 100644 --- a/pkg/scale/README.md +++ b/pkg/scale/README.md @@ -82,7 +82,7 @@ SCALE uses a compact encoding for variable width unsigned integers. ### Basic Example Basic example which encodes and decodes a `uint`. -``` +```go import ( "fmt" "github.com/ChainSafe/gossamer/pkg/scale" @@ -111,7 +111,7 @@ func ExampleBasic() { Use the `scale` struct tag for struct fields to conform to specific encoding sequence of struct field values. A struct tag of `"-"` will be omitted from encoding and decoding. -``` +```go import ( "fmt" "github.com/ChainSafe/gossamer/pkg/scale" @@ -159,7 +159,7 @@ result := scale.NewResult(int32(0), int32(0) result.Set(scale.Ok, 10) ``` -``` +```go import ( "fmt" "github.com/ChainSafe/gossamer/pkg/scale" @@ -213,7 +213,7 @@ func ExampleResult() { A `VaryingDataType` is analogous to a Rust enum. A `VaryingDataType` needs to be constructed using the `NewVaryingDataType` constructor. `VaryingDataTypeValue` is an interface with one `Index() uint` method that needs to be implemented. The returned `uint` index should be unique per type and needs to be the same index as defined in the Rust enum to ensure interopability. To set the value of the `VaryingDataType`, the `VaryingDataType.Set()` function should be called with an associated `VaryingDataTypeValue`. -``` +```go import ( "fmt" "github.com/ChainSafe/gossamer/pkg/scale" @@ -323,4 +323,192 @@ func ExampleVaryingDataTypeSlice() { panic(fmt.Errorf("uh oh: %+v %+v", vdts, vdts1)) } } +``` + +#### Nested VaryingDataType + +See `varying_data_type_nested_example.go` for a working example of a custom `VaryingDataType` with another custom `VaryingDataType` as a value of the parent `VaryingDataType`. In the case of nested `VaryingDataTypes`, a custom type needs to be created for the child `VaryingDataType` because it needs to fulfill the `VaryingDataTypeValue` interface. + +```go +import ( + "fmt" + "reflect" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// ParentVDT is a VaryingDataType that consists of multiple nested VaryingDataType +// instances (aka. a rust enum containing multiple enum options) +type ParentVDT scale.VaryingDataType + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (pvdt *ParentVDT) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*pvdt) + err = vdt.Set(val) + if err != nil { + return + } + // store original ParentVDT with VaryingDataType that has been set + *pvdt = ParentVDT(vdt) + return +} + +// Value will return value from underying VaryingDataType +func (pvdt *ParentVDT) Value() (val scale.VaryingDataTypeValue) { + vdt := scale.VaryingDataType(*pvdt) + return vdt.Value() +} + +// NewParentVDT is constructor for ParentVDT +func NewParentVDT() ParentVDT { + // use standard VaryingDataType constructor to construct a VaryingDataType + vdt, err := scale.NewVaryingDataType(NewChildVDT(), NewOtherChildVDT()) + if err != nil { + panic(err) + } + // cast to ParentVDT + return ParentVDT(vdt) +} + +// ChildVDT type is used as a VaryingDataTypeValue for ParentVDT +type ChildVDT scale.VaryingDataType + +// Index fulfills the VaryingDataTypeValue interface. T +func (cvdt ChildVDT) Index() uint { + return 1 +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (cvdt *ChildVDT) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*cvdt) + err = vdt.Set(val) + if err != nil { + return + } + // store original ParentVDT with VaryingDataType that has been set + *cvdt = ChildVDT(vdt) + return +} + +// Value will return value from underying VaryingDataType +func (cvdt *ChildVDT) Value() (val scale.VaryingDataTypeValue) { + vdt := scale.VaryingDataType(*cvdt) + return vdt.Value() +} + +// NewChildVDT is constructor for ChildVDT +func NewChildVDT() ChildVDT { + // use standard VaryingDataType constructor to construct a VaryingDataType + // constarined to types ChildInt16, ChildStruct, and ChildString + vdt, err := scale.NewVaryingDataType(ChildInt16(0), ChildStruct{}, ChildString("")) + if err != nil { + panic(err) + } + // cast to ParentVDT + return ChildVDT(vdt) +} + +// OtherChildVDT type is used as a VaryingDataTypeValue for ParentVDT +type OtherChildVDT scale.VaryingDataType + +// Index fulfills the VaryingDataTypeValue interface. +func (ocvdt OtherChildVDT) Index() uint { + return 2 +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (cvdt *OtherChildVDT) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*cvdt) + err = vdt.Set(val) + if err != nil { + return + } + // store original ParentVDT with VaryingDataType that has been set + *cvdt = OtherChildVDT(vdt) + return +} + +// NewOtherChildVDT is constructor for OtherChildVDT +func NewOtherChildVDT() OtherChildVDT { + // use standard VaryingDataType constructor to construct a VaryingDataType + // constarined to types ChildInt16 and ChildStruct + vdt, err := scale.NewVaryingDataType(ChildInt16(0), ChildStruct{}, ChildString("")) + if err != nil { + panic(err) + } + // cast to ParentVDT + return OtherChildVDT(vdt) +} + +// ChildInt16 is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT +type ChildInt16 int16 + +// Index fulfills the VaryingDataTypeValue interface. The ChildVDT type is used as a +// VaryingDataTypeValue for ParentVDT +func (ci ChildInt16) Index() uint { + return 1 +} + +// ChildStruct is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT +type ChildStruct struct { + A string + B bool +} + +// Index fulfills the VaryingDataTypeValue interface +func (cs ChildStruct) Index() uint { + return 2 +} + +// ChildString is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT +type ChildString string + +// Index fulfills the VaryingDataTypeValue interface +func (cs ChildString) Index() uint { + return 3 +} + +func ExampleNestedVaryingDataType() { + parent := NewParentVDT() + + // populate parent with ChildVDT + child := NewChildVDT() + child.Set(ChildInt16(888)) + err := parent.Set(child) + if err != nil { + panic(err) + } + + // validate ParentVDT.Value() + fmt.Printf("parent.Value(): %+v\n", parent.Value()) + // should cast to ChildVDT, since that was set earlier + valChildVDT := parent.Value().(ChildVDT) + // validate ChildVDT.Value() as ChildInt16(888) + fmt.Printf("child.Value(): %+v\n", valChildVDT.Value()) + + // marshal into scale encoded bytes + bytes, err := scale.Marshal(parent) + if err != nil { + panic(err) + } + fmt.Printf("bytes: % x\n", bytes) + + // unmarshal into another ParentVDT + dstParent := NewParentVDT() + err = scale.Unmarshal(bytes, &dstParent) + if err != nil { + panic(err) + } + // assert both ParentVDT instances are the same + fmt.Println(reflect.DeepEqual(parent, dstParent)) + + // Output: + // parent.Value(): {value:888 cache:map[1:0 2:{A: B:false} 3:]} + // child.Value(): 888 + // bytes: 01 01 78 03 + // true +} ``` \ No newline at end of file diff --git a/pkg/scale/decode.go b/pkg/scale/decode.go index 73e19c85d1..4e427e1b35 100644 --- a/pkg/scale/decode.go +++ b/pkg/scale/decode.go @@ -149,7 +149,12 @@ func (ds *decodeState) unmarshal(dstv reflect.Value) (err error) { case reflect.Ptr: err = ds.decodePointer(dstv) case reflect.Struct: - err = ds.decodeStruct(dstv) + ok := reflect.ValueOf(in).CanConvert(reflect.TypeOf(VaryingDataType{})) + if ok { + err = ds.decodeCustomVaryingDataType(dstv) + } else { + err = ds.decodeStruct(dstv) + } case reflect.Array: err = ds.decodeArray(dstv) case reflect.Slice: @@ -344,6 +349,19 @@ func (ds *decodeState) decodeVaryingDataTypeSlice(dstv reflect.Value) (err error return } +func (ds *decodeState) decodeCustomVaryingDataType(dstv reflect.Value) (err error) { + initialType := dstv.Type() + converted := dstv.Convert(reflect.TypeOf(VaryingDataType{})) + tempVal := reflect.New(converted.Type()) + tempVal.Elem().Set(converted) + err = ds.decodeVaryingDataType(tempVal.Elem()) + if err != nil { + return + } + dstv.Set(tempVal.Elem().Convert(initialType)) + return +} + func (ds *decodeState) decodeVaryingDataType(dstv reflect.Value) (err error) { var b byte b, err = ds.ReadByte() @@ -358,12 +376,13 @@ func (ds *decodeState) decodeVaryingDataType(dstv reflect.Value) (err error) { return } - tempVal := reflect.New(reflect.TypeOf(val)).Elem() - err = ds.unmarshal(tempVal) + tempVal := reflect.New(reflect.TypeOf(val)) + tempVal.Elem().Set(reflect.ValueOf(val)) + err = ds.unmarshal(tempVal.Elem()) if err != nil { return } - err = vdt.Set(tempVal.Interface().(VaryingDataTypeValue)) + err = vdt.Set(tempVal.Elem().Interface().(VaryingDataTypeValue)) if err != nil { return } diff --git a/pkg/scale/encode.go b/pkg/scale/encode.go index 03f019b988..9074d03aa2 100644 --- a/pkg/scale/encode.go +++ b/pkg/scale/encode.go @@ -73,7 +73,12 @@ func (es *encodeState) marshal(in interface{}) (err error) { err = es.marshal(elem.Interface()) } case reflect.Struct: - err = es.encodeStruct(in) + ok := reflect.ValueOf(in).CanConvert(reflect.TypeOf(VaryingDataType{})) + if ok { + err = es.encodeCustomVaryingDataType(in) + } else { + err = es.encodeStruct(in) + } case reflect.Array: err = es.encodeArray(in) case reflect.Slice: @@ -148,6 +153,11 @@ func (es *encodeState) encodeResult(res Result) (err error) { return } +func (es *encodeState) encodeCustomVaryingDataType(in interface{}) (err error) { + vdt := reflect.ValueOf(in).Convert(reflect.TypeOf(VaryingDataType{})).Interface().(VaryingDataType) + return es.encodeVaryingDataType(vdt) +} + func (es *encodeState) encodeVaryingDataType(vdt VaryingDataType) (err error) { err = es.WriteByte(byte(vdt.value.Index())) if err != nil { diff --git a/pkg/scale/varying_data_type_nested_example_test.go b/pkg/scale/varying_data_type_nested_example_test.go new file mode 100644 index 0000000000..54e1e67530 --- /dev/null +++ b/pkg/scale/varying_data_type_nested_example_test.go @@ -0,0 +1,186 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package scale_test + +import ( + "fmt" + "reflect" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// ParentVDT is a VaryingDataType that consists of multiple nested VaryingDataType +// instances (aka. a rust enum containing multiple enum options) +type ParentVDT scale.VaryingDataType + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (pvdt *ParentVDT) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*pvdt) + err = vdt.Set(val) + if err != nil { + return + } + // store original ParentVDT with VaryingDataType that has been set + *pvdt = ParentVDT(vdt) + return +} + +// Value will return value from underying VaryingDataType +func (pvdt *ParentVDT) Value() (val scale.VaryingDataTypeValue) { + vdt := scale.VaryingDataType(*pvdt) + return vdt.Value() +} + +// NewParentVDT is constructor for ParentVDT +func NewParentVDT() ParentVDT { + // use standard VaryingDataType constructor to construct a VaryingDataType + vdt, err := scale.NewVaryingDataType(NewChildVDT(), NewOtherChildVDT()) + if err != nil { + panic(err) + } + // cast to ParentVDT + return ParentVDT(vdt) +} + +// ChildVDT type is used as a VaryingDataTypeValue for ParentVDT +type ChildVDT scale.VaryingDataType + +// Index fulfils the VaryingDataTypeValue interface. T +func (cvdt ChildVDT) Index() uint { + return 1 +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (cvdt *ChildVDT) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*cvdt) + err = vdt.Set(val) + if err != nil { + return + } + // store original ParentVDT with VaryingDataType that has been set + *cvdt = ChildVDT(vdt) + return +} + +// Value will return value from underying VaryingDataType +func (cvdt *ChildVDT) Value() (val scale.VaryingDataTypeValue) { + vdt := scale.VaryingDataType(*cvdt) + return vdt.Value() +} + +// NewChildVDT is constructor for ChildVDT +func NewChildVDT() ChildVDT { + // use standard VaryingDataType constructor to construct a VaryingDataType + // constarined to types ChildInt16, ChildStruct, and ChildString + vdt, err := scale.NewVaryingDataType(ChildInt16(0), ChildStruct{}, ChildString("")) + if err != nil { + panic(err) + } + // cast to ParentVDT + return ChildVDT(vdt) +} + +// OtherChildVDT type is used as a VaryingDataTypeValue for ParentVDT +type OtherChildVDT scale.VaryingDataType + +// Index fulfils the VaryingDataTypeValue interface. +func (ocvdt OtherChildVDT) Index() uint { + return 2 +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (cvdt *OtherChildVDT) Set(val scale.VaryingDataTypeValue) (err error) { //nolint:revive + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*cvdt) + err = vdt.Set(val) + if err != nil { + return + } + // store original ParentVDT with VaryingDataType that has been set + *cvdt = OtherChildVDT(vdt) + return +} + +// NewOtherChildVDT is constructor for OtherChildVDT +func NewOtherChildVDT() OtherChildVDT { + // use standard VaryingDataType constructor to construct a VaryingDataType + // constarined to types ChildInt16 and ChildStruct + vdt, err := scale.NewVaryingDataType(ChildInt16(0), ChildStruct{}, ChildString("")) + if err != nil { + panic(err) + } + // cast to ParentVDT + return OtherChildVDT(vdt) +} + +// ChildInt16 is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT +type ChildInt16 int16 + +// Index fulfils the VaryingDataTypeValue interface. The ChildVDT type is used as a +// VaryingDataTypeValue for ParentVDT +func (ci ChildInt16) Index() uint { + return 1 +} + +// ChildStruct is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT +type ChildStruct struct { + A string + B bool +} + +// Index fulfils the VaryingDataTypeValue interface +func (cs ChildStruct) Index() uint { + return 2 +} + +// ChildString is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT +type ChildString string + +// Index fulfils the VaryingDataTypeValue interface +func (cs ChildString) Index() uint { + return 3 +} + +func Example() { + parent := NewParentVDT() + + // populate parent with ChildVDT + child := NewChildVDT() + child.Set(ChildInt16(888)) + err := parent.Set(child) + if err != nil { + panic(err) + } + + // validate ParentVDT.Value() + fmt.Printf("parent.Value(): %+v\n", parent.Value()) + // should cast to ChildVDT, since that was set earlier + valChildVDT := parent.Value().(ChildVDT) + // validate ChildVDT.Value() as ChildInt16(888) + fmt.Printf("child.Value(): %+v\n", valChildVDT.Value()) + + // marshal into scale encoded bytes + bytes, err := scale.Marshal(parent) + if err != nil { + panic(err) + } + fmt.Printf("bytes: % x\n", bytes) + + // unmarshal into another ParentVDT + dstParent := NewParentVDT() + err = scale.Unmarshal(bytes, &dstParent) + if err != nil { + panic(err) + } + // assert both ParentVDT instances are the same + fmt.Println(reflect.DeepEqual(parent, dstParent)) + + // Output: + // parent.Value(): {value:888 cache:map[1:0 2:{A: B:false} 3:]} + // child.Value(): 888 + // bytes: 01 01 78 03 + // true +} diff --git a/pkg/scale/varying_data_type_nested_test.go b/pkg/scale/varying_data_type_nested_test.go new file mode 100644 index 0000000000..dec5dc2413 --- /dev/null +++ b/pkg/scale/varying_data_type_nested_test.go @@ -0,0 +1,199 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package scale + +import ( + "math/big" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" +) + +type parentVDT VaryingDataType + +func (pvdt *parentVDT) Set(val VaryingDataTypeValue) (err error) { + vdt := VaryingDataType(*pvdt) + err = vdt.Set(val) + if err != nil { + return + } + *pvdt = parentVDT(vdt) + return +} + +func mustNewParentVDT() parentVDT { + vdt, err := NewVaryingDataType(mustNewChildVDT(), mustNewChildVDT1()) + if err != nil { + panic(err) + } + return parentVDT(vdt) +} + +type childVDT VaryingDataType + +func (cvdt childVDT) Index() uint { + return 1 +} + +func mustNewChildVDT() childVDT { + vdt, err := NewVaryingDataType(VDTValue{}, VDTValue1{}, VDTValue2{}, VDTValue3(0)) + if err != nil { + panic(err) + } + return childVDT(vdt) +} + +func mustNewChildVDTAndSet(vdtv VaryingDataTypeValue) childVDT { + vdt, err := NewVaryingDataType(VDTValue{}, VDTValue1{}, VDTValue2{}, VDTValue3(0)) + if err != nil { + panic(err) + } + err = vdt.Set(vdtv) + if err != nil { + panic(err) + } + return childVDT(vdt) +} + +type childVDT1 VaryingDataType + +func (cvdt childVDT1) Index() uint { + return 2 +} + +func mustNewChildVDT1() childVDT1 { + vdt, err := NewVaryingDataType(VDTValue{}, VDTValue1{}, VDTValue2{}) + if err != nil { + panic(err) + } + return childVDT1(vdt) +} + +func mustNewChildVDT1AndSet(vdtv VaryingDataTypeValue) childVDT1 { + vdt, err := NewVaryingDataType(VDTValue{}, VDTValue1{}, VDTValue2{}) + if err != nil { + panic(err) + } + err = vdt.Set(vdtv) + if err != nil { + panic(err) + } + return childVDT1(vdt) +} + +type constructorTest struct { + name string + newIn func(t *testing.T) interface{} + want []byte + wantErr bool +} + +var nestedVaryingDataTypeTests = []constructorTest{ + { + name: "ParentVDT with ChildVDT", + newIn: func(t *testing.T) interface{} { + pvdt := mustNewParentVDT() + err := pvdt.Set(mustNewChildVDTAndSet(VDTValue3(16383))) + if err != nil { + t.Fatalf("%v", err) + } + return pvdt + }, + want: newWant( + // index of childVDT + []byte{1}, + // index of VDTValue3 + []byte{4}, + // encoding of int16 + []byte{0xff, 0x3f}, + ), + }, + { + name: "ParentVDT with ChildVDT1", + newIn: func(t *testing.T) interface{} { + pvdt := mustNewParentVDT() + err := pvdt.Set(mustNewChildVDT1AndSet( + VDTValue{ + A: big.NewInt(1073741823), + B: int(1073741823), + C: uint(1073741823), + D: int8(1), + E: uint8(1), + F: int16(16383), + G: uint16(16383), + H: int32(1073741823), + I: uint32(1073741823), + J: int64(9223372036854775807), + K: uint64(9223372036854775807), + L: byteArray(64), + M: testStrings[1], + N: true, + }, + )) + if err != nil { + t.Fatalf("%v", err) + } + return pvdt + }, + want: newWant( + // index of childVDT1 + []byte{2}, + // index of VDTValue + []byte{1}, + // encoding of struct + []byte{ + 0xfe, 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0xff, + 0x01, + 0x01, + 0xff, 0x3f, + 0xff, 0x3f, + 0xff, 0xff, 0xff, 0x3f, + 0xff, 0xff, 0xff, 0x3f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, + }, + append([]byte{0x01, 0x01}, byteArray(64)...), + append([]byte{0xC2, 0x02, 0x01, 0x00}, testStrings[1]...), + []byte{0x01}, + ), + }, +} + +func Test_encodeState_encodeCustomVaryingDataType_nested(t *testing.T) { + for _, tt := range nestedVaryingDataTypeTests { + t.Run(tt.name, func(t *testing.T) { + b, err := Marshal(tt.newIn(t)) + if (err != nil) != tt.wantErr { + t.Errorf("Marshal() error = %v, wantErr %v", err, tt.wantErr) + } + if diff := cmp.Diff(b, tt.want); diff != "" { + t.Errorf("Marshal() diff: %s", diff) + } + }) + } +} + +func Test_decodeState_decodeCustomVaryingDataType_nested(t *testing.T) { + for _, tt := range nestedVaryingDataTypeTests { + t.Run(tt.name, func(t *testing.T) { + dst := mustNewParentVDT() + if err := Unmarshal(tt.want, &dst); (err != nil) != tt.wantErr { + t.Errorf("decodeState.unmarshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + expected := tt.newIn(t) + + diff := cmp.Diff(dst, expected, + cmp.AllowUnexported(parentVDT{}, childVDT{}, childVDT1{}), + cmpopts.IgnoreUnexported(big.Int{}, VDTValue2{}, MyStructWithIgnore{}), + ) + if diff != "" { + t.Errorf("decodeState.unmarshal() = %s", diff) + } + }) + } +} diff --git a/pkg/scale/varying_data_type_test.go b/pkg/scale/varying_data_type_test.go index 0a794d4b15..3c7b50d6ba 100644 --- a/pkg/scale/varying_data_type_test.go +++ b/pkg/scale/varying_data_type_test.go @@ -29,6 +29,8 @@ func mustNewVaryingDataTypeAndSet(value VaryingDataTypeValue, values ...VaryingD return } +type customVDT VaryingDataType + type VDTValue struct { A *big.Int B int @@ -294,10 +296,10 @@ func Test_encodeState_encodeVaryingDataType(t *testing.T) { es := &encodeState{fieldScaleIndicesCache: cache} vdt := tt.in.(VaryingDataType) if err := es.marshal(vdt); (err != nil) != tt.wantErr { - t.Errorf("encodeState.encodeStruct() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("encodeState.marshal() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(es.Buffer.Bytes(), tt.want) { - t.Errorf("encodeState.encodeStruct() = %v, want %v", es.Buffer.Bytes(), tt.want) + t.Errorf("encodeState.marshal() = %v, want %v", es.Buffer.Bytes(), tt.want) } }) } @@ -324,6 +326,49 @@ func Test_decodeState_decodeVaryingDataType(t *testing.T) { } } +func Test_encodeState_encodeCustomVaryingDataType(t *testing.T) { + for _, tt := range varyingDataTypeTests { + t.Run(tt.name, func(t *testing.T) { + es := &encodeState{fieldScaleIndicesCache: cache} + vdt := tt.in.(VaryingDataType) + cvdt := customVDT(vdt) + if err := es.marshal(cvdt); (err != nil) != tt.wantErr { + t.Errorf("encodeState.encodeStruct() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(es.Buffer.Bytes(), tt.want) { + t.Errorf("encodeState.encodeStruct() = %v, want %v", es.Buffer.Bytes(), tt.want) + } + }) + } +} +func Test_decodeState_decodeCustomVaryingDataType(t *testing.T) { + for _, tt := range varyingDataTypeTests { + t.Run(tt.name, func(t *testing.T) { + vdt, err := NewVaryingDataType(VDTValue{}, VDTValue1{}, VDTValue2{}, VDTValue3(0)) + if err != nil { + t.Errorf("%v", err) + return + } + dst := customVDT(vdt) + if err := Unmarshal(tt.want, &dst); (err != nil) != tt.wantErr { + t.Errorf("decodeState.unmarshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + + dstVDT := reflect.ValueOf(tt.in).Convert(reflect.TypeOf(VaryingDataType{})).Interface().(VaryingDataType) + inVDT := reflect.ValueOf(tt.in).Convert(reflect.TypeOf(VaryingDataType{})).Interface().(VaryingDataType) + diff := cmp.Diff(dstVDT.Value(), inVDT.Value(), + cmpopts.IgnoreUnexported(big.Int{}, VDTValue2{}, MyStructWithIgnore{})) + if diff != "" { + t.Errorf("decodeState.unmarshal() = %s", diff) + } + if reflect.TypeOf(dst) != reflect.TypeOf(customVDT{}) { + t.Errorf("types mismatch dst: %v expected: %v", reflect.TypeOf(dst), reflect.TypeOf(customVDT{})) + } + }) + } +} + func TestNewVaryingDataType(t *testing.T) { type args struct { values []VaryingDataTypeValue