From a46c2b1e2ba29515ce6b05cc7d69b8ba2618ccad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 15 Jul 2024 18:40:28 -0700 Subject: [PATCH 1/2] improve Cadence composite to Go composite decoding allow nested types, improve errors --- types.go | 230 ++++++++++++++++++++++++++++---------------------- types_test.go | 74 +++++++++++++--- 2 files changed, 190 insertions(+), 114 deletions(-) diff --git a/types.go b/types.go index c3cee3c263..a957b549bb 100644 --- a/types.go +++ b/types.go @@ -522,167 +522,191 @@ func DecodeFields(composite Composite, s interface{}) error { return fmt.Errorf("cannot set field %s", structField.Name) } - cadenceField := fieldsMap[cadenceFieldNameTag] - if cadenceField == nil { + value := fieldsMap[cadenceFieldNameTag] + if value == nil { return fmt.Errorf("%s field not found", cadenceFieldNameTag) } - cadenceFieldValue := reflect.ValueOf(cadenceField) - - var decodeSpecialFieldFunc func(p reflect.Type, value Value) (*reflect.Value, error) - - switch fieldValue.Kind() { - case reflect.Ptr: - decodeSpecialFieldFunc = decodeOptional - case reflect.Map: - decodeSpecialFieldFunc = decodeDict - case reflect.Array, reflect.Slice: - decodeSpecialFieldFunc = decodeSlice - } - - if decodeSpecialFieldFunc != nil { - cadenceFieldValuePtr, err := decodeSpecialFieldFunc(fieldValue.Type(), cadenceField) - if err != nil { - return fmt.Errorf("cannot decode %s field %s: %w", fieldValue.Kind(), structField.Name, err) - } - cadenceFieldValue = *cadenceFieldValuePtr - } - - if !cadenceFieldValue.CanConvert(fieldValue.Type()) { + converted, err := decodeFieldValue(fieldValue.Type(), value) + if err != nil { return fmt.Errorf( - "cannot convert cadence field %s of type %s to struct field %s of type %s", + "cannot convert Cadence field %s into Go field %s: %w", cadenceFieldNameTag, - cadenceField.Type().ID(), structField.Name, - fieldValue.Type(), + err, ) } - fieldValue.Set(cadenceFieldValue.Convert(fieldValue.Type())) + fieldValue.Set(converted) } return nil } -func decodeOptional(valueType reflect.Type, cadenceField Value) (*reflect.Value, error) { - optional, ok := cadenceField.(Optional) - if !ok { - return nil, fmt.Errorf("field is not an optional") +func decodeFieldValue(targetType reflect.Type, value Value) (reflect.Value, error) { + var decodeSpecialFieldFunc func(p reflect.Type, value Value) (reflect.Value, error) + + switch targetType.Kind() { + case reflect.Ptr: + decodeSpecialFieldFunc = decodeOptional + case reflect.Map: + decodeSpecialFieldFunc = decodeDict + case reflect.Array, reflect.Slice: + decodeSpecialFieldFunc = decodeSlice + } + + var reflectedValue reflect.Value + + if decodeSpecialFieldFunc != nil { + var err error + reflectedValue, err = decodeSpecialFieldFunc(targetType, value) + if err != nil { + ty := value.Type() + if ty == nil { + return reflect.Value{}, fmt.Errorf( + "cannot convert Cadence value to Go type %s: %w", + targetType, + err, + ) + } else { + return reflect.Value{}, fmt.Errorf( + "cannot convert Cadence value of type %s to Go type %s: %w", + ty.ID(), + targetType, + err, + ) + } + } + } else { + reflectedValue = reflect.ValueOf(value) } - // if optional is nil, skip and default the field to nil - if optional.Value == nil { - zeroValue := reflect.Zero(valueType) - return &zeroValue, nil + if !reflectedValue.CanConvert(targetType) { + ty := value.Type() + if ty == nil { + return reflect.Value{}, fmt.Errorf( + "cannot convert Cadence value to Go type %s", + targetType, + ) + } else { + return reflect.Value{}, fmt.Errorf( + "cannot convert Cadence value of type %s to Go type %s", + ty.ID(), + targetType, + ) + } } - optionalValue := reflect.ValueOf(optional.Value) + return reflectedValue.Convert(targetType), nil +} - // Check the type - if valueType.Elem() != optionalValue.Type() && valueType.Elem().Kind() != reflect.Interface { - return nil, fmt.Errorf("cannot set field: expected %v, got %v", - valueType.Elem(), optionalValue.Type()) +func decodeOptional(pointerTargetType reflect.Type, cadenceValue Value) (reflect.Value, error) { + cadenceOptional, ok := cadenceValue.(Optional) + if !ok { + return reflect.Value{}, fmt.Errorf("field is not an optional") } - if valueType.Elem().Kind() == reflect.Interface { - newInterfaceVal := reflect.New(reflect.TypeOf((*interface{})(nil)).Elem()) - newInterfaceVal.Elem().Set(optionalValue) + // If Cadence optional is nil, skip and default the field to Go nil + cadenceInnerValue := cadenceOptional.Value + if cadenceInnerValue == nil { + return reflect.Zero(pointerTargetType), nil + } + + // Create a new pointer + newPtr := reflect.New(pointerTargetType.Elem()) - return &newInterfaceVal, nil + innerValue, err := decodeFieldValue( + pointerTargetType.Elem(), + cadenceInnerValue, + ) + if err != nil { + return reflect.Value{}, fmt.Errorf( + "cannot decode optional value: %w", + err, + ) } - // Create a new pointer for optionalValue - newPtr := reflect.New(optionalValue.Type()) - newPtr.Elem().Set(optionalValue) + newPtr.Elem().Set(innerValue) - return &newPtr, nil + return newPtr, nil } -func decodeDict(valueType reflect.Type, cadenceField Value) (*reflect.Value, error) { - dict, ok := cadenceField.(Dictionary) +func decodeDict(mapTargetType reflect.Type, cadenceValue Value) (reflect.Value, error) { + cadenceDictionary, ok := cadenceValue.(Dictionary) if !ok { - return nil, fmt.Errorf("field is not a dictionary") + return reflect.Value{}, fmt.Errorf( + "cannot decode non-Cadence dictionary %T to Go map", + cadenceValue, + ) } - mapKeyType := valueType.Key() - mapValueType := valueType.Elem() + keyTargetType := mapTargetType.Key() + valueTargetType := mapTargetType.Elem() - mapValue := reflect.MakeMap(valueType) - for _, pair := range dict.Pairs { + mapValue := reflect.MakeMap(mapTargetType) - // Convert key and value to their Go counterparts - var key, value reflect.Value - if mapKeyType.Kind() == reflect.Ptr { - return nil, fmt.Errorf("map key cannot be a pointer (optional) type") - } - key = reflect.ValueOf(pair.Key) + for _, pair := range cadenceDictionary.Pairs { - if mapValueType.Kind() == reflect.Ptr { - // If the map value is a pointer type, unwrap it from optional - valueOptional, err := decodeOptional(mapValueType, pair.Value) - if err != nil { - return nil, fmt.Errorf("cannot decode optional map value for key %s: %w", pair.Key.String(), err) - } - value = *valueOptional - } else { - value = reflect.ValueOf(pair.Value) + key, err := decodeFieldValue(keyTargetType, pair.Key) + if err != nil { + return reflect.Value{}, fmt.Errorf( + "cannot decode dictionary key: %w", + err, + ) } - if mapKeyType != key.Type() { - return nil, fmt.Errorf("map key type mismatch: expected %v, got %v", mapKeyType, key.Type()) - } - if mapValueType != value.Type() && mapValueType.Kind() != reflect.Interface { - return nil, fmt.Errorf("map value type mismatch: expected %v, got %v", mapValueType, value.Type()) + value, err := decodeFieldValue(valueTargetType, pair.Value) + if err != nil { + return reflect.Value{}, fmt.Errorf( + "cannot decode dictionary value: %w", + err, + ) } - // Add key-value pair to the map mapValue.SetMapIndex(key, value) } - return &mapValue, nil + return mapValue, nil } -func decodeSlice(valueType reflect.Type, cadenceField Value) (*reflect.Value, error) { - array, ok := cadenceField.(Array) +func decodeSlice(arrayTargetType reflect.Type, cadenceValue Value) (reflect.Value, error) { + cadenceArray, ok := cadenceValue.(Array) if !ok { - return nil, fmt.Errorf("field is not an array") + return reflect.Value{}, fmt.Errorf( + "cannot decode non-Cadence array %T to Go slice", + cadenceValue, + ) } + elementTargetType := arrayTargetType.Elem() + var arrayValue reflect.Value - constantSizeArray, ok := array.ArrayType.(*ConstantSizedArrayType) + cadenceConstantSizeArrayType, ok := cadenceArray.ArrayType.(*ConstantSizedArrayType) if ok { - arrayValue = reflect.New(reflect.ArrayOf(int(constantSizeArray.Size), valueType.Elem())).Elem() + // If the Cadence array is constant-sized, create a Go array + size := int(cadenceConstantSizeArrayType.Size) + arrayValue = reflect.New(reflect.ArrayOf(size, elementTargetType)).Elem() } else { - // If the array is not constant sized, create a slice - arrayValue = reflect.MakeSlice(valueType, len(array.Values), len(array.Values)) + // If the Cadence array is not constant-sized, create a Go slice + size := len(cadenceArray.Values) + arrayValue = reflect.MakeSlice(arrayTargetType, size, size) } - for i, value := range array.Values { - var elementValue reflect.Value - if valueType.Elem().Kind() == reflect.Ptr { - // If the array value is a pointer type, unwrap it from optional - valueOptional, err := decodeOptional(valueType.Elem(), value) - if err != nil { - return nil, fmt.Errorf("error decoding array element optional: %w", err) - } - elementValue = *valueOptional - } else { - elementValue = reflect.ValueOf(value) - } - if elementValue.Type() != valueType.Elem() && valueType.Elem().Kind() != reflect.Interface { - return nil, fmt.Errorf( - "array element type mismatch at index %d: expected %v, got %v", + for i, cadenceElement := range cadenceArray.Values { + elementValue, err := decodeFieldValue(elementTargetType, cadenceElement) + if err != nil { + return reflect.Value{}, fmt.Errorf( + "cannot decode array element %d: %w", i, - valueType.Elem(), - elementValue.Type(), + err, ) } arrayValue.Index(i).Set(elementValue) } - return &arrayValue, nil + return arrayValue, nil } // Parameter diff --git a/types_test.go b/types_test.go index f13bb96b7a..d42f9b84be 100644 --- a/types_test.go +++ b/types_test.go @@ -2271,6 +2271,19 @@ func TestDecodeFields(t *testing.T) { NewOptional(nil), }, }, + UInt8(42), + NewOptional(nil), + NewOptional(UInt8(42)), + Array{ + ArrayType: NewVariableSizedArrayType(UInt8Type), + Values: []Value{ + UInt8(4), + UInt8(2), + }, + }, + NewDictionary([]KeyValuePair{ + {Key: UInt8(42), Value: UInt8(24)}, + }), }, ).WithType(NewEventType( utils.TestLocation, @@ -2368,6 +2381,33 @@ func TestDecodeFields(t *testing.T) { }, ), }, + { + Identifier: "goUint8", + Type: UInt8Type, + }, + { + Identifier: "goUint8PtrNil", + Type: &OptionalType{ + Type: UInt8Type, + }, + }, + { + Identifier: "goUint8PtrSome", + Type: &OptionalType{ + Type: UInt8Type, + }, + }, + { + Identifier: "goUint8Slice", + Type: NewVariableSizedArrayType(UInt8Type), + }, + { + Identifier: "goUint8Map", + Type: &DictionaryType{ + KeyType: UInt8Type, + ElementType: UInt8Type, + }, + }, }, nil, )) @@ -2388,6 +2428,11 @@ func TestDecodeFields(t *testing.T) { FixedArrayInt [2]Int `cadence:"fixedArrayIntField"` VariableArrayAnyStruct []interface{} `cadence:"variableArrayAnyStructField"` VariableArrayOptionalAnyStruct []*interface{} `cadence:"variableArrayOptionalAnyStructField"` + GoUint8 uint8 `cadence:"goUint8"` + GoUint8PtrNil *uint8 `cadence:"goUint8PtrNil"` + GoUint8PtrSome *uint8 `cadence:"goUint8PtrSome"` + GoUint8Slice []uint8 `cadence:"goUint8Slice"` + GoUint8Map map[uint8]uint8 `cadence:"goUint8Map"` NonCadenceField Int } @@ -2440,6 +2485,13 @@ func TestDecodeFields(t *testing.T) { require.NotNil(t, evt.DictOptionalAnyStruct["k2"]) assert.Nil(t, evt.DictOptionalAnyStruct["nilK"]) + assert.Equal(t, uint8(42), evt.GoUint8) + assert.Equal(t, (*uint8)(nil), evt.GoUint8PtrNil) + expectedUint8 := uint8(42) + assert.Equal(t, &expectedUint8, evt.GoUint8PtrSome) + assert.Equal(t, []uint8{4, 2}, evt.GoUint8Slice) + assert.Equal(t, map[uint8]uint8{42: 24}, evt.GoUint8Map) + type ErrCases struct { Value interface{} ExpectedErr string @@ -2488,7 +2540,7 @@ func TestDecodeFields(t *testing.T) { {Value: &struct { A String `cadence:"intField"` }{}, - ExpectedErr: "cannot convert cadence field intField of type Int to struct field A of type cadence.String", + ExpectedErr: "cannot convert Cadence field intField into Go field A: cannot convert Cadence value of type Int to Go type cadence.String", Description: "should err when mapping to invalid type", }, {Value: &struct { @@ -2506,61 +2558,61 @@ func TestDecodeFields(t *testing.T) { {Value: &struct { O *String `cadence:"optionalIntField"` }{}, - ExpectedErr: "cannot decode ptr field O: cannot set field: expected cadence.String, got cadence.Int", + ExpectedErr: "cannot convert Cadence field optionalIntField into Go field O: cannot convert Cadence value of type (Int)? to Go type *cadence.String: cannot decode optional value: cannot convert Cadence value of type Int to Go type cadence.String", Description: "should err when mapping to optional field with wrong type", }, {Value: &struct { DOptional map[*String]*Int `cadence:"dictOptionalField"` }{}, - ExpectedErr: "cannot decode map field DOptional: map key cannot be a pointer (optional) type", + ExpectedErr: "cannot convert Cadence field dictOptionalField into Go field DOptional: cannot convert Cadence value to Go type map[*cadence.String]*cadence.Int: cannot decode dictionary key: cannot convert Cadence value of type String to Go type *cadence.String: field is not an optional", Description: "should err when mapping to dictionary field with ptr key type", }, {Value: &struct { D map[String]String `cadence:"dictField"` }{}, - ExpectedErr: "cannot decode map field D: map value type mismatch: expected cadence.String, got cadence.Int", + ExpectedErr: "cannot convert Cadence field dictField into Go field D: cannot convert Cadence value to Go type map[cadence.String]cadence.String: cannot decode dictionary value: cannot convert Cadence value of type Int to Go type cadence.String", Description: "should err when mapping to dictionary field with wrong value type", }, {Value: &struct { A []String `cadence:"intField"` }{}, - ExpectedErr: "cannot decode slice field A: field is not an array", + ExpectedErr: "cannot convert Cadence field intField into Go field A: cannot convert Cadence value of type Int to Go type []cadence.String: cannot decode non-Cadence array cadence.Int to Go slice", Description: "should err when mapping to array field with wrong type", }, {Value: &struct { A []String `cadence:"variableArrayIntField"` }{}, - ExpectedErr: "cannot decode slice field A: array element type mismatch at index 0: expected cadence.String, got cadence.Int", + ExpectedErr: "cannot convert Cadence field variableArrayIntField into Go field A: cannot convert Cadence value of type [Int] to Go type []cadence.String: cannot decode array element 0: cannot convert Cadence value of type Int to Go type cadence.String", Description: "should err when mapping to array field with wrong element type", }, {Value: &struct { A []*String `cadence:"variableArrayOptionalIntField"` }{}, - ExpectedErr: "cannot decode slice field A: error decoding array element optional: cannot set field: expected cadence.String, got cadence.Int", + ExpectedErr: "cannot convert Cadence field variableArrayOptionalIntField into Go field A: cannot convert Cadence value of type [(Int)?] to Go type []*cadence.String: cannot decode array element 0: cannot convert Cadence value of type (Int)? to Go type *cadence.String: cannot decode optional value: cannot convert Cadence value of type Int to Go type cadence.String", Description: "should err when mapping to array field with wrong type", }, {Value: &struct { A map[Int]Int `cadence:"dictField"` }{}, - ExpectedErr: "cannot decode map field A: map key type mismatch: expected cadence.Int, got cadence.String", + ExpectedErr: "cannot convert Cadence field dictField into Go field A: cannot convert Cadence value to Go type map[cadence.Int]cadence.Int: cannot decode dictionary key: cannot convert Cadence value of type String to Go type cadence.Int", Description: "should err when mapping to map field with mismatching key type", }, {Value: &struct { A map[String]*String `cadence:"dictOptionalField"` }{}, - ExpectedErr: "cannot decode map field A: cannot decode optional map value for key \"k\": cannot set field: expected cadence.String, got cadence.Int", + ExpectedErr: "cannot convert Cadence field dictOptionalField into Go field A: cannot convert Cadence value to Go type map[cadence.String]*cadence.String: cannot decode dictionary value: cannot convert Cadence value of type (Int)? to Go type *cadence.String: cannot decode optional value: cannot convert Cadence value of type Int to Go type cadence.String", Description: "should err when mapping to map field with mismatching value type", }, {Value: &struct { A map[String]Int `cadence:"intField"` }{}, - ExpectedErr: "cannot decode map field A: field is not a dictionary", + ExpectedErr: "cannot convert Cadence field intField into Go field A: cannot convert Cadence value of type Int to Go type map[cadence.String]cadence.Int: cannot decode non-Cadence dictionary cadence.Int to Go map", Description: "should err when mapping to map with mismatching field type", }, {Value: &struct { A *Int `cadence:"intField"` }{}, - ExpectedErr: "cannot decode ptr field A: field is not an optional", + ExpectedErr: "cannot convert Cadence field intField into Go field A: cannot convert Cadence value of type Int to Go type *cadence.Int: field is not an optional", Description: "should err when mapping to optional field with mismatching type", }, } 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 2/2] 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{}