Unverified Commit 30cf80e2 authored by Eric Myhre's avatar Eric Myhre Committed by GitHub

Merge pull request #26 from ipld/feat/recursive-selectors

Feat/recursive selectors
parents 631c9dad cbedbe9a
......@@ -28,7 +28,7 @@ func (s ExploreAll) Decide(n ipld.Node) bool {
}
// ParseExploreAll assembles a Selector from a ExploreAll selector node
func ParseExploreAll(n ipld.Node) (Selector, error) {
func (pc ParseContext) ParseExploreAll(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
......@@ -36,7 +36,7 @@ func ParseExploreAll(n ipld.Node) (Selector, error) {
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreAll selector")
}
selector, err := ParseSelector(next)
selector, err := pc.ParseSelector(next)
if err != nil {
return nil, err
}
......
......@@ -13,12 +13,12 @@ func TestParseExploreAll(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
t.Run("parsing non map node should error", func(t *testing.T) {
sn := fnb.CreateInt(0)
_, err := ParseExploreAll(sn)
_, err := ParseContext{}.ParseExploreAll(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector body must be a map"))
})
t.Run("parsing map node without next field should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
_, err := ParseExploreAll(sn)
_, err := ParseContext{}.ParseExploreAll(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreAll selector"))
})
......@@ -26,7 +26,7 @@ func TestParseExploreAll(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateInt(0))
})
_, err := ParseExploreAll(sn)
_, err := ParseContext{}.ParseExploreAll(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector is a keyed union and thus must be a map"))
})
t.Run("parsing map node with next field with valid selector node should parse", func(t *testing.T) {
......@@ -35,7 +35,7 @@ func TestParseExploreAll(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
s, err := ParseExploreAll(sn)
s, err := ParseContext{}.ParseExploreAll(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreAll{Matcher{}})
})
......
......@@ -40,7 +40,7 @@ func (s ExploreFields) Decide(n ipld.Node) bool {
// ParseExploreFields assembles a Selector
// from a ExploreFields selector node
func ParseExploreFields(n ipld.Node) (Selector, error) {
func (pc ParseContext) ParseExploreFields(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
......@@ -63,7 +63,7 @@ func ParseExploreFields(n ipld.Node) (Selector, error) {
kstr, _ := kn.AsString()
x.interests = append(x.interests, PathSegmentString{kstr})
x.selections[kstr], err = ParseSelector(v)
x.selections[kstr], err = pc.ParseSelector(v)
if err != nil {
return nil, err
}
......
......@@ -13,19 +13,19 @@ func TestParseExploreFields(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
t.Run("parsing non map node should error", func(t *testing.T) {
sn := fnb.CreateInt(0)
_, err := ParseExploreFields(sn)
_, err := ParseContext{}.ParseExploreFields(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector body must be a map"))
})
t.Run("parsing map node without fields value should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
_, err := ParseExploreFields(sn)
_, err := ParseContext{}.ParseExploreFields(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: fields in ExploreFields selector must be present"))
})
t.Run("parsing map node with fields value that is not a map should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(fieldsKey), vnb.CreateString("cheese"))
})
_, err := ParseExploreFields(sn)
_, err := ParseContext{}.ParseExploreFields(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: fields in ExploreFields selector must be a map"))
})
t.Run("parsing map node with selector node in fields that is invalid should return child's error", func(t *testing.T) {
......@@ -34,7 +34,7 @@ func TestParseExploreFields(t *testing.T) {
mb.Insert(knb.CreateString("applesauce"), vnb.CreateInt(0))
}))
})
_, err := ParseExploreFields(sn)
_, err := ParseContext{}.ParseExploreFields(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector is a keyed union and thus must be a map"))
})
t.Run("parsing map node with fields value that is map of only valid selector node should parse", func(t *testing.T) {
......@@ -45,7 +45,7 @@ func TestParseExploreFields(t *testing.T) {
}))
}))
})
s, err := ParseExploreFields(sn)
s, err := ParseContext{}.ParseExploreFields(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []PathSegment{PathSegmentString{S: "applesauce"}}})
})
......
......@@ -39,7 +39,7 @@ func (s ExploreIndex) Decide(n ipld.Node) bool {
// ParseExploreIndex assembles a Selector
// from a ExploreIndex selector node
func ParseExploreIndex(n ipld.Node) (Selector, error) {
func (pc ParseContext) ParseExploreIndex(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
......@@ -55,7 +55,7 @@ func ParseExploreIndex(n ipld.Node) (Selector, error) {
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreIndex selector")
}
selector, err := ParseSelector(next)
selector, err := pc.ParseSelector(next)
if err != nil {
return nil, err
}
......
......@@ -14,14 +14,14 @@ func TestParseExploreIndex(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
t.Run("parsing non map node should error", func(t *testing.T) {
sn := fnb.CreateInt(0)
_, err := ParseExploreIndex(sn)
_, err := ParseContext{}.ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector body must be a map"))
})
t.Run("parsing map node without next field should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(indexKey), vnb.CreateInt(2))
})
_, err := ParseExploreIndex(sn)
_, err := ParseContext{}.ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreIndex selector"))
})
t.Run("parsing map node without index field should error", func(t *testing.T) {
......@@ -30,7 +30,7 @@ func TestParseExploreIndex(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
_, err := ParseExploreIndex(sn)
_, err := ParseContext{}.ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: index field must be present in ExploreIndex selector"))
})
t.Run("parsing map node with index field that is not an int should error", func(t *testing.T) {
......@@ -40,7 +40,7 @@ func TestParseExploreIndex(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
_, err := ParseExploreIndex(sn)
_, err := ParseContext{}.ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: index field must be a number in ExploreIndex selector"))
})
t.Run("parsing map node with next field with invalid selector node should return child's error", func(t *testing.T) {
......@@ -48,7 +48,7 @@ func TestParseExploreIndex(t *testing.T) {
mb.Insert(knb.CreateString(indexKey), vnb.CreateInt(2))
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateInt(0))
})
_, err := ParseExploreIndex(sn)
_, err := ParseContext{}.ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector is a keyed union and thus must be a map"))
})
t.Run("parsing map node with next field with valid selector node should parse", func(t *testing.T) {
......@@ -58,7 +58,7 @@ func TestParseExploreIndex(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
s, err := ParseExploreIndex(sn)
s, err := ParseContext{}.ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}})
})
......
......@@ -43,7 +43,7 @@ func (s ExploreRange) Decide(n ipld.Node) bool {
// ParseExploreRange assembles a Selector
// from a ExploreRange selector node
func ParseExploreRange(n ipld.Node) (Selector, error) {
func (pc ParseContext) ParseExploreRange(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
......@@ -70,7 +70,7 @@ func ParseExploreRange(n ipld.Node) (Selector, error) {
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreRange selector")
}
selector, err := ParseSelector(next)
selector, err := pc.ParseSelector(next)
if err != nil {
return nil, err
}
......
......@@ -14,7 +14,7 @@ func TestParseExploreRange(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
t.Run("parsing non map node should error", func(t *testing.T) {
sn := fnb.CreateInt(0)
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector body must be a map"))
})
t.Run("parsing map node without next field should error", func(t *testing.T) {
......@@ -22,7 +22,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(startKey), vnb.CreateInt(2))
mb.Insert(knb.CreateString(endKey), vnb.CreateInt(3))
})
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreRange selector"))
})
t.Run("parsing map node without start field should error", func(t *testing.T) {
......@@ -32,7 +32,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: start field must be present in ExploreRange selector"))
})
t.Run("parsing map node with start field that is not an int should error", func(t *testing.T) {
......@@ -43,7 +43,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: start field must be a number in ExploreRange selector"))
})
t.Run("parsing map node without end field should error", func(t *testing.T) {
......@@ -53,7 +53,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: end field must be present in ExploreRange selector"))
})
t.Run("parsing map node with end field that is not an int should error", func(t *testing.T) {
......@@ -64,7 +64,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: end field must be a number in ExploreRange selector"))
})
t.Run("parsing map node where end is not greater than start should error", func(t *testing.T) {
......@@ -75,7 +75,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: end field must be greater than start field in ExploreRange selector"))
})
t.Run("parsing map node with next field with invalid selector node should return child's error", func(t *testing.T) {
......@@ -84,7 +84,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(endKey), vnb.CreateInt(3))
mb.Insert(knb.CreateString(nextSelectorKey), vnb.CreateInt(0))
})
_, err := ParseExploreRange(sn)
_, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector is a keyed union and thus must be a map"))
})
......@@ -96,7 +96,7 @@ func TestParseExploreRange(t *testing.T) {
mb.Insert(knb.CreateString(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
s, err := ParseExploreRange(sn)
s, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreRange{Matcher{}, 2, 3, []PathSegment{PathSegmentInt{I: 2}}})
})
......
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// ExploreRecursive traverses some structure recursively.
// To guide this exploration, it uses a "sequence", which is another Selector
// tree; some leaf node in this sequence should contain an ExploreRecursiveEdge
// selector, which denotes the place recursion should occur.
//
// In implementation, whenever evaluation reaches an ExploreRecursiveEdge marker
// in the recursion sequence's Selector tree, the implementation logically
// produces another new Selector which is a copy of the original
// ExploreRecursive selector, but with a decremented maxDepth parameter, and
// continues evaluation thusly.
//
// It is not valid for an ExploreRecursive selector's sequence to contain
// no instances of ExploreRecursiveEdge; it *is* valid for it to contain
// more than one ExploreRecursiveEdge.
//
// ExploreRecursive can contain a nested ExploreRecursive!
// This is comparable to a nested for-loop.
// In these cases, any ExploreRecursiveEdge instance always refers to the
// nearest parent ExploreRecursive (in other words, ExploreRecursiveEdge can
// be thought of like the 'continue' statement, or end of a for-loop body;
// it is *not* a 'goto' statement).
//
// Be careful when using ExploreRecursive with a large maxDepth parameter;
// it can easily cause very large traversals (especially if used in combination
// with selectors like ExploreAll inside the sequence).
type ExploreRecursive struct {
sequence Selector // selector for element we're interested in
current Selector // selector to apply to the current node
maxDepth int
}
// Interests for ExploreRecursive is empty (meaning traverse everything)
func (s ExploreRecursive) Interests() []PathSegment {
return s.current.Interests()
}
// Explore returns the node's selector for all fields
func (s ExploreRecursive) Explore(n ipld.Node, p PathSegment) Selector {
nextSelector := s.current.Explore(n, p)
if nextSelector == nil {
return nil
}
_, ok := nextSelector.(ExploreRecursiveEdge)
if !ok {
return ExploreRecursive{s.sequence, nextSelector, s.maxDepth}
}
if s.maxDepth < 2 {
return nil
}
return ExploreRecursive{s.sequence, s.sequence, s.maxDepth - 1}
}
// Decide always returns false because this is not a matcher
func (s ExploreRecursive) Decide(n ipld.Node) bool {
return s.current.Decide(n)
}
type exploreRecursiveContext struct {
edgesFound int
}
func (erc *exploreRecursiveContext) Link(s Selector) bool {
_, ok := s.(ExploreRecursiveEdge)
if ok {
erc.edgesFound++
}
return ok
}
// ParseExploreRecursive assembles a Selector from a ExploreRecursive selector node
func (pc ParseContext) ParseExploreRecursive(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
maxDepthNode, err := n.TraverseField(maxDepthKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: maxDepth field must be present in ExploreRecursive selector")
}
maxDepthValue, err := maxDepthNode.AsInt()
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: maxDepth field must be a number in ExploreRecursive selector")
}
sequence, err := n.TraverseField(sequenceKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: sequence field must be present in ExploreRecursive selector")
}
erc := &exploreRecursiveContext{}
selector, err := pc.PushParent(erc).ParseSelector(sequence)
if err != nil {
return nil, err
}
if erc.edgesFound == 0 {
return nil, fmt.Errorf("selector spec parse rejected: ExploreRecursive must have at least one ExploreRecursiveEdge")
}
return ExploreRecursive{selector, selector, maxDepthValue}, nil
}
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// ExploreRecursiveEdge is a special sentinel value which is used to mark
// the end of a sequence started by an ExploreRecursive selector: the recursion
// goes back to the initial state of the earlier ExploreRecursive selector,
// and proceeds again (with a decremented maxDepth value).
//
// An ExploreRecursive selector that doesn't contain an ExploreRecursiveEdge
// is nonsensical. Containing more than one ExploreRecursiveEdge is valid.
// An ExploreRecursiveEdge without an enclosing ExploreRecursive is an error.
type ExploreRecursiveEdge struct{}
// Interests should ultimately never get called for an ExploreRecursiveEdge selector
func (s ExploreRecursiveEdge) Interests() []PathSegment {
panic("Traversed Explore Recursive Edge Node With No Parent")
}
// Explore should ultimately never get called for an ExploreRecursiveEdge selector
func (s ExploreRecursiveEdge) Explore(n ipld.Node, p PathSegment) Selector {
panic("Traversed Explore Recursive Edge Node With No Parent")
}
// Decide should ultimately never get called for an ExploreRecursiveEdge selector
func (s ExploreRecursiveEdge) Decide(n ipld.Node) bool {
panic("Traversed Explore Recursive Edge Node With No Parent")
}
// ParseExploreRecursiveEdge assembles a Selector
// from a exploreRecursiveEdge selector node
func (pc ParseContext) ParseExploreRecursiveEdge(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
s := ExploreRecursiveEdge{}
for _, parent := range pc.parentStack {
if parent.Link(s) {
return s, nil
}
}
return nil, fmt.Errorf("selector spec parse rejected: ExploreRecursiveEdge must be beneath ExploreRecursive")
}
package selector
import (
"bytes"
"fmt"
"testing"
"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/warpfork/go-wish"
)
func TestParseExploreRecursive(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
t.Run("parsing non map node should error", func(t *testing.T) {
sn := fnb.CreateInt(0)
_, err := ParseContext{}.ParseExploreRecursive(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector body must be a map"))
})
t.Run("parsing map node without sequence field should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(maxDepthKey), vnb.CreateInt(2))
})
_, err := ParseContext{}.ParseExploreRecursive(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: sequence field must be present in ExploreRecursive selector"))
})
t.Run("parsing map node without maxDepth field should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(sequenceKey), 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) {}))
}))
})
_, err := ParseContext{}.ParseExploreRecursive(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: maxDepth field must be present in ExploreRecursive selector"))
})
t.Run("parsing map node with maxDepth field that is not an int should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(maxDepthKey), vnb.CreateString("cheese"))
mb.Insert(knb.CreateString(sequenceKey), 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) {}))
}))
})
_, err := ParseContext{}.ParseExploreRecursive(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: maxDepth field must be a number in ExploreRecursive selector"))
})
t.Run("parsing map node with sequence field with invalid selector node should return child's error", func(t *testing.T) {
sn := fnb.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.CreateInt(0))
})
_, err := ParseContext{}.ParseExploreRecursive(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector is a keyed union and thus must be a map"))
})
t.Run("parsing map node with sequence field with valid selector w/o ExploreRecursiveEdge should not parse", func(t *testing.T) {
sn := fnb.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(matcherKey), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
})
_, err := ParseContext{}.ParseExploreRecursive(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: ExploreRecursive must have at least one ExploreRecursiveEdge"))
})
t.Run("parsing map node that is ExploreRecursiveEdge without ExploreRecursive parent should not parse", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
_, err := ParseContext{}.ParseExploreRecursiveEdge(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: ExploreRecursiveEdge must be beneath ExploreRecursive"))
})
t.Run("parsing map node with sequence field with valid selector node should parse", func(t *testing.T) {
sn := fnb.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) {}))
}))
}))
}))
})
s, err := ParseContext{}.ParseExploreRecursive(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreRecursive{ExploreAll{ExploreRecursiveEdge{}}, ExploreAll{ExploreRecursiveEdge{}}, 2})
})
}
/*
{
exploreRecursive: {
maxDepth: 3
sequence: {
exploreFields: {
fields: {
Parents: {
exploreAll: {
exploreRecursiveEdge: {}
}
}
}
}
}
}
}
*/
func TestExploreRecursiveExplore(t *testing.T) {
recursiveEdge := ExploreRecursiveEdge{}
maxDepth := 3
var rs Selector
t.Run("exploring should traverse until we get to maxDepth", func(t *testing.T) {
parentsSelector := ExploreAll{recursiveEdge}
subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []PathSegment{PathSegmentString{S: "Parents"}}}
rs = ExploreRecursive{subTree, subTree, maxDepth}
nodeString := `{
"Parents": [
{
"Parents": [
{
"Parents": [
{
"Parents": []
}
]
}
]
}
]
}
`
rn, err := dagjson.Decoder(ipldfree.NodeBuilder(), bytes.NewBufferString(nodeString))
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rn, err = rn.TraverseField("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rn, err = rn.TraverseIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rn, err = rn.TraverseField("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rn, err = rn.TraverseIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 2})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rn, err = rn.TraverseField("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth - 2})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rn, err = rn.TraverseIndex(0)
Wish(t, rs, ShouldEqual, nil)
Wish(t, err, ShouldEqual, nil)
})
t.Run("exploring should continue till we get to selector that returns nil on explore", func(t *testing.T) {
parentsSelector := ExploreIndex{recursiveEdge, [1]PathSegment{PathSegmentInt{I: 1}}}
subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []PathSegment{PathSegmentString{S: "Parents"}}}
rs = ExploreRecursive{subTree, subTree, maxDepth}
nodeString := `{
"Parents": {
}
}
`
rn, err := dagjson.Decoder(ipldfree.NodeBuilder(), bytes.NewBufferString(nodeString))
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rn, err = rn.TraverseField("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
Wish(t, rs, ShouldEqual, nil)
})
t.Run("exploring should work when there is nested recursion", func(t *testing.T) {
parentsSelector := ExploreAll{recursiveEdge}
sideSelector := ExploreAll{recursiveEdge}
subTree := ExploreFields{map[string]Selector{
"Parents": parentsSelector,
"Side": ExploreRecursive{sideSelector, sideSelector, maxDepth},
}, []PathSegment{
PathSegmentString{S: "Parents"},
PathSegmentString{S: "Side"},
},
}
s := ExploreRecursive{subTree, subTree, maxDepth}
nodeString := `{
"Parents": [
{
"Parents": [],
"Side": {
"cheese": {
"whiz": {
}
}
}
}
],
"Side": {
"real": {
"apple": {
"sauce": {
}
}
}
}
}
`
n, err := dagjson.Decoder(ipldfree.NodeBuilder(), bytes.NewBufferString(nodeString))
Wish(t, err, ShouldEqual, nil)
// traverse down Parent nodes
rn := n
rs = s
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rn, err = rn.TraverseField("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rn, err = rn.TraverseIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rn, err = rn.TraverseField("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
// traverse down top level Side tree (nested recursion)
rn = n
rs = s
rs = rs.Explore(rn, PathSegmentString{S: "Side"})
rn, err = rn.TraverseField("Side")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth}, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "real"})
rn, err = rn.TraverseField("real")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 1}, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "apple"})
rn, err = rn.TraverseField("apple")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 2}, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "sauce"})
rn, err = rn.TraverseField("sauce")
Wish(t, rs, ShouldEqual, nil)
Wish(t, err, ShouldEqual, nil)
// traverse once down Parent (top level recursion) then down Side tree (nested recursion)
rn = n
rs = s
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rn, err = rn.TraverseField("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rn, err = rn.TraverseIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Side"})
rn, err = rn.TraverseField("Side")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth}, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "cheese"})
rn, err = rn.TraverseField("cheese")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 1}, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "whiz"})
rn, err = rn.TraverseField("whiz")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 2}, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
})
}
......@@ -73,7 +73,7 @@ func (s ExploreUnion) Decide(n ipld.Node) bool {
// ParseExploreUnion assembles a Selector
// from an ExploreUnion selector node
func ParseExploreUnion(n ipld.Node) (Selector, error) {
func (pc ParseContext) ParseExploreUnion(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_List {
return nil, fmt.Errorf("selector spec parse rejected: explore union selector must be a list")
}
......@@ -85,7 +85,7 @@ func ParseExploreUnion(n ipld.Node) (Selector, error) {
if err != nil {
return nil, fmt.Errorf("error during selector spec parse: %s", err)
}
member, err := ParseSelector(v)
member, err := pc.ParseSelector(v)
if err != nil {
return nil, err
}
......
......@@ -14,7 +14,7 @@ func TestParseExploreUnion(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
t.Run("parsing non list node should error", func(t *testing.T) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
_, err := ParseExploreUnion(sn)
_, err := ParseContext{}.ParseExploreUnion(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: explore union selector must be a list"))
})
t.Run("parsing list node where one node is invalid should return child's error", func(t *testing.T) {
......@@ -24,7 +24,7 @@ func TestParseExploreUnion(t *testing.T) {
}))
lb.Append(vnb.CreateInt(2))
})
_, err := ParseExploreUnion(sn)
_, err := ParseContext{}.ParseExploreUnion(sn)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: selector is a keyed union and thus must be a map"))
})
......@@ -42,7 +42,7 @@ func TestParseExploreUnion(t *testing.T) {
}))
}))
})
s, err := ParseExploreUnion(sn)
s, err := ParseContext{}.ParseExploreUnion(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}}}})
})
......
......@@ -38,7 +38,7 @@ func (s Matcher) Decide(n ipld.Node) bool {
// ParseMatcher assembles a Selector
// from a matcher selector node
// TODO: Parse labels and conditions
func ParseMatcher(n ipld.Node) (Selector, error) {
func (pc ParseContext) ParseMatcher(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
......
......@@ -15,8 +15,24 @@ type Selector interface {
Decide(ipld.Node) bool
}
// ParsedParent is created whenever you are parsing a selector node that may have
// child selectors nodes that need to know it
type ParsedParent interface {
Link(s Selector) bool
}
// ParseContext tracks the progress when parsing a selector
type ParseContext struct {
parentStack []ParsedParent
}
// ParseSelector creates a Selector that can be traversed from an IPLD Selector node
func ParseSelector(n ipld.Node) (Selector, error) {
return ParseContext{}.ParseSelector(n)
}
// ParseSelector creates a Selector from an IPLD Selector Node with the given context
func (pc ParseContext) ParseSelector(n ipld.Node) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector is a keyed union and thus must be a map")
}
......@@ -29,22 +45,35 @@ func ParseSelector(n ipld.Node) (Selector, error) {
// (This switch is where the keyed union discriminators concretely happen.)
switch kstr {
case exploreFieldsKey:
return ParseExploreFields(v)
return pc.ParseExploreFields(v)
case exploreAllKey:
return ParseExploreAll(v)
return pc.ParseExploreAll(v)
case exploreIndexKey:
return ParseExploreIndex(v)
return pc.ParseExploreIndex(v)
case exploreRangeKey:
return ParseExploreRange(v)
return pc.ParseExploreRange(v)
case exploreUnionKey:
return ParseExploreUnion(v)
return pc.ParseExploreUnion(v)
case exploreRecursiveKey:
return pc.ParseExploreRecursive(v)
case exploreRecursiveEdgeKey:
return pc.ParseExploreRecursiveEdge(v)
case matcherKey:
return ParseMatcher(v)
return pc.ParseMatcher(v)
default:
return nil, fmt.Errorf("selector spec parse rejected: %q is not a known member of the selector union", kstr)
}
}
// PushParent puts a parent onto the stack of parents for a parse context
func (pc ParseContext) PushParent(parent ParsedParent) ParseContext {
l := len(pc.parentStack)
parents := make([]ParsedParent, 0, l+1)
parents = append(parents, parent)
parents = append(parents, pc.parentStack...)
return ParseContext{parents}
}
// PathSegment can describe either an index in a list or a key in a map, as either int or a string
type PathSegment interface {
String() string
......
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