diff --git a/traversal/selector/exploreAll.go b/traversal/selector/exploreAll.go index 55cb0ea5..056a7b57 100644 --- a/traversal/selector/exploreAll.go +++ b/traversal/selector/exploreAll.go @@ -18,8 +18,8 @@ func (s ExploreAll) Interests() []ipld.PathSegment { } // Explore returns the node's selector for all fields -func (s ExploreAll) Explore(n ipld.Node, p ipld.PathSegment) Selector { - return s.next +func (s ExploreAll) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { + return s.next, nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreFields.go b/traversal/selector/exploreFields.go index 1705734a..b3884847 100644 --- a/traversal/selector/exploreFields.go +++ b/traversal/selector/exploreFields.go @@ -29,8 +29,8 @@ func (s ExploreFields) Interests() []ipld.PathSegment { // Explore returns the selector for the given path if it is a field in // the selector node or nil if not -func (s ExploreFields) Explore(n ipld.Node, p ipld.PathSegment) Selector { - return s.selections[p.String()] +func (s ExploreFields) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { + return s.selections[p.String()], nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreIndex.go b/traversal/selector/exploreIndex.go index 81b40510..fc4db961 100644 --- a/traversal/selector/exploreIndex.go +++ b/traversal/selector/exploreIndex.go @@ -20,16 +20,16 @@ func (s ExploreIndex) Interests() []ipld.PathSegment { // Explore returns the node's selector if // the path matches the index for this selector or nil if not -func (s ExploreIndex) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreIndex) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { if n.Kind() != ipld.Kind_List { - return nil + return nil, nil } expectedIndex, expectedErr := p.Index() actualIndex, actualErr := s.interest[0].Index() if expectedErr != nil || actualErr != nil || expectedIndex != actualIndex { - return nil + return nil, nil } - return s.next + return s.next, nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreIndex_test.go b/traversal/selector/exploreIndex_test.go index e665367e..01ceb19b 100644 --- a/traversal/selector/exploreIndex_test.go +++ b/traversal/selector/exploreIndex_test.go @@ -68,7 +68,7 @@ func TestExploreIndexExplore(t *testing.T) { s := ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(3)}} t.Run("exploring should return nil unless node is a list", func(t *testing.T) { n := fluent.MustBuildMap(basicnode.Prototype__Map{}, 0, func(na fluent.MapAssembler) {}) - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, nil) }) n := fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { @@ -78,15 +78,15 @@ func TestExploreIndexExplore(t *testing.T) { na.AssembleValue().AssignInt(3) }) t.Run("exploring should return nil when given a path segment with a different index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return nil when given a path segment that isn't an index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfString("cheese")) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfString("cheese")) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return the next selector when given a path segment with the right index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, Matcher{}) }) } diff --git a/traversal/selector/exploreRange.go b/traversal/selector/exploreRange.go index 17e33054..66bd56fb 100644 --- a/traversal/selector/exploreRange.go +++ b/traversal/selector/exploreRange.go @@ -22,18 +22,18 @@ func (s ExploreRange) Interests() []ipld.PathSegment { // Explore returns the node's selector if // the path matches an index in the range of this selector -func (s ExploreRange) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreRange) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { if n.Kind() != ipld.Kind_List { - return nil + return nil, nil } index, err := p.Index() if err != nil { - return nil + return nil, nil } if index < s.start || index >= s.end { - return nil + return nil, nil } - return s.next + return s.next, nil } // Decide always returns false because this is not a matcher diff --git a/traversal/selector/exploreRange_test.go b/traversal/selector/exploreRange_test.go index c1df770d..17a3cdfa 100644 --- a/traversal/selector/exploreRange_test.go +++ b/traversal/selector/exploreRange_test.go @@ -106,7 +106,7 @@ func TestExploreRangeExplore(t *testing.T) { s := ExploreRange{Matcher{}, 3, 4, []ipld.PathSegment{ipld.PathSegmentOfInt(3)}} t.Run("exploring should return nil unless node is a list", func(t *testing.T) { n := fluent.MustBuildMap(basicnode.Prototype__Map{}, 0, func(na fluent.MapAssembler) {}) - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, nil) }) n := fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { @@ -116,15 +116,15 @@ func TestExploreRangeExplore(t *testing.T) { na.AssembleValue().AssignInt(3) }) t.Run("exploring should return nil when given a path segment out of range", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return nil when given a path segment that isn't an index", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfString("cheese")) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfString("cheese")) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("exploring should return the next selector when given a path segment with index in range", func(t *testing.T) { - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, Matcher{}) }) } diff --git a/traversal/selector/exploreRecursive.go b/traversal/selector/exploreRecursive.go index 83b076e4..6d69aeed 100644 --- a/traversal/selector/exploreRecursive.go +++ b/traversal/selector/exploreRecursive.go @@ -86,28 +86,34 @@ func (s ExploreRecursive) Interests() []ipld.PathSegment { } // Explore returns the node's selector for all fields -func (s ExploreRecursive) Explore(n ipld.Node, p ipld.PathSegment) Selector { - if s.stopAt != nil && s.stopAt.Match(n) { - return nil +func (s ExploreRecursive) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { + if s.stopAt != nil { + target, err := n.LookupBySegment(p) + if err != nil { + return nil, err + } + if s.stopAt.Match(target) { + return nil, nil + } } - nextSelector := s.current.Explore(n, p) + nextSelector, _ := s.current.Explore(n, p) limit := s.limit if nextSelector == nil { - return nil + return nil, nil } if !s.hasRecursiveEdge(nextSelector) { - return ExploreRecursive{s.sequence, nextSelector, limit, s.stopAt} + return ExploreRecursive{s.sequence, nextSelector, limit, s.stopAt}, nil } switch limit.mode { case RecursionLimit_Depth: if limit.depth < 2 { - return s.replaceRecursiveEdge(nextSelector, nil) + return s.replaceRecursiveEdge(nextSelector, nil), nil } - return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), RecursionLimit{RecursionLimit_Depth, limit.depth - 1}, s.stopAt} + return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), RecursionLimit{RecursionLimit_Depth, limit.depth - 1}, s.stopAt}, nil case RecursionLimit_None: - return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), limit, s.stopAt} + return ExploreRecursive{s.sequence, s.replaceRecursiveEdge(nextSelector, s.sequence), limit, s.stopAt}, nil default: panic("Unsupported recursion limit type") } diff --git a/traversal/selector/exploreRecursiveEdge.go b/traversal/selector/exploreRecursiveEdge.go index 79eb8ce7..dacaeab9 100644 --- a/traversal/selector/exploreRecursiveEdge.go +++ b/traversal/selector/exploreRecursiveEdge.go @@ -22,7 +22,7 @@ func (s ExploreRecursiveEdge) Interests() []ipld.PathSegment { } // Explore should ultimately never get called for an ExploreRecursiveEdge selector -func (s ExploreRecursiveEdge) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreRecursiveEdge) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { panic("Traversed Explore Recursive Edge Node With No Parent") } diff --git a/traversal/selector/exploreRecursive_test.go b/traversal/selector/exploreRecursive_test.go index f8936a1c..9dd6fe4d 100644 --- a/traversal/selector/exploreRecursive_test.go +++ b/traversal/selector/exploreRecursive_test.go @@ -202,28 +202,28 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, nil) Wish(t, err, ShouldEqual, nil) @@ -253,31 +253,31 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil}) Wish(t, err, ShouldEqual, nil) @@ -296,11 +296,11 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) Wish(t, rs, ShouldEqual, nil) }) t.Run("exploring should work when there is nested recursion", func(t *testing.T) { @@ -345,15 +345,15 @@ func TestExploreRecursiveExplore(t *testing.T) { // traverse down Parent nodes rn := n rs = s - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) @@ -361,19 +361,19 @@ func TestExploreRecursiveExplore(t *testing.T) { // traverse down top level Side tree (nested recursion) rn = n rs = s - rs = rs.Explore(rn, ipld.PathSegmentOfString("Side")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Side")) rn, err = rn.LookupByString("Side") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("real")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("real")) rn, err = rn.LookupByString("real") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("apple")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("apple")) rn, err = rn.LookupByString("apple") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("sauce")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("sauce")) rn, err = rn.LookupByString("sauce") Wish(t, rs, ShouldEqual, nil) Wish(t, err, ShouldEqual, nil) @@ -381,23 +381,23 @@ func TestExploreRecursiveExplore(t *testing.T) { // traverse once down Parent (top level recursion) then down Side tree (nested recursion) rn = n rs = s - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Side")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Side")) rn, err = rn.LookupByString("Side") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("cheese")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("cheese")) rn, err = rn.LookupByString("cheese") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("whiz")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("whiz")) rn, err = rn.LookupByString("whiz") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) @@ -426,20 +426,20 @@ func TestExploreRecursiveExplore(t *testing.T) { err := dagjson.Decode(nb, strings.NewReader(nodeString)) Wish(t, err, ShouldEqual, nil) rn := nb.Build() - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfString("Parents")) rn, err = rn.LookupByString("Parents") Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}) Wish(t, err, ShouldEqual, nil) - rs = rs.Explore(rn, ipld.PathSegmentOfInt(0)) + rs, _ = rs.Explore(rn, ipld.PathSegmentOfInt(0)) rn, err = rn.LookupByIndex(0) Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}) Wish(t, err, ShouldEqual, nil) diff --git a/traversal/selector/exploreUnion.go b/traversal/selector/exploreUnion.go index 3918fa73..67a90f60 100644 --- a/traversal/selector/exploreUnion.go +++ b/traversal/selector/exploreUnion.go @@ -42,22 +42,25 @@ func (s ExploreUnion) Interests() []ipld.PathSegment { // - a new union selector if more than one member returns a selector // - if exactly one member returns a selector, that selector // - nil if no members return a selector -func (s ExploreUnion) Explore(n ipld.Node, p ipld.PathSegment) Selector { +func (s ExploreUnion) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { // TODO: memory efficient? nonNilResults := make([]Selector, 0, len(s.Members)) for _, member := range s.Members { - resultSelector := member.Explore(n, p) + resultSelector, err := member.Explore(n, p) + if err != nil { + return nil, err + } if resultSelector != nil { nonNilResults = append(nonNilResults, resultSelector) } } if len(nonNilResults) == 0 { - return nil + return nil, nil } if len(nonNilResults) == 1 { - return nonNilResults[0] + return nonNilResults[0], nil } - return ExploreUnion{nonNilResults} + return ExploreUnion{nonNilResults}, nil } // Decide returns true for a Union selector if any of the member selectors diff --git a/traversal/selector/exploreUnion_test.go b/traversal/selector/exploreUnion_test.go index 8e5c005d..84d7b1df 100644 --- a/traversal/selector/exploreUnion_test.go +++ b/traversal/selector/exploreUnion_test.go @@ -57,14 +57,14 @@ func TestExploreUnionExplore(t *testing.T) { }) t.Run("exploring should return nil if all member selectors return nil when explored", func(t *testing.T) { s := ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}}}} - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(3)) Wish(t, returnedSelector, ShouldEqual, nil) }) t.Run("if exactly one member selector returns a non-nil selector when explored, exploring should return that value", func(t *testing.T) { s := ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}}}} - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, Matcher{}) }) t.Run("exploring should return a new union selector if more than one member selector returns a non nil selector when explored", func(t *testing.T) { @@ -75,7 +75,7 @@ func TestExploreUnionExplore(t *testing.T) { ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []ipld.PathSegment{ipld.PathSegmentOfString("applesauce")}}, }} - returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2)) + returnedSelector, _ := s.Explore(n, ipld.PathSegmentOfInt(2)) Wish(t, returnedSelector, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, Matcher{}}}) }) } diff --git a/traversal/selector/matcher.go b/traversal/selector/matcher.go index 3c3aa1e2..dcf51ad5 100644 --- a/traversal/selector/matcher.go +++ b/traversal/selector/matcher.go @@ -25,8 +25,8 @@ func (s Matcher) Interests() []ipld.PathSegment { } // Explore will return nil because a matcher is a terminal selector -func (s Matcher) Explore(n ipld.Node, p ipld.PathSegment) Selector { - return nil +func (s Matcher) Explore(n ipld.Node, p ipld.PathSegment) (Selector, error) { + return nil, nil } // Decide is always true for a match cause it's in the result set diff --git a/traversal/selector/selector.go b/traversal/selector/selector.go index 313cb596..b54b9787 100644 --- a/traversal/selector/selector.go +++ b/traversal/selector/selector.go @@ -98,7 +98,7 @@ type Selector interface { // Remember that Explore does **not** iterate `node` itself; the visits to any children of `node` will be driven from the outside, by the traversal function. // (The Selector's job is just guiding that process by returning information.) // The architecture works this way so that a sufficiently clever traversal function could consider several reasons for exploring a node before deciding whether to do so. - Explore(node ipld.Node, child ipld.PathSegment) (subsequent Selector) + Explore(node ipld.Node, child ipld.PathSegment) (subsequent Selector, err error) // Decide returns true if the subject node is "matched". // diff --git a/traversal/walk.go b/traversal/walk.go index 30253984..7251f047 100644 --- a/traversal/walk.go +++ b/traversal/walk.go @@ -114,7 +114,10 @@ func (prog Progress) walkAdv_iterateAll(n ipld.Node, s selector.Selector, fn Adv if err != nil { return err } - sNext := s.Explore(n, ps) + sNext, err := s.Explore(n, ps) + if err != nil { + return err + } if sNext != nil { progNext := prog progNext.Path = prog.Path.AppendSegment(ps) @@ -146,7 +149,10 @@ func (prog Progress) walkAdv_iterateSelective(n ipld.Node, attn []ipld.PathSegme if err != nil { continue } - sNext := s.Explore(n, ps) + sNext, err := s.Explore(n, ps) + if err != nil { + return err + } if sNext != nil { progNext := prog progNext.Path = prog.Path.AppendSegment(ps) diff --git a/traversal/walk_with_stop_test.go b/traversal/walk_with_stop_test.go new file mode 100644 index 00000000..1a41dab7 --- /dev/null +++ b/traversal/walk_with_stop_test.go @@ -0,0 +1,240 @@ +package traversal_test + +import ( + "fmt" + "testing" + + . "github.com/warpfork/go-wish" + + "github.com/ipld/go-ipld-prime" + _ "github.com/ipld/go-ipld-prime/codec/dagjson" + "github.com/ipld/go-ipld-prime/fluent" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + basicnode "github.com/ipld/go-ipld-prime/node/basic" + "github.com/ipld/go-ipld-prime/traversal" + "github.com/ipld/go-ipld-prime/traversal/selector" + "github.com/ipld/go-ipld-prime/traversal/selector/builder" +) + +/* Remember, we've got the following fixtures in scope: +var ( + leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) + leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) + middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { + na.AssembleEntry("foo").AssignBool(true) + na.AssembleEntry("bar").AssignBool(false) + na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { + na.AssembleEntry("alink").AssignLink(leafAlphaLnk) + na.AssembleEntry("nonlink").AssignString("zoo") + }) + })) + middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafBetaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + })) + rootNode, rootNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("plain").AssignString("olde string") + na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) + na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) + na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) + })) +) +*/ + +// ExploreRecursiveWithStop builds a recursive selector node with a stop condition +func ExploreRecursiveWithStop(limit selector.RecursionLimit, sequence builder.SelectorSpec, stopLnk ipld.Link) ipld.Node { + np := basicnode.Prototype__Map{} + return fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { + // RecursionLimit + na.AssembleEntry(selector.SelectorKey_ExploreRecursive).CreateMap(3, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) { + switch limit.Mode() { + case selector.RecursionLimit_Depth: + na.AssembleEntry(selector.SelectorKey_LimitDepth).AssignInt(limit.Depth()) + case selector.RecursionLimit_None: + na.AssembleEntry(selector.SelectorKey_LimitNone).CreateMap(0, func(na fluent.MapAssembler) {}) + default: + panic("Unsupported recursion limit type") + } + }) + // Sequence + na.AssembleEntry(selector.SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_ExploreUnion).CreateList(2, func(na fluent.ListAssembler) { + na.AssembleValue().AssignNode(fluent.MustBuildMap(np, 1, func(na fluent.MapAssembler) { + na.AssembleEntry(selector.SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {}) + })) + na.AssembleValue().AssignNode(sequence.Node()) + }) + }) + + // Stop condition + if stopLnk != nil { + cond := fluent.MustBuildMap(basicnode.Prototype__Map{}, 1, func(na fluent.MapAssembler) { + na.AssembleEntry(string(selector.ConditionMode_Link)).AssignLink(stopLnk) + }) + na.AssembleEntry(selector.SelectorKey_StopAt).AssignNode(cond) + } + }) + }) + +} + +func TestStopAtLink(t *testing.T) { + ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) + t.Run("test ExploreRecursive stopAt with simple node", func(t *testing.T) { + // Selector that passes through the map + s, err := selector.CompileSelector(ExploreRecursiveWithStop( + selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), + middleMapNodeLnk)) + if err != nil { + t.Fatal(err) + } + var order int + lsys := cidlink.DefaultLinkSystem() + lsys.StorageReadOpener = (&store).OpenRead + err = traversal.Progress{ + Cfg: &traversal.Config{ + LinkSystem: lsys, + LinkTargetNodePrototypeChooser: basicnode.Chooser, + }, + }.WalkMatching(rootNode, s, func(prog traversal.Progress, n ipld.Node) error { + // fmt.Println("Order", order, prog.Path.String()) + switch order { + case 0: + // Root + Wish(t, prog.Path.String(), ShouldEqual, "") + case 1: + Wish(t, prog.Path.String(), ShouldEqual, "plain") + Wish(t, n, ShouldEqual, basicnode.NewString("olde string")) + case 2: + Wish(t, prog.Path.String(), ShouldEqual, "linkedString") + case 3: + Wish(t, prog.Path.String(), ShouldEqual, "linkedList") + // We are starting to traverse the linkedList, we passed through the map already + case 4: + Wish(t, prog.Path.String(), ShouldEqual, "linkedList/0") + } + order++ + return nil + }) + Wish(t, err, ShouldEqual, nil) + Wish(t, order, ShouldEqual, 8) + }) +} + +// mkChain creates a DAG that represent a chain of subDAGs. +// The stopAt condition is extremely appealing for these use cases, as we can +// partially sync a chain using ExploreRecursive without having to sync the +// chain from scratch if we are already partially synced +func mkChain() (ipld.Node, []ipld.Link) { + leafAlpha, leafAlphaLnk = encode(basicnode.NewString("alpha")) + leafBeta, leafBetaLnk = encode(basicnode.NewString("beta")) + middleMapNode, middleMapNodeLnk = encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 3, func(na fluent.MapAssembler) { + na.AssembleEntry("foo").AssignBool(true) + na.AssembleEntry("bar").AssignBool(false) + na.AssembleEntry("nested").CreateMap(2, func(na fluent.MapAssembler) { + na.AssembleEntry("alink").AssignLink(leafAlphaLnk) + na.AssembleEntry("nonlink").AssignString("zoo") + }) + })) + middleListNode, middleListNodeLnk = encode(fluent.MustBuildList(basicnode.Prototype__List{}, 4, func(na fluent.ListAssembler) { + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + na.AssembleValue().AssignLink(leafBetaLnk) + na.AssembleValue().AssignLink(leafAlphaLnk) + })) + + _, ch1Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("linkedList").AssignLink(middleListNodeLnk) + })) + _, ch2Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("linkedMap").AssignLink(middleMapNodeLnk) + na.AssembleEntry("ch1").AssignLink(ch1Lnk) + })) + _, ch3Lnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("linkedString").AssignLink(leafAlphaLnk) + na.AssembleEntry("ch2").AssignLink(ch2Lnk) + })) + + headNode, headLnk := encode(fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) { + na.AssembleEntry("plain").AssignString("olde string") + na.AssembleEntry("ch3").AssignLink(ch3Lnk) + })) + return headNode, []ipld.Link{headLnk, ch3Lnk, ch2Lnk, ch1Lnk} +} + +func TestStopInChain(t *testing.T) { + chainNode, chainLnks := mkChain() + // Stay in head + stopAtInChainTest(t, chainNode, chainLnks[1], 2) + // Get head and following block + stopAtInChainTest(t, chainNode, chainLnks[2], 4) + // One more + stopAtInChainTest(t, chainNode, chainLnks[3], 11) + // Get the full chain + stopAtInChainTest(t, chainNode, nil, 17) +} + +func stopAtInChainTest(t *testing.T, chainNode ipld.Node, stopLnk ipld.Link, numSeen int) { + ssb := builder.NewSelectorSpecBuilder(basicnode.Prototype__Any{}) + t.Run(fmt.Sprintf("test ExploreRecursive stopAt in chain with stoplink: %s", stopLnk), func(t *testing.T) { + s, err := selector.CompileSelector(ExploreRecursiveWithStop( + selector.RecursionLimitNone(), ssb.ExploreAll(ssb.ExploreRecursiveEdge()), + stopLnk)) + if err != nil { + t.Fatal(err) + } + + var order int + lsys := cidlink.DefaultLinkSystem() + lsys.StorageReadOpener = (&store).OpenRead + err = traversal.Progress{ + Cfg: &traversal.Config{ + LinkSystem: lsys, + LinkTargetNodePrototypeChooser: basicnode.Chooser, + }, + }.WalkMatching(chainNode, s, func(prog traversal.Progress, n ipld.Node) error { + //fmt.Println("Order", order, prog.Path.String()) + switch order { + case 0: + // Root + Wish(t, prog.Path.String(), ShouldEqual, "") + case 1: + Wish(t, prog.Path.String(), ShouldEqual, "plain") + Wish(t, n, ShouldEqual, basicnode.NewString("olde string")) + case 2: + Wish(t, prog.Path.String(), ShouldEqual, "ch3") + case 3: + if numSeen > 4 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/linkedString") + } + case 4: + if numSeen > 11 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/ch1") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/linkedMap") + } + case 5: + if numSeen > 11 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/ch1/linkedList") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/linkedMap/bar") + } + case 10: + if numSeen > 11 { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/ch2/linkedMap") + } else { + Wish(t, prog.Path.String(), ShouldEqual, "ch3/linkedString") + } + } + order++ + return nil + }) + Wish(t, err, ShouldEqual, nil) + Wish(t, order, ShouldEqual, numSeen) + }) +}