Skip to content

Commit

Permalink
Merge pull request #3469 from onflow/bastian/improve-decode-fields
Browse files Browse the repository at this point in the history
Improve Cadence composite to Go struct decoding
  • Loading branch information
turbolent authored Jul 16, 2024
2 parents 3610da6 + 3fabbac commit 5de98cd
Show file tree
Hide file tree
Showing 2 changed files with 262 additions and 133 deletions.
298 changes: 176 additions & 122 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,185 +504,239 @@ 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)
}

cadenceField := fieldsMap[cadenceFieldNameTag]
if cadenceField == nil {
return fmt.Errorf("%s field not found", cadenceFieldNameTag)
}
_, err := decodeStructInto(v, targetType, composite)
if err != nil {
return err
}

cadenceFieldValue := reflect.ValueOf(cadenceField)
return nil
}

var decodeSpecialFieldFunc func(p reflect.Type, value Value) (*reflect.Value, error)
func decodeFieldValue(targetType reflect.Type, value Value) (reflect.Value, error) {
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
switch targetType.Kind() {
case reflect.Ptr:
decodeSpecialFieldFunc = decodeOptional
case reflect.Map:
decodeSpecialFieldFunc = decodeDict
case reflect.Array, reflect.Slice:
decodeSpecialFieldFunc = decodeSlice
case reflect.Struct:
if !targetType.Implements(reflect.TypeOf((*Value)(nil)).Elem()) {
decodeSpecialFieldFunc = decodeStruct
}
}

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)
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,
)
}
cadenceFieldValue = *cadenceFieldValuePtr
}
} else {
reflectedValue = reflect.ValueOf(value)
}

if !cadenceFieldValue.CanConvert(fieldValue.Type()) {
return fmt.Errorf(
"cannot convert cadence field %s of type %s to struct field %s of type %s",
cadenceFieldNameTag,
cadenceField.Type().ID(),
structField.Name,
fieldValue.Type(),
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,
)
}

fieldValue.Set(cadenceFieldValue.Convert(fieldValue.Type()))
}

return nil
return reflectedValue.Convert(targetType), nil
}

func decodeOptional(valueType reflect.Type, cadenceField Value) (*reflect.Value, error) {
optional, ok := cadenceField.(Optional)
func decodeOptional(pointerTargetType reflect.Type, cadenceValue Value) (reflect.Value, error) {
cadenceOptional, ok := cadenceValue.(Optional)
if !ok {
return nil, fmt.Errorf("field is not an optional")
}

// if optional is nil, skip and default the field to nil
if optional.Value == nil {
zeroValue := reflect.Zero(valueType)
return &zeroValue, nil
return reflect.Value{}, fmt.Errorf("field is not an optional")
}

optionalValue := reflect.ValueOf(optional.Value)

// 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())
// If Cadence optional is nil, skip and default the field to Go nil
cadenceInnerValue := cadenceOptional.Value
if cadenceInnerValue == nil {
return reflect.Zero(pointerTargetType), nil
}

if valueType.Elem().Kind() == reflect.Interface {
newInterfaceVal := reflect.New(reflect.TypeOf((*interface{})(nil)).Elem())
newInterfaceVal.Elem().Set(optionalValue)
// 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
}

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
Expand Down
Loading

0 comments on commit 5de98cd

Please sign in to comment.