Commit 8101125e authored by hannahhoward's avatar hannahhoward

feat(selectors): better recursive selector validation

Support validating recursive selectors to match ExploreRecursive with ExploreRecursiveEdge
parent a2104b7f
......@@ -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 ParseExploreAll(n ipld.Node, selectorContexts ...SelectorContext) (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 := ParseSelector(next, selectorContexts...)
if err != nil {
return nil, err
}
......
......@@ -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 ParseExploreFields(n ipld.Node, selectorContexts ...SelectorContext) (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 = ParseSelector(v, selectorContexts...)
if err != nil {
return nil, err
}
......
......@@ -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 ParseExploreIndex(n ipld.Node, selectorContexts ...SelectorContext) (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 := ParseSelector(next, selectorContexts...)
if err != nil {
return nil, err
}
......
......@@ -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 ParseExploreRange(n ipld.Node, selectorContexts ...SelectorContext) (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 := ParseSelector(next, selectorContexts...)
if err != nil {
return nil, err
}
......
......@@ -63,8 +63,20 @@ 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 ParseExploreRecursive(n ipld.Node) (Selector, error) {
func ParseExploreRecursive(n ipld.Node, selectorContexts ...SelectorContext) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
......@@ -81,9 +93,13 @@ func ParseExploreRecursive(n ipld.Node) (Selector, error) {
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: sequence field must be present in ExploreRecursive selector")
}
selector, err := ParseSelector(sequence)
erc := &exploreRecursiveContext{}
selector, err := ParseSelector(sequence, append([]SelectorContext{erc}, selectorContexts...)...)
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
}
......@@ -18,24 +18,30 @@ type ExploreRecursiveEdge struct{}
// Interests should ultimately never get called for an ExploreRecursiveEdge selector
func (s ExploreRecursiveEdge) Interests() []PathSegment {
return []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 {
return nil
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 {
return false
panic("Traversed Explore Recursive Edge Node With No Parent")
}
// ParseExploreRecursiveEdge assembles a Selector
// from a exploreRecursiveEdge selector node
func ParseExploreRecursiveEdge(n ipld.Node) (Selector, error) {
func ParseExploreRecursiveEdge(n ipld.Node, selectorContexts ...SelectorContext) (Selector, error) {
if n.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: selector body must be a map")
}
return ExploreRecursiveEdge{}, nil
s := ExploreRecursiveEdge{}
for _, selectorContext := range selectorContexts {
if selectorContext.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"
......@@ -50,6 +52,25 @@ func TestParseExploreRecursive(t *testing.T) {
_, err := 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 := 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 := 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))
......@@ -65,6 +86,7 @@ func TestParseExploreRecursive(t *testing.T) {
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreRecursive{ExploreAll{ExploreRecursiveEdge{}}, ExploreAll{ExploreRecursiveEdge{}}, 2})
})
}
/*
......@@ -89,30 +111,31 @@ func TestParseExploreRecursive(t *testing.T) {
*/
func TestExploreRecursiveExplore(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
recursiveEdge := ExploreRecursiveEdge{}
maxDepth := 3
var err error
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}
rn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("Parents"), 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("Parents"), 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("Parents"), 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("Parents"), vnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
}))
}))
}))
})
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})
......@@ -122,6 +145,7 @@ func TestExploreRecursiveExplore(t *testing.T) {
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)
......@@ -142,11 +166,13 @@ func TestExploreRecursiveExplore(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}
rn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("Parents"), vnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.Append(vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
})
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})
......@@ -166,25 +192,32 @@ func TestExploreRecursiveExplore(t *testing.T) {
},
}
s := ExploreRecursive{subTree, subTree, maxDepth}
n := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("Parents"), 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("Parents"), vnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {}))
mb.Insert(knb.CreateString("Side"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("cheese"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("whiz"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
}))
mb.Insert(knb.CreateString("Side"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("real"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("apple"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("sauce"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {}))
}))
}))
}))
})
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"})
......@@ -199,6 +232,8 @@ func TestExploreRecursiveExplore(t *testing.T) {
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"})
......@@ -217,6 +252,8 @@ func TestExploreRecursiveExplore(t *testing.T) {
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"})
......
......@@ -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 ParseExploreUnion(n ipld.Node, selectorContexts ...SelectorContext) (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 := ParseSelector(v, selectorContexts...)
if err != nil {
return nil, err
}
......
......@@ -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 ParseMatcher(n ipld.Node, selectorContexts ...SelectorContext) (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,12 @@ type Selector interface {
Decide(ipld.Node) bool
}
type SelectorContext interface {
Link(s Selector) bool
}
// ParseSelector creates a Selector that can be traversed from an IPLD Selector node
func ParseSelector(n ipld.Node) (Selector, error) {
func ParseSelector(n ipld.Node, selectorContexts ...SelectorContext) (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,21 +33,21 @@ 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 ParseExploreFields(v, selectorContexts...)
case exploreAllKey:
return ParseExploreAll(v)
return ParseExploreAll(v, selectorContexts...)
case exploreIndexKey:
return ParseExploreIndex(v)
return ParseExploreIndex(v, selectorContexts...)
case exploreRangeKey:
return ParseExploreRange(v)
return ParseExploreRange(v, selectorContexts...)
case exploreUnionKey:
return ParseExploreUnion(v)
return ParseExploreUnion(v, selectorContexts...)
case exploreRecursiveKey:
return ParseExploreRecursive(v)
return ParseExploreRecursive(v, selectorContexts...)
case exploreRecursiveEdgeKey:
return ParseExploreRecursiveEdge(v)
return ParseExploreRecursiveEdge(v, selectorContexts...)
case matcherKey:
return ParseMatcher(v)
return ParseMatcher(v, selectorContexts...)
default:
return nil, fmt.Errorf("selector spec parse rejected: %q is not a known member of the selector union", kstr)
}
......
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