diff --git a/fluent/toInterfaceValue.go b/fluent/toInterfaceValue.go new file mode 100644 index 00000000..b9ed8beb --- /dev/null +++ b/fluent/toInterfaceValue.go @@ -0,0 +1,71 @@ +package fluent + +import ( + "errors" + + "github.com/ipld/go-ipld-prime/datamodel" +) + +var errInvalidKind = errors.New("invalid kind") +var errUnknownKind = errors.New("unknown kind") + +// ToInterface converts an IPLD node to its simplest equivalent Go value. +// +// Booleans, integers, floats, strings, bytes, and links are returned as themselves, +// as per the node's AsT method. Note that nulls are returned as untyped nils. +// +// Lists and maps are returned as []interface{} and map[string]interface{}, respectively. +func ToInterface(node datamodel.Node) (interface{}, error) { + switch k := node.Kind(); k { + case datamodel.Kind_Invalid: + return nil, errInvalidKind + case datamodel.Kind_Null: + return nil, nil + case datamodel.Kind_Bool: + return node.AsBool() + case datamodel.Kind_Int: + return node.AsInt() + case datamodel.Kind_Float: + return node.AsFloat() + case datamodel.Kind_String: + return node.AsString() + case datamodel.Kind_Bytes: + return node.AsBytes() + case datamodel.Kind_Link: + return node.AsLink() + case datamodel.Kind_Map: + outMap := make(map[string]interface{}, node.Length()) + for mi := node.MapIterator(); !mi.Done(); { + k, v, err := mi.Next() + if err != nil { + return nil, err + } + kVal, err := k.AsString() + if err != nil { + return nil, err + } + vVal, err := ToInterface(v) + if err != nil { + return nil, err + } + outMap[kVal] = vVal + } + return outMap, nil + case datamodel.Kind_List: + outList := make([]interface{}, 0, node.Length()) + for li := node.ListIterator(); !li.Done(); { + _, v, err := li.Next() + if err != nil { + return nil, err + } + vVal, err := ToInterface(v) + if err != nil { + return nil, err + } + outList = append(outList, vVal) + } + return outList, nil + default: + return nil, errUnknownKind + } +} diff --git a/fluent/toInterfaceValue_test.go b/fluent/toInterfaceValue_test.go new file mode 100644 index 00000000..9e0b3c7e --- /dev/null +++ b/fluent/toInterfaceValue_test.go @@ -0,0 +1,47 @@ +package fluent_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/ipfs/go-cid" + "github.com/ipld/go-ipld-prime/fluent" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/node/basicnode" +) + +var roundTripTestCases = []struct { + name string + value interface{} +}{ + {name: "Number", value: int64(100)}, + {name: "String", value: "hi"}, + {name: "Bool", value: true}, + {name: "Bytes", value: []byte("hi")}, + {name: "Map", value: map[string]interface{}{"a": "1", "b": int64(2), "c": 3.14, "d": true}}, + {name: "Array", value: []interface{}{"a", "b", "c"}}, + {name: "Nil", value: nil}, +} + +func TestRoundTrip(t *testing.T) { + for _, testCase := range roundTripTestCases { + t.Run(testCase.name, func(t *testing.T) { + c := qt.New(t) + n, err := fluent.Reflect(basicnode.Prototype.Any, testCase.value) + c.Assert(err, qt.IsNil) + out, err := fluent.ToInterface(n) + c.Assert(err, qt.IsNil) + c.Check(out, qt.DeepEquals, testCase.value) + }) + } +} + +func TestLink(t *testing.T) { + c := qt.New(t) + someCid, err := cid.Parse("bafybeihrqe2hmfauph5yfbd6ucv7njqpiy4tvbewlvhzjl4bhnyiu6h7pm") + c.Assert(err, qt.IsNil) + link := cidlink.Link{Cid: someCid} + v, err := fluent.ToInterface(basicnode.NewLink(link)) + c.Assert(err, qt.IsNil) + c.Assert(v.(cidlink.Link), qt.Equals, link) +}