Skip to content

Commit 914a747

Browse files
authored
feat(pkg/scale): support for custom VaryingDataType types (#2612)
* wip decode functionality * wip encode custom vdt * refactor tests * more tests, fix decode for nested vdt * update readme, provide example of nested VDTs * add copyright * fix lint
1 parent 81cf2de commit 914a747

File tree

6 files changed

+658
-11
lines changed

6 files changed

+658
-11
lines changed

pkg/scale/README.md

Lines changed: 192 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ SCALE uses a compact encoding for variable width unsigned integers.
8282
### Basic Example
8383

8484
Basic example which encodes and decodes a `uint`.
85-
```
85+
```go
8686
import (
8787
"fmt"
8888
"github.com/ChainSafe/gossamer/pkg/scale"
@@ -111,7 +111,7 @@ func ExampleBasic() {
111111

112112
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.
113113

114-
```
114+
```go
115115
import (
116116
"fmt"
117117
"github.com/ChainSafe/gossamer/pkg/scale"
@@ -159,7 +159,7 @@ result := scale.NewResult(int32(0), int32(0)
159159
result.Set(scale.Ok, 10)
160160
```
161161

162-
```
162+
```go
163163
import (
164164
"fmt"
165165
"github.com/ChainSafe/gossamer/pkg/scale"
@@ -213,7 +213,7 @@ func ExampleResult() {
213213
A `VaryingDataType` is analogous to a Rust enum. A `VaryingDataType` needs to be constructed using the `NewVaryingDataType` constructor. `VaryingDataTypeValue` is an
214214
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`.
215215

216-
```
216+
```go
217217
import (
218218
"fmt"
219219
"github.com/ChainSafe/gossamer/pkg/scale"
@@ -323,4 +323,192 @@ func ExampleVaryingDataTypeSlice() {
323323
panic(fmt.Errorf("uh oh: %+v %+v", vdts, vdts1))
324324
}
325325
}
326+
```
327+
328+
#### Nested VaryingDataType
329+
330+
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.
331+
332+
```go
333+
import (
334+
"fmt"
335+
"reflect"
336+
337+
"github.com/ChainSafe/gossamer/pkg/scale"
338+
)
339+
340+
// ParentVDT is a VaryingDataType that consists of multiple nested VaryingDataType
341+
// instances (aka. a rust enum containing multiple enum options)
342+
type ParentVDT scale.VaryingDataType
343+
344+
// Set will set a VaryingDataTypeValue using the underlying VaryingDataType
345+
func (pvdt *ParentVDT) Set(val scale.VaryingDataTypeValue) (err error) {
346+
// cast to VaryingDataType to use VaryingDataType.Set method
347+
vdt := scale.VaryingDataType(*pvdt)
348+
err = vdt.Set(val)
349+
if err != nil {
350+
return
351+
}
352+
// store original ParentVDT with VaryingDataType that has been set
353+
*pvdt = ParentVDT(vdt)
354+
return
355+
}
356+
357+
// Value will return value from underying VaryingDataType
358+
func (pvdt *ParentVDT) Value() (val scale.VaryingDataTypeValue) {
359+
vdt := scale.VaryingDataType(*pvdt)
360+
return vdt.Value()
361+
}
362+
363+
// NewParentVDT is constructor for ParentVDT
364+
func NewParentVDT() ParentVDT {
365+
// use standard VaryingDataType constructor to construct a VaryingDataType
366+
vdt, err := scale.NewVaryingDataType(NewChildVDT(), NewOtherChildVDT())
367+
if err != nil {
368+
panic(err)
369+
}
370+
// cast to ParentVDT
371+
return ParentVDT(vdt)
372+
}
373+
374+
// ChildVDT type is used as a VaryingDataTypeValue for ParentVDT
375+
type ChildVDT scale.VaryingDataType
376+
377+
// Index fulfills the VaryingDataTypeValue interface. T
378+
func (cvdt ChildVDT) Index() uint {
379+
return 1
380+
}
381+
382+
// Set will set a VaryingDataTypeValue using the underlying VaryingDataType
383+
func (cvdt *ChildVDT) Set(val scale.VaryingDataTypeValue) (err error) {
384+
// cast to VaryingDataType to use VaryingDataType.Set method
385+
vdt := scale.VaryingDataType(*cvdt)
386+
err = vdt.Set(val)
387+
if err != nil {
388+
return
389+
}
390+
// store original ParentVDT with VaryingDataType that has been set
391+
*cvdt = ChildVDT(vdt)
392+
return
393+
}
394+
395+
// Value will return value from underying VaryingDataType
396+
func (cvdt *ChildVDT) Value() (val scale.VaryingDataTypeValue) {
397+
vdt := scale.VaryingDataType(*cvdt)
398+
return vdt.Value()
399+
}
400+
401+
// NewChildVDT is constructor for ChildVDT
402+
func NewChildVDT() ChildVDT {
403+
// use standard VaryingDataType constructor to construct a VaryingDataType
404+
// constarined to types ChildInt16, ChildStruct, and ChildString
405+
vdt, err := scale.NewVaryingDataType(ChildInt16(0), ChildStruct{}, ChildString(""))
406+
if err != nil {
407+
panic(err)
408+
}
409+
// cast to ParentVDT
410+
return ChildVDT(vdt)
411+
}
412+
413+
// OtherChildVDT type is used as a VaryingDataTypeValue for ParentVDT
414+
type OtherChildVDT scale.VaryingDataType
415+
416+
// Index fulfills the VaryingDataTypeValue interface.
417+
func (ocvdt OtherChildVDT) Index() uint {
418+
return 2
419+
}
420+
421+
// Set will set a VaryingDataTypeValue using the underlying VaryingDataType
422+
func (cvdt *OtherChildVDT) Set(val scale.VaryingDataTypeValue) (err error) {
423+
// cast to VaryingDataType to use VaryingDataType.Set method
424+
vdt := scale.VaryingDataType(*cvdt)
425+
err = vdt.Set(val)
426+
if err != nil {
427+
return
428+
}
429+
// store original ParentVDT with VaryingDataType that has been set
430+
*cvdt = OtherChildVDT(vdt)
431+
return
432+
}
433+
434+
// NewOtherChildVDT is constructor for OtherChildVDT
435+
func NewOtherChildVDT() OtherChildVDT {
436+
// use standard VaryingDataType constructor to construct a VaryingDataType
437+
// constarined to types ChildInt16 and ChildStruct
438+
vdt, err := scale.NewVaryingDataType(ChildInt16(0), ChildStruct{}, ChildString(""))
439+
if err != nil {
440+
panic(err)
441+
}
442+
// cast to ParentVDT
443+
return OtherChildVDT(vdt)
444+
}
445+
446+
// ChildInt16 is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT
447+
type ChildInt16 int16
448+
449+
// Index fulfills the VaryingDataTypeValue interface. The ChildVDT type is used as a
450+
// VaryingDataTypeValue for ParentVDT
451+
func (ci ChildInt16) Index() uint {
452+
return 1
453+
}
454+
455+
// ChildStruct is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT
456+
type ChildStruct struct {
457+
A string
458+
B bool
459+
}
460+
461+
// Index fulfills the VaryingDataTypeValue interface
462+
func (cs ChildStruct) Index() uint {
463+
return 2
464+
}
465+
466+
// ChildString is used as a VaryingDataTypeValue for ChildVDT and OtherChildVDT
467+
type ChildString string
468+
469+
// Index fulfills the VaryingDataTypeValue interface
470+
func (cs ChildString) Index() uint {
471+
return 3
472+
}
473+
474+
func ExampleNestedVaryingDataType() {
475+
parent := NewParentVDT()
476+
477+
// populate parent with ChildVDT
478+
child := NewChildVDT()
479+
child.Set(ChildInt16(888))
480+
err := parent.Set(child)
481+
if err != nil {
482+
panic(err)
483+
}
484+
485+
// validate ParentVDT.Value()
486+
fmt.Printf("parent.Value(): %+v\n", parent.Value())
487+
// should cast to ChildVDT, since that was set earlier
488+
valChildVDT := parent.Value().(ChildVDT)
489+
// validate ChildVDT.Value() as ChildInt16(888)
490+
fmt.Printf("child.Value(): %+v\n", valChildVDT.Value())
491+
492+
// marshal into scale encoded bytes
493+
bytes, err := scale.Marshal(parent)
494+
if err != nil {
495+
panic(err)
496+
}
497+
fmt.Printf("bytes: % x\n", bytes)
498+
499+
// unmarshal into another ParentVDT
500+
dstParent := NewParentVDT()
501+
err = scale.Unmarshal(bytes, &dstParent)
502+
if err != nil {
503+
panic(err)
504+
}
505+
// assert both ParentVDT instances are the same
506+
fmt.Println(reflect.DeepEqual(parent, dstParent))
507+
508+
// Output:
509+
// parent.Value(): {value:888 cache:map[1:0 2:{A: B:false} 3:]}
510+
// child.Value(): 888
511+
// bytes: 01 01 78 03
512+
// true
513+
}
326514
```

pkg/scale/decode.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,12 @@ func (ds *decodeState) unmarshal(dstv reflect.Value) (err error) {
149149
case reflect.Ptr:
150150
err = ds.decodePointer(dstv)
151151
case reflect.Struct:
152-
err = ds.decodeStruct(dstv)
152+
ok := reflect.ValueOf(in).CanConvert(reflect.TypeOf(VaryingDataType{}))
153+
if ok {
154+
err = ds.decodeCustomVaryingDataType(dstv)
155+
} else {
156+
err = ds.decodeStruct(dstv)
157+
}
153158
case reflect.Array:
154159
err = ds.decodeArray(dstv)
155160
case reflect.Slice:
@@ -344,6 +349,19 @@ func (ds *decodeState) decodeVaryingDataTypeSlice(dstv reflect.Value) (err error
344349
return
345350
}
346351

352+
func (ds *decodeState) decodeCustomVaryingDataType(dstv reflect.Value) (err error) {
353+
initialType := dstv.Type()
354+
converted := dstv.Convert(reflect.TypeOf(VaryingDataType{}))
355+
tempVal := reflect.New(converted.Type())
356+
tempVal.Elem().Set(converted)
357+
err = ds.decodeVaryingDataType(tempVal.Elem())
358+
if err != nil {
359+
return
360+
}
361+
dstv.Set(tempVal.Elem().Convert(initialType))
362+
return
363+
}
364+
347365
func (ds *decodeState) decodeVaryingDataType(dstv reflect.Value) (err error) {
348366
var b byte
349367
b, err = ds.ReadByte()
@@ -358,12 +376,13 @@ func (ds *decodeState) decodeVaryingDataType(dstv reflect.Value) (err error) {
358376
return
359377
}
360378

361-
tempVal := reflect.New(reflect.TypeOf(val)).Elem()
362-
err = ds.unmarshal(tempVal)
379+
tempVal := reflect.New(reflect.TypeOf(val))
380+
tempVal.Elem().Set(reflect.ValueOf(val))
381+
err = ds.unmarshal(tempVal.Elem())
363382
if err != nil {
364383
return
365384
}
366-
err = vdt.Set(tempVal.Interface().(VaryingDataTypeValue))
385+
err = vdt.Set(tempVal.Elem().Interface().(VaryingDataTypeValue))
367386
if err != nil {
368387
return
369388
}

pkg/scale/encode.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ func (es *encodeState) marshal(in interface{}) (err error) {
7373
err = es.marshal(elem.Interface())
7474
}
7575
case reflect.Struct:
76-
err = es.encodeStruct(in)
76+
ok := reflect.ValueOf(in).CanConvert(reflect.TypeOf(VaryingDataType{}))
77+
if ok {
78+
err = es.encodeCustomVaryingDataType(in)
79+
} else {
80+
err = es.encodeStruct(in)
81+
}
7782
case reflect.Array:
7883
err = es.encodeArray(in)
7984
case reflect.Slice:
@@ -148,6 +153,11 @@ func (es *encodeState) encodeResult(res Result) (err error) {
148153
return
149154
}
150155

156+
func (es *encodeState) encodeCustomVaryingDataType(in interface{}) (err error) {
157+
vdt := reflect.ValueOf(in).Convert(reflect.TypeOf(VaryingDataType{})).Interface().(VaryingDataType)
158+
return es.encodeVaryingDataType(vdt)
159+
}
160+
151161
func (es *encodeState) encodeVaryingDataType(vdt VaryingDataType) (err error) {
152162
err = es.WriteByte(byte(vdt.value.Index()))
153163
if err != nil {

0 commit comments

Comments
 (0)