Skip to content

Commit

Permalink
Use reflection to parse values (#78)
Browse files Browse the repository at this point in the history
* stated on generic struct mapper

* added todos

* added better tests

* tested creating struct

* added struct tags

* fixing tests

* fixing final tests

* added example of Struct

* added more convenience to create structs from account

* move from camelCase to snake_case

* remove println

* remove the type since it can be derrived from value

* fixed nested struct support

* fixed NFT contract and added MetadataViews contract

* added metadata structs except royalties with caps and some shared ones

* remove WithStructArg

* added option to skip field in struct if we do not want it in cadence

* use json structtag if it is there and cadence is not there

* tidy
  • Loading branch information
bjartek authored Nov 2, 2022
1 parent 200c0d0 commit 032890b
Show file tree
Hide file tree
Showing 24 changed files with 1,502 additions and 296 deletions.
149 changes: 149 additions & 0 deletions cadence.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package overflow

import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"

"github.com/fatih/structtag"
"github.com/onflow/cadence"
)

Expand Down Expand Up @@ -113,3 +117,148 @@ func CadenceValueToInterface(field cadence.Value) interface{} {
return field.String()
}
}

// a resolver to resolve a input type into a name, can be used to resolve struct names for instance
type InputResolver func(string) (string, error)

func InputToCadence(v interface{}, resolver InputResolver) (cadence.Value, error) {
f := reflect.ValueOf(v)
return ReflectToCadence(f, resolver)
}

func ReflectToCadence(value reflect.Value, resolver InputResolver) (cadence.Value, error) {
inputType := value.Type()

kind := inputType.Kind()
switch kind {
case reflect.Interface:
return cadence.NewValue(value.Interface())
case reflect.Struct:
var val []cadence.Value
fields := []cadence.Field{}
for i := 0; i < value.NumField(); i++ {
fieldValue := value.Field(i)
cadenceVal, err := ReflectToCadence(fieldValue, resolver)
if err != nil {
return nil, err
}
cadenceType := cadenceVal.Type()

field := inputType.Field(i)

tags, err := structtag.Parse(string(field.Tag))
if err != nil {
return nil, err
}

name := ""
tag, err := tags.Get("cadence")
if err != nil {
tag, _ = tags.Get("json")
}
if tag != nil {
name = tag.Name
}

if name == "-" {
continue
}

if name == "" {
name = strings.ToLower(field.Name)
}

fields = append(fields, cadence.Field{
Identifier: name,
Type: cadenceType,
})

val = append(val, cadenceVal)
}

resolvedIdentifier, err := resolver(inputType.Name())
if err != nil {
return nil, err
}
structType := cadence.StructType{
QualifiedIdentifier: resolvedIdentifier,
Fields: fields,
}

structValue := cadence.NewStruct(val).WithType(&structType)
return structValue, nil

case reflect.Pointer:
if value.IsNil() {
return cadence.NewOptional(nil), nil
}

ptrValue, err := ReflectToCadence(value.Elem(), resolver)
if err != nil {
return nil, err
}
return cadence.NewOptional(ptrValue), nil

case reflect.Int:
return cadence.NewInt(value.Interface().(int)), nil
case reflect.Int8:
return cadence.NewInt8(value.Interface().(int8)), nil
case reflect.Int16:
return cadence.NewInt16(value.Interface().(int16)), nil
case reflect.Int32:
return cadence.NewInt32(value.Interface().(int32)), nil
case reflect.Int64:
return cadence.NewInt64(value.Interface().(int64)), nil
case reflect.Bool:
return cadence.NewBool(value.Interface().(bool)), nil
case reflect.Uint:
return cadence.NewUInt(value.Interface().(uint)), nil
case reflect.Uint8:
return cadence.NewUInt8(value.Interface().(uint8)), nil
case reflect.Uint16:
return cadence.NewUInt16(value.Interface().(uint16)), nil
case reflect.Uint32:
return cadence.NewUInt32(value.Interface().(uint32)), nil
case reflect.Uint64:
return cadence.NewUInt64(value.Interface().(uint64)), nil
case reflect.String:
result, err := cadence.NewString(value.Interface().(string))
return result, err
case reflect.Float64:
result, err := cadence.NewUFix64(fmt.Sprintf("%f", value.Interface().(float64)))
return result, err

case reflect.Map:
array := []cadence.KeyValuePair{}
iter := value.MapRange()

for iter.Next() {
key := iter.Key()
val := iter.Value()
cadenceKey, err := ReflectToCadence(key, resolver)
if err != nil {
return nil, err
}
cadenceVal, err := ReflectToCadence(val, resolver)
if err != nil {
return nil, err
}
array = append(array, cadence.KeyValuePair{Key: cadenceKey, Value: cadenceVal})
}
return cadence.NewDictionary(array), nil
case reflect.Slice, reflect.Array:
array := []cadence.Value{}
for i := 0; i < value.Len(); i++ {
arrValue := value.Index(i)
cadenceVal, err := ReflectToCadence(arrValue, resolver)
if err != nil {
return nil, err
}
array = append(array, cadenceVal)
}
return cadence.NewArray(array), nil

}

return nil, fmt.Errorf("Not supported type for now. Type : %s", inputType.Kind())
}
86 changes: 86 additions & 0 deletions cadence_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package overflow

import (
"encoding/json"
"fmt"
"testing"

"github.com/hexops/autogold"
Expand Down Expand Up @@ -96,5 +98,89 @@ func TestCadenceValueToJson(t *testing.T) {
result, err := CadenceValueToJsonString(cadence.String(""))
assert.NoError(t, err)
assert.Equal(t, "", result)
}

func TestParseInputValue(t *testing.T) {

foo := "foo"

var strPointer *string = nil
values := []interface{}{
"foo",
uint64(42),
map[string]uint64{"foo": uint64(42)},
[]uint64{42, 69},
[2]string{"foo", "bar"},
&foo,
strPointer,
}

for idx, value := range values {
t.Run(fmt.Sprintf("parse input %d", idx), func(t *testing.T) {
cv, err := InputToCadence(value, func(string) (string, error) {
return "", nil
})
assert.NoError(t, err)
v := CadenceValueToInterface(cv)

vj, err := json.Marshal(v)
assert.NoError(t, err)

cvj, err := json.Marshal(value)
assert.NoError(t, err)

assert.Equal(t, string(cvj), string(vj))
})
}

}

func TestMarshalCadenceStruct(t *testing.T) {

val, err := InputToCadence(Foo{Bar: "foo"}, func(string) (string, error) {
return "A.123.Foo.Bar", nil
})
assert.NoError(t, err)
assert.Equal(t, "A.123.Foo.Bar", val.Type().ID())
jsonVal, err := CadenceValueToJsonString(val)
assert.NoError(t, err)
assert.JSONEq(t, `{ "bar": "foo" }`, jsonVal)

}

func TestMarshalCadenceStructWithStructTag(t *testing.T) {

val, err := InputToCadence(Foo{Bar: "foo"}, func(string) (string, error) {
return "A.123.Foo.Baz", nil
})
assert.NoError(t, err)
assert.Equal(t, "A.123.Foo.Baz", val.Type().ID())
jsonVal, err := CadenceValueToJsonString(val)
assert.NoError(t, err)
assert.JSONEq(t, `{ "bar": "foo" }`, jsonVal)

}

// in Debug.cdc
type Foo struct {
Bar string
}

type Debug_FooBar struct {
Bar string
Foo Debug_Foo
}

type Debug_Foo_Skip struct {
Bar string
Skip string `cadence:"-"`
}

type Debug_Foo struct {
Bar string
}

// in Foo.Bar.Baz
type Baz struct {
Something string `json:"bar"`
}
9 changes: 9 additions & 0 deletions contracts/Debug.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import NonFungibleToken from "./NonFungibleToken.cdc"

pub contract Debug {

pub struct FooBar {
pub let foo:Foo
pub let bar:String

init(foo:Foo, bar:String) {
self.foo=foo
self.bar=bar
}
}

pub struct Foo{
pub let bar: String
Expand Down
Loading

0 comments on commit 032890b

Please sign in to comment.