Commit e4e37206 authored by hannahhoward's avatar hannahhoward

feat(selectors): create a builder

Adds a builder class to quickly assemble selectors nodes, using a DSL that enforces the underlying
schema. This allows quick assembly of selector nodes. This will likely be removed once IPLD Schema
code gen is finished, but for the time being, it provides a quick way to ensure mostly typesafe
selector nodes.
parent cbedbe9a
package selector
import (
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent"
)
// SelectorSpec is a specification for a selector that can build
// a selector ipld.Node or an actual parsed Selector
type SelectorSpec interface {
Node() ipld.Node
Selector() (Selector, error)
}
// SelectorSpecBuilder is a utility interface to build selector ipld nodes
// quickly.
//
// It serves two purposes:
// 1. Save the user of go-ipld-prime time and mental overhead with an easy
// interface for making selector nodes in much less code without having to remember
// the selector sigils
// 2. Provide a level of protection from selector schema changes, at least in terms
// of naming, if not structure
type SelectorSpecBuilder interface {
ExploreRecursiveEdge() SelectorSpec
ExploreRecursive(maxDepth int, sequence SelectorSpec) SelectorSpec
ExploreUnion(...SelectorSpec) SelectorSpec
ExploreAll(next SelectorSpec) SelectorSpec
ExploreIndex(index int, next SelectorSpec) SelectorSpec
ExploreRange(start int, end int, next SelectorSpec) SelectorSpec
ExploreFields(ExploreFieldsSpecBuildingClosure) SelectorSpec
Matcher() SelectorSpec
}
// ExploreFieldsSpecBuildingClosure is a function that provided to SelectorSpecBuilder's
// ExploreFields method that assembles the fields map in the selector using
// an ExploreFieldsSpecBuilder
type ExploreFieldsSpecBuildingClosure func(ExploreFieldsSpecBuilder)
// ExploreFieldsSpecBuilder is an interface for assemble the map of fields to
// selectors in ExploreFields
type ExploreFieldsSpecBuilder interface {
Insert(k string, v SelectorSpec)
Delete(k string)
}
type selectorSpecBuilder struct {
fnb fluent.NodeBuilder
}
type selectorSpec struct {
n ipld.Node
}
func (ss selectorSpec) Node() ipld.Node {
return ss.n
}
func (ss selectorSpec) Selector() (Selector, error) {
return ParseSelector(ss.n)
}
// NewSelectorSpecBuilder creates a SelectorSpecBuilder from an underlying ipld NodeBuilder
func NewSelectorSpecBuilder(nb ipld.NodeBuilder) SelectorSpecBuilder {
fnb := fluent.WrapNodeBuilder(nb)
return &selectorSpecBuilder{fnb}
}
func (ssb *selectorSpecBuilder) ExploreRecursiveEdge() SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreRecursiveEdgeKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}),
}
}
func (ssb *selectorSpecBuilder) ExploreRecursive(maxDepth int, sequence SelectorSpec) SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreRecursiveKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(maxDepthKey), vnb.CreateInt(maxDepth))
mb.Insert(knb.CreateString(sequenceKey), sequence.Node())
}))
}),
}
}
func (ssb *selectorSpecBuilder) ExploreAll(next SelectorSpec) SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreAllKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(nextSelectorKey), next.Node())
}))
}),
}
}
func (ssb *selectorSpecBuilder) ExploreIndex(index int, next SelectorSpec) SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreIndexKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(indexKey), vnb.CreateInt(index))
mb.Insert(knb.CreateString(nextSelectorKey), next.Node())
}))
}),
}
}
func (ssb *selectorSpecBuilder) ExploreRange(start int, end int, next SelectorSpec) SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreRangeKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(startKey), vnb.CreateInt(start))
mb.Insert(knb.CreateString(endKey), vnb.CreateInt(end))
mb.Insert(knb.CreateString(nextSelectorKey), next.Node())
}))
}),
}
}
func (ssb *selectorSpecBuilder) ExploreUnion(members ...SelectorSpec) SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreUnionKey), vnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
for _, member := range members {
lb.Append(member.Node())
}
}))
}),
}
}
func (ssb *selectorSpecBuilder) ExploreFields(specBuilder ExploreFieldsSpecBuildingClosure) SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreFieldsKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(fieldsKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
specBuilder(exploreFieldsSpecBuilder{mb, knb})
}))
}))
}),
}
}
func (ssb *selectorSpecBuilder) Matcher() SelectorSpec {
return selectorSpec{
ssb.fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}),
}
}
type exploreFieldsSpecBuilder struct {
mb fluent.MapBuilder
knb fluent.NodeBuilder
}
func (efsb exploreFieldsSpecBuilder) Insert(field string, s SelectorSpec) {
efsb.mb.Insert(efsb.knb.CreateString(field), s.Node())
}
func (efsb exploreFieldsSpecBuilder) Delete(field string) {
efsb.mb.Delete(efsb.knb.CreateString(field))
}
package selector
import (
"testing"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
. "github.com/warpfork/go-wish"
)
func TestBuildingSelectors(t *testing.T) {
nb := ipldfree.NodeBuilder()
fnb := fluent.WrapNodeBuilder(nb)
ssb := NewSelectorSpecBuilder(nb)
t.Run("Matcher builds matcher nodes", func(t *testing.T) {
sn := ssb.Matcher().Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
})
Wish(t, sn, ShouldEqual, esn)
})
t.Run("ExploreRecursiveEdge builds ExploreRecursiveEdge nodes", func(t *testing.T) {
sn := ssb.ExploreRecursiveEdge().Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreRecursiveEdgeKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
})
Wish(t, sn, ShouldEqual, esn)
})
t.Run("ExploreAll builds ExploreAll nodes", func(t *testing.T) {
sn := ssb.ExploreAll(ssb.Matcher()).Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreAllKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
})
Wish(t, sn, ShouldEqual, esn)
})
t.Run("ExploreIndex builds ExploreIndex nodes", func(t *testing.T) {
sn := ssb.ExploreIndex(2, ssb.Matcher()).Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreIndexKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(indexKey), vnb.CreateInt(2))
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
})
Wish(t, sn, ShouldEqual, esn)
})
t.Run("ExploreRange builds ExploreRange nodes", func(t *testing.T) {
sn := ssb.ExploreRange(2, 3, ssb.Matcher()).Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreRangeKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(startKey), vnb.CreateInt(2))
mb.Insert(knb.CreateString(endKey), vnb.CreateInt(3))
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
})
Wish(t, sn, ShouldEqual, esn)
})
t.Run("ExploreRecursive builds ExploreRecursive nodes", func(t *testing.T) {
sn := ssb.ExploreRecursive(2, ssb.ExploreAll(ssb.ExploreRecursiveEdge())).Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreRecursiveKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(maxDepthKey), vnb.CreateInt(2))
mb.Insert(knb.CreateString(sequenceKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreAllKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreRecursiveEdgeKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
}))
})
Wish(t, sn, ShouldEqual, esn)
})
t.Run("ExploreUnion builds ExploreUnion nodes", func(t *testing.T) {
sn := ssb.ExploreUnion(ssb.Matcher(), ssb.ExploreIndex(2, ssb.Matcher())).Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreUnionKey), vnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.Append(vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
lb.Append(vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreIndexKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(indexKey), vnb.CreateInt(2))
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
}))
})
Wish(t, sn, ShouldEqual, esn)
})
t.Run("ExploreFields builds ExploreFields nodes", func(t *testing.T) {
sn := ssb.ExploreFields(func(efsb ExploreFieldsSpecBuilder) { efsb.Insert("applesauce", ssb.Matcher()) }).Node()
esn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(exploreFieldsKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(fieldsKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("applesauce"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
})
Wish(t, sn, ShouldEqual, esn)
})
}
...@@ -7,7 +7,7 @@ import ( ...@@ -7,7 +7,7 @@ import (
ipld "github.com/ipld/go-ipld-prime" ipld "github.com/ipld/go-ipld-prime"
_ "github.com/ipld/go-ipld-prime/encoding/dagjson" _ "github.com/ipld/go-ipld-prime/encoding/dagjson"
"github.com/ipld/go-ipld-prime/fluent" ipldfree "github.com/ipld/go-ipld-prime/impl/free"
"github.com/ipld/go-ipld-prime/traversal" "github.com/ipld/go-ipld-prime/traversal"
"github.com/ipld/go-ipld-prime/traversal/selector" "github.com/ipld/go-ipld-prime/traversal/selector"
) )
...@@ -42,6 +42,7 @@ var ( ...@@ -42,6 +42,7 @@ var (
// covers traverse using a variety of selectors. // covers traverse using a variety of selectors.
// all cases here use one already-loaded Node; no link-loading exercised. // all cases here use one already-loaded Node; no link-loading exercised.
func TestTraverse(t *testing.T) { func TestTraverse(t *testing.T) {
ssb := selector.NewSelectorSpecBuilder(ipldfree.NodeBuilder())
t.Run("traverse selecting true should visit the root", func(t *testing.T) { t.Run("traverse selecting true should visit the root", func(t *testing.T) {
err := traversal.Traverse(fnb.CreateString("x"), selector.Matcher{}, func(tp traversal.TraversalProgress, n ipld.Node) error { err := traversal.Traverse(fnb.CreateString("x"), selector.Matcher{}, func(tp traversal.TraversalProgress, n ipld.Node) error {
Wish(t, n, ShouldEqual, fnb.CreateString("x")) Wish(t, n, ShouldEqual, fnb.CreateString("x"))
...@@ -59,19 +60,11 @@ func TestTraverse(t *testing.T) { ...@@ -59,19 +60,11 @@ func TestTraverse(t *testing.T) {
Wish(t, err, ShouldEqual, nil) Wish(t, err, ShouldEqual, nil)
}) })
t.Run("traverse selecting fields should work", func(t *testing.T) { t.Run("traverse selecting fields should work", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) { ss := ssb.ExploreFields(func(efsb selector.ExploreFieldsSpecBuilder) {
mb.Insert(knb.CreateString("f"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) { efsb.Insert("foo", ssb.Matcher())
mb.Insert(knb.CreateString("f>"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) { efsb.Insert("bar", ssb.Matcher())
mb.Insert(knb.CreateString("foo"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("."), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
mb.Insert(knb.CreateString("bar"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("."), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
}) })
s, err := selector.ParseSelector(sn) s, err := ss.Selector()
Require(t, err, ShouldEqual, nil) Require(t, err, ShouldEqual, nil)
var order int var order int
err = traversal.Traverse(middleMapNode, s, func(tp traversal.TraversalProgress, n ipld.Node) error { err = traversal.Traverse(middleMapNode, s, func(tp traversal.TraversalProgress, n ipld.Node) error {
...@@ -90,25 +83,13 @@ func TestTraverse(t *testing.T) { ...@@ -90,25 +83,13 @@ func TestTraverse(t *testing.T) {
Wish(t, order, ShouldEqual, 2) Wish(t, order, ShouldEqual, 2)
}) })
t.Run("traverse selecting fields recursively should work", func(t *testing.T) { t.Run("traverse selecting fields recursively should work", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) { ss := ssb.ExploreFields(func(efsb selector.ExploreFieldsSpecBuilder) {
mb.Insert(knb.CreateString("f"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) { efsb.Insert("foo", ssb.Matcher())
mb.Insert(knb.CreateString("f>"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) { efsb.Insert("nested", ssb.ExploreFields(func(efsb selector.ExploreFieldsSpecBuilder) {
mb.Insert(knb.CreateString("foo"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) { efsb.Insert("nonlink", ssb.Matcher())
mb.Insert(knb.CreateString("."), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
mb.Insert(knb.CreateString("nested"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("f"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("f>"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("nonlink"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("."), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
}))
}))
})) }))
}) })
s, err := selector.ParseSelector(sn) s, err := ss.Selector()
Require(t, err, ShouldEqual, nil) Require(t, err, ShouldEqual, nil)
var order int var order int
err = traversal.Traverse(middleMapNode, s, func(tp traversal.TraversalProgress, n ipld.Node) error { err = traversal.Traverse(middleMapNode, s, func(tp traversal.TraversalProgress, n ipld.Node) error {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment