From 77c4f6db146d65e8a2cbcbd74ff15fcacadb66ca Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 18 Nov 2021 14:53:09 -0800 Subject: [PATCH 1/5] Add toInterfaceValue --- fluent/toInterfaceValue.go | 67 +++++++++++++++++++++++++++++++++ fluent/toInterfaceValue_test.go | 54 ++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 fluent/toInterfaceValue.go create mode 100644 fluent/toInterfaceValue_test.go diff --git a/fluent/toInterfaceValue.go b/fluent/toInterfaceValue.go new file mode 100644 index 00000000..8ad78398 --- /dev/null +++ b/fluent/toInterfaceValue.go @@ -0,0 +1,67 @@ +package fluent + +import ( + "github.com/ipld/go-ipld-prime/datamodel" +) + +func ToInterfaceValue(node datamodel.Node) (interface{}, error) { + switch k := node.Kind(); k { + case datamodel.Kind_Invalid: + panic("invalid node") + 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[interface{}]interface{}) + for mi := node.MapIterator(); !mi.Done(); { + k, v, err := mi.Next() + if err != nil { + return nil, err + } + kVal, err := ToInterfaceValue(k) + if err != nil { + return nil, err + } + vVal, err := ToInterfaceValue(v) + if err != nil { + return nil, err + } + outMap[kVal] = vVal + + if mi.Done() { + break + } + } + 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 := ToInterfaceValue(v) + if err != nil { + return nil, err + } + outList = append(outList, vVal) + + if li.Done() { + break + } + } + return outList, nil + } + panic("unhandled case in switch") +} diff --git a/fluent/toInterfaceValue_test.go b/fluent/toInterfaceValue_test.go new file mode 100644 index 00000000..1f498a4b --- /dev/null +++ b/fluent/toInterfaceValue_test.go @@ -0,0 +1,54 @@ +package fluent_test + +import ( + "testing" + + "github.com/ipld/go-ipld-prime/fluent" + "github.com/ipld/go-ipld-prime/node/basicnode" +) + +func TestListValue(t *testing.T) { + a := []string{"a", "b", "c"} + n, err := fluent.Reflect(basicnode.Prototype.Any, a) + if err != nil { + t.Fatal(err) + } + out, err := fluent.ToInterfaceValue(n) + if err != nil { + t.Fatal(err) + } + outArr := out.([]interface{}) + + if len(a) != len(outArr) { + t.Errorf("Mismatch in array size") + } + + for i, v := range outArr { + if a[i] != v { + t.Errorf("expected %v, got %v at index %v", a[i], v, i) + } + } +} + +func TestMapValue(t *testing.T) { + a := map[string]interface{}{"a": "1", "b": int64(2), "c": 3.14} + n, err := fluent.Reflect(basicnode.Prototype.Any, a) + if err != nil { + t.Fatal(err) + } + out, err := fluent.ToInterfaceValue(n) + if err != nil { + t.Fatal(err) + } + outM := out.(map[interface{}]interface{}) + + if len(a) != len(outM) { + t.Errorf("Mismatch in size") + } + + for k, v := range outM { + if v != a[k.(string)] { + t.Errorf("expected %v, got %v at key %v", a[k.(string)], v, k) + } + } +} From 540fce63c51b3a190406241999ef6723ba6f1fda Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 18 Nov 2021 15:06:32 -0800 Subject: [PATCH 2/5] Only string keys --- fluent/toInterfaceValue.go | 4 ++-- fluent/toInterfaceValue_test.go | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fluent/toInterfaceValue.go b/fluent/toInterfaceValue.go index 8ad78398..847b3b20 100644 --- a/fluent/toInterfaceValue.go +++ b/fluent/toInterfaceValue.go @@ -23,13 +23,13 @@ func ToInterfaceValue(node datamodel.Node) (interface{}, error) { case datamodel.Kind_Link: return node.AsLink() case datamodel.Kind_Map: - outMap := make(map[interface{}]interface{}) + outMap := make(map[string]interface{}) for mi := node.MapIterator(); !mi.Done(); { k, v, err := mi.Next() if err != nil { return nil, err } - kVal, err := ToInterfaceValue(k) + kVal, err := k.AsString() if err != nil { return nil, err } diff --git a/fluent/toInterfaceValue_test.go b/fluent/toInterfaceValue_test.go index 1f498a4b..19a16a37 100644 --- a/fluent/toInterfaceValue_test.go +++ b/fluent/toInterfaceValue_test.go @@ -40,15 +40,16 @@ func TestMapValue(t *testing.T) { if err != nil { t.Fatal(err) } - outM := out.(map[interface{}]interface{}) + outM := out.(map[string]interface{}) if len(a) != len(outM) { t.Errorf("Mismatch in size") } for k, v := range outM { - if v != a[k.(string)] { - t.Errorf("expected %v, got %v at key %v", a[k.(string)], v, k) + if v != a[k] { + t.Errorf("expected %v, got %v at key %v", a[k], v, k) } } + } From d51989aa49e597011d5e3a3c8164e1e048e3e153 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 29 Nov 2021 19:19:42 -0800 Subject: [PATCH 3/5] Add better tests and don't panic --- fluent/toInterfaceValue.go | 26 ++++++----- fluent/toInterfaceValue_test.go | 76 ++++++++++++++++----------------- 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/fluent/toInterfaceValue.go b/fluent/toInterfaceValue.go index 847b3b20..1200dbf0 100644 --- a/fluent/toInterfaceValue.go +++ b/fluent/toInterfaceValue.go @@ -1,13 +1,18 @@ package fluent import ( + "errors" + "github.com/ipld/go-ipld-prime/datamodel" ) -func ToInterfaceValue(node datamodel.Node) (interface{}, error) { +var ErrInvalidKind = errors.New("invalid kind") +var ErrUnknownKind = errors.New("unknown kind") + +func ToInterface(node datamodel.Node) (interface{}, error) { switch k := node.Kind(); k { case datamodel.Kind_Invalid: - panic("invalid node") + return nil, ErrInvalidKind case datamodel.Kind_Null: return nil, nil case datamodel.Kind_Bool: @@ -23,7 +28,7 @@ func ToInterfaceValue(node datamodel.Node) (interface{}, error) { case datamodel.Kind_Link: return node.AsLink() case datamodel.Kind_Map: - outMap := make(map[string]interface{}) + outMap := make(map[string]interface{}, node.Length()) for mi := node.MapIterator(); !mi.Done(); { k, v, err := mi.Next() if err != nil { @@ -33,15 +38,11 @@ func ToInterfaceValue(node datamodel.Node) (interface{}, error) { if err != nil { return nil, err } - vVal, err := ToInterfaceValue(v) + vVal, err := ToInterface(v) if err != nil { return nil, err } outMap[kVal] = vVal - - if mi.Done() { - break - } } return outMap, nil case datamodel.Kind_List: @@ -51,17 +52,14 @@ func ToInterfaceValue(node datamodel.Node) (interface{}, error) { if err != nil { return nil, err } - vVal, err := ToInterfaceValue(v) + vVal, err := ToInterface(v) if err != nil { return nil, err } outList = append(outList, vVal) - - if li.Done() { - break - } } return outList, nil + default: + return nil, ErrUnknownKind } - panic("unhandled case in switch") } diff --git a/fluent/toInterfaceValue_test.go b/fluent/toInterfaceValue_test.go index 19a16a37..ffd9aa8d 100644 --- a/fluent/toInterfaceValue_test.go +++ b/fluent/toInterfaceValue_test.go @@ -1,55 +1,51 @@ package fluent_test import ( + "encoding/json" "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" ) -func TestListValue(t *testing.T) { - a := []string{"a", "b", "c"} - n, err := fluent.Reflect(basicnode.Prototype.Any, a) - if err != nil { - t.Fatal(err) - } - out, err := fluent.ToInterfaceValue(n) - if err != nil { - t.Fatal(err) - } - outArr := out.([]interface{}) - - if len(a) != len(outArr) { - t.Errorf("Mismatch in array size") - } - - for i, v := range outArr { - if a[i] != v { - t.Errorf("expected %v, got %v at index %v", a[i], v, i) - } - } +var roundTripTestCases = []struct { + desc string + value interface{} +}{ + {desc: "Number", value: 100}, + {desc: "String", value: "hi"}, + {desc: "Bool", value: "hi"}, + {desc: "Bytes", value: []byte("hi")}, + {desc: "Map", value: map[string]interface{}{"a": "1", "b": int64(2), "c": 3.14, "d": true}}, + {desc: "Array", value: []string{"a", "b", "c"}}, } -func TestMapValue(t *testing.T) { - a := map[string]interface{}{"a": "1", "b": int64(2), "c": 3.14} - n, err := fluent.Reflect(basicnode.Prototype.Any, a) - if err != nil { - t.Fatal(err) - } - out, err := fluent.ToInterfaceValue(n) - if err != nil { - t.Fatal(err) - } - outM := out.(map[string]interface{}) - - if len(a) != len(outM) { - t.Errorf("Mismatch in size") +func TestRoundTrip(t *testing.T) { + for _, testCase := range roundTripTestCases { + t.Run(testCase.desc, 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) + outJson, err := json.Marshal(out) + qt.Check(t, err, qt.IsNil) + qt.Check(t, outJson, qt.JSONEquals, testCase.value) + }) } +} - for k, v := range outM { - if v != a[k] { - t.Errorf("expected %v, got %v at key %v", a[k], v, k) - } - } +func TestLink(t *testing.T) { + c := qt.New(t) + var someCid, err = cid.Parse("bafybeihrqe2hmfauph5yfbd6ucv7njqpiy4tvbewlvhzjl4bhnyiu6h7pm") + c.Assert(err, qt.IsNil) + var link = cidlink.Link{Cid: someCid} + var node = basicnode.NewLink(link) + v, err := fluent.ToInterface(node) + c.Assert(err, qt.IsNil) + c.Assert(v.(cidlink.Link), qt.Equals, link) } From 86281112ec8215913709885127f605c5305c97c6 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 30 Nov 2021 07:08:15 -0800 Subject: [PATCH 4/5] PR Comments --- fluent/toInterfaceValue.go | 8 ++++---- fluent/toInterfaceValue_test.go | 30 +++++++++++++----------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/fluent/toInterfaceValue.go b/fluent/toInterfaceValue.go index 1200dbf0..48f95871 100644 --- a/fluent/toInterfaceValue.go +++ b/fluent/toInterfaceValue.go @@ -6,13 +6,13 @@ import ( "github.com/ipld/go-ipld-prime/datamodel" ) -var ErrInvalidKind = errors.New("invalid kind") -var ErrUnknownKind = errors.New("unknown kind") +var errInvalidKind = errors.New("invalid kind") +var errUnknownKind = errors.New("unknown kind") func ToInterface(node datamodel.Node) (interface{}, error) { switch k := node.Kind(); k { case datamodel.Kind_Invalid: - return nil, ErrInvalidKind + return nil, errInvalidKind case datamodel.Kind_Null: return nil, nil case datamodel.Kind_Bool: @@ -60,6 +60,6 @@ func ToInterface(node datamodel.Node) (interface{}, error) { } return outList, nil default: - return nil, ErrUnknownKind + return nil, errUnknownKind } } diff --git a/fluent/toInterfaceValue_test.go b/fluent/toInterfaceValue_test.go index ffd9aa8d..9e0b3c7e 100644 --- a/fluent/toInterfaceValue_test.go +++ b/fluent/toInterfaceValue_test.go @@ -1,7 +1,6 @@ package fluent_test import ( - "encoding/json" "testing" qt "github.com/frankban/quicktest" @@ -12,40 +11,37 @@ import ( ) var roundTripTestCases = []struct { - desc string + name string value interface{} }{ - {desc: "Number", value: 100}, - {desc: "String", value: "hi"}, - {desc: "Bool", value: "hi"}, - {desc: "Bytes", value: []byte("hi")}, - {desc: "Map", value: map[string]interface{}{"a": "1", "b": int64(2), "c": 3.14, "d": true}}, - {desc: "Array", value: []string{"a", "b", "c"}}, + {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.desc, func(t *testing.T) { + 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) - outJson, err := json.Marshal(out) - qt.Check(t, err, qt.IsNil) - qt.Check(t, outJson, qt.JSONEquals, testCase.value) + c.Check(out, qt.DeepEquals, testCase.value) }) } } func TestLink(t *testing.T) { c := qt.New(t) - var someCid, err = cid.Parse("bafybeihrqe2hmfauph5yfbd6ucv7njqpiy4tvbewlvhzjl4bhnyiu6h7pm") + someCid, err := cid.Parse("bafybeihrqe2hmfauph5yfbd6ucv7njqpiy4tvbewlvhzjl4bhnyiu6h7pm") c.Assert(err, qt.IsNil) - var link = cidlink.Link{Cid: someCid} - var node = basicnode.NewLink(link) - v, err := fluent.ToInterface(node) + 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) } From aa7de1c1444383c9dafec3f35e4c633e4d496cb1 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Tue, 30 Nov 2021 07:09:47 -0800 Subject: [PATCH 5/5] Add docs --- fluent/toInterfaceValue.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fluent/toInterfaceValue.go b/fluent/toInterfaceValue.go index 48f95871..b9ed8beb 100644 --- a/fluent/toInterfaceValue.go +++ b/fluent/toInterfaceValue.go @@ -9,6 +9,12 @@ import ( 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: