From 3fabbac0d6704458120ae1e2285e4221dfa66ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 15 Jul 2024 19:14:51 -0700 Subject: [PATCH] allow decoding of nested structs --- types.go | 96 +++++++++++++++++++++++++++++++++------------------ types_test.go | 23 ++++++++++++ 2 files changed, 86 insertions(+), 33 deletions(-) diff --git a/types.go b/types.go index a957b549bb..acbf1b36ee 100644 --- a/types.go +++ b/types.go @@ -504,40 +504,11 @@ func DecodeFields(composite Composite, s interface{}) error { } v = v.Elem() - t := v.Type() + targetType := v.Type() - fieldsMap := FieldsMappedByName(composite) - - for i := 0; i < v.NumField(); i++ { - structField := t.Field(i) - tag := structField.Tag - fieldValue := v.Field(i) - - cadenceFieldNameTag := tag.Get("cadence") - if cadenceFieldNameTag == "" { - continue - } - - if !fieldValue.IsValid() || !fieldValue.CanSet() { - return fmt.Errorf("cannot set field %s", structField.Name) - } - - value := fieldsMap[cadenceFieldNameTag] - if value == nil { - return fmt.Errorf("%s field not found", cadenceFieldNameTag) - } - - converted, err := decodeFieldValue(fieldValue.Type(), value) - if err != nil { - return fmt.Errorf( - "cannot convert Cadence field %s into Go field %s: %w", - cadenceFieldNameTag, - structField.Name, - err, - ) - } - - fieldValue.Set(converted) + _, err := decodeStructInto(v, targetType, composite) + if err != nil { + return err } return nil @@ -553,6 +524,10 @@ func decodeFieldValue(targetType reflect.Type, value Value) (reflect.Value, erro decodeSpecialFieldFunc = decodeDict case reflect.Array, reflect.Slice: decodeSpecialFieldFunc = decodeSlice + case reflect.Struct: + if !targetType.Implements(reflect.TypeOf((*Value)(nil)).Elem()) { + decodeSpecialFieldFunc = decodeStruct + } } var reflectedValue reflect.Value @@ -709,6 +684,61 @@ func decodeSlice(arrayTargetType reflect.Type, cadenceValue Value) (reflect.Valu return arrayValue, nil } +func decodeStruct(structTargetType reflect.Type, cadenceValue Value) (reflect.Value, error) { + structValue := reflect.New(structTargetType) + return decodeStructInto(structValue.Elem(), structTargetType, cadenceValue) +} + +func decodeStructInto( + structValue reflect.Value, + structTargetType reflect.Type, + cadenceValue Value, +) (reflect.Value, error) { + composite, ok := cadenceValue.(Composite) + if !ok { + return reflect.Value{}, fmt.Errorf( + "cannot decode non-Cadence composite %T to Go struct", + cadenceValue, + ) + } + + fieldsMap := FieldsMappedByName(composite) + + for i := 0; i < structValue.NumField(); i++ { + structField := structTargetType.Field(i) + tag := structField.Tag + fieldValue := structValue.Field(i) + + cadenceFieldNameTag := tag.Get("cadence") + if cadenceFieldNameTag == "" { + continue + } + + if !fieldValue.IsValid() || !fieldValue.CanSet() { + return reflect.Value{}, fmt.Errorf("cannot set field %s", structField.Name) + } + + value := fieldsMap[cadenceFieldNameTag] + if value == nil { + return reflect.Value{}, fmt.Errorf("%s field not found", cadenceFieldNameTag) + } + + converted, err := decodeFieldValue(fieldValue.Type(), value) + if err != nil { + return reflect.Value{}, fmt.Errorf( + "cannot convert Cadence field %s into Go field %s: %w", + cadenceFieldNameTag, + structField.Name, + err, + ) + } + + fieldValue.Set(converted) + } + + return structValue, nil +} + // Parameter type Parameter struct { diff --git a/types_test.go b/types_test.go index d42f9b84be..382a2f5371 100644 --- a/types_test.go +++ b/types_test.go @@ -2284,6 +2284,19 @@ func TestDecodeFields(t *testing.T) { NewDictionary([]KeyValuePair{ {Key: UInt8(42), Value: UInt8(24)}, }), + NewStruct([]Value{ + NewInt(42), + }).WithType(NewStructType( + utils.TestLocation, + "NestedStruct", + []Field{ + { + Identifier: "intField", + Type: IntType, + }, + }, + nil, + )), }, ).WithType(NewEventType( utils.TestLocation, @@ -2408,10 +2421,18 @@ func TestDecodeFields(t *testing.T) { ElementType: UInt8Type, }, }, + { + Identifier: "goUint8Struct", + Type: AnyStructType, + }, }, nil, )) + type nestedStruct struct { + Int Int `cadence:"intField"` + } + type eventStruct struct { Int Int `cadence:"intField"` String String `cadence:"stringField"` @@ -2433,6 +2454,7 @@ func TestDecodeFields(t *testing.T) { GoUint8PtrSome *uint8 `cadence:"goUint8PtrSome"` GoUint8Slice []uint8 `cadence:"goUint8Slice"` GoUint8Map map[uint8]uint8 `cadence:"goUint8Map"` + GoUint8Struct nestedStruct `cadence:"goUint8Struct"` NonCadenceField Int } @@ -2491,6 +2513,7 @@ func TestDecodeFields(t *testing.T) { assert.Equal(t, &expectedUint8, evt.GoUint8PtrSome) assert.Equal(t, []uint8{4, 2}, evt.GoUint8Slice) assert.Equal(t, map[uint8]uint8{42: 24}, evt.GoUint8Map) + assert.Equal(t, NewInt(42), evt.GoUint8Struct.Int) type ErrCases struct { Value interface{}