Unverified Commit 27a6b503 authored by Hannah Howard's avatar Hannah Howard Committed by GitHub

Merge pull request #17 from ipld/feat/update-to-selector-spec

Update to selector spec, minus recursive selectors
parents 1392c36d e87610df
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// ExploreAll is similar to a `*` -- it traverses all elements of an array,
// or all entries in a map, and applies a next selector to the reached nodes.
type ExploreAll struct {
next Selector // selector for element we're interested in
}
// Interests for ExploreAll is nil (meaning traverse everything)
func (s ExploreAll) Interests() []PathSegment {
return nil
}
// Explore returns the node's selector for all fields
func (s ExploreAll) Explore(n ipld.Node, p PathSegment) Selector {
return s.next
}
// Decide always returns false because this is not a matcher
func (s ExploreAll) Decide(n ipld.Node) bool {
return false
}
// ParseExploreAll assembles a Selector from a ExploreAll selector node
func 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")
}
next, err := n.TraverseField(nextSelectorKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreAll selector")
}
selector, err := ParseSelector(next)
if err != nil {
return nil, err
}
return ExploreAll{selector}, nil
}
package selector
import (
"fmt"
"testing"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
. "github.com/warpfork/go-wish"
)
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)
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)
Wish(t, err, ShouldEqual, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreAll selector"))
})
t.Run("parsing map node without next 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(nextSelectorKey), vnb.CreateInt(0))
})
_, err := 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) {
sn := fnb.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) {}))
}))
})
s, err := ParseExploreAll(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreAll{Matcher{}})
})
}
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// ExploreFields traverses named fields in a map (or equivalently, struct, if
// traversing on typed/schema nodes) and applies a next selector to the
// reached nodes.
//
// Note that a concept of "ExplorePath" (e.g. "foo/bar/baz") can be represented
// as a set of three nexted ExploreFields selectors, each specifying one field.
// (For this reason, we don't have a special "ExplorePath" feature; use this.)
//
// ExploreFields also works for selecting specific elements out of a list;
// if a "field" is a base-10 int, it will be coerced and do the right thing.
// ExploreIndex or ExploreRange is more appropriate, however, and should be preferred.
type ExploreFields struct {
selections map[string]Selector
interests []PathSegment // keys of above; already boxed as that's the only way we consume them
}
// Interests for ExploreFields are the fields listed in the selector node
func (s ExploreFields) Interests() []PathSegment {
return s.interests
}
// 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 PathSegment) Selector {
return s.selections[p.String()]
}
// Decide always returns false because this is not a matcher
func (s ExploreFields) Decide(n ipld.Node) bool {
return false
}
// ParseExploreFields assembles a Selector
// from a ExploreFields selector node
func 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")
}
fields, err := n.TraverseField(fieldsKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: fields in ExploreFields selector must be present")
}
if fields.ReprKind() != ipld.ReprKind_Map {
return nil, fmt.Errorf("selector spec parse rejected: fields in ExploreFields selector must be a map")
}
x := ExploreFields{
make(map[string]Selector, fields.Length()),
make([]PathSegment, 0, fields.Length()),
}
for itr := fields.MapIterator(); !itr.Done(); {
kn, v, err := itr.Next()
if err != nil {
return nil, fmt.Errorf("error during selector spec parse: %s", err)
}
kstr, _ := kn.AsString()
x.interests = append(x.interests, PathSegmentString{kstr})
x.selections[kstr], err = ParseSelector(v)
if err != nil {
return nil, err
}
}
return x, nil
}
package selector
import (
"fmt"
"testing"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
. "github.com/warpfork/go-wish"
)
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)
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)
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)
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) {
sn := fnb.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.CreateInt(0))
}))
})
_, err := 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) {
sn := fnb.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) {}))
}))
}))
})
s, err := ParseExploreFields(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []PathSegment{PathSegmentString{S: "applesauce"}}})
})
}
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// ExploreIndex traverses a specific index in a list, and applies a next
// selector to the reached node.
type ExploreIndex struct {
next Selector // selector for element we're interested in
interest [1]PathSegment // index of element we're interested in
}
// Interests for ExploreIndex is just the index specified by the selector node
func (s ExploreIndex) Interests() []PathSegment {
return s.interest[:]
}
// Explore returns the node's selector if
// the path matches the index the index for this selector or nil if not
func (s ExploreIndex) Explore(n ipld.Node, p PathSegment) Selector {
if n.ReprKind() != ipld.ReprKind_List {
return nil
}
expectedIndex, expectedErr := p.Index()
actualIndex, actualErr := s.interest[0].Index()
if expectedErr != nil || actualErr != nil || expectedIndex != actualIndex {
return nil
}
return s.next
}
// Decide always returns false because this is not a matcher
func (s ExploreIndex) Decide(n ipld.Node) bool {
return false
}
// ParseExploreIndex assembles a Selector
// from a ExploreIndex selector node
func 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")
}
indexNode, err := n.TraverseField(indexKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: index field must be present in ExploreIndex selector")
}
indexValue, err := indexNode.AsInt()
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: index field must be a number in ExploreIndex selector")
}
next, err := n.TraverseField(nextSelectorKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreIndex selector")
}
selector, err := ParseSelector(next)
if err != nil {
return nil, err
}
return ExploreIndex{selector, [1]PathSegment{PathSegmentInt{I: indexValue}}}, nil
}
package selector
import (
"fmt"
"testing"
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
. "github.com/warpfork/go-wish"
)
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)
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)
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) {
sn := fnb.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 := 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) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(indexKey), vnb.CreateString("cheese"))
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 := 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) {
sn := fnb.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.CreateInt(0))
})
_, err := 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) {
sn := fnb.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) {}))
}))
})
s, err := ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}})
})
}
func TestExploreIndexExplore(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
s := ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 3}}}
t.Run("exploring should return nil unless node is a list", func(t *testing.T) {
n := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
returnedSelector := s.Explore(n, PathSegmentInt{I: 3})
Wish(t, returnedSelector, ShouldEqual, nil)
})
t.Run("exploring should return nil when given a path segment with a different index", func(t *testing.T) {
n := fnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.AppendAll([]ipld.Node{fnb.CreateInt(0), fnb.CreateInt(1), fnb.CreateInt(2), fnb.CreateInt(3)})
})
returnedSelector := s.Explore(n, PathSegmentInt{I: 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) {
n := fnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.AppendAll([]ipld.Node{fnb.CreateInt(0), fnb.CreateInt(1), fnb.CreateInt(2), fnb.CreateInt(3)})
})
returnedSelector := s.Explore(n, PathSegmentString{S: "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) {
n := fnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.AppendAll([]ipld.Node{fnb.CreateInt(0), fnb.CreateInt(1), fnb.CreateInt(2), fnb.CreateInt(3)})
})
returnedSelector := s.Explore(n, PathSegmentInt{I: 3})
Wish(t, returnedSelector, ShouldEqual, Matcher{})
})
}
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// ExploreRange traverses a list, and for each element in the range specified,
// will apply a next selector to those reached nodes.
type ExploreRange struct {
next Selector // selector for element we're interested in
start int
end int
interest []PathSegment // index of element we're interested in
}
// Interests for ExploreRange are all path segments within the iteration range
func (s ExploreRange) Interests() []PathSegment {
return s.interest
}
// 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 PathSegment) Selector {
if n.ReprKind() != ipld.ReprKind_List {
return nil
}
index, err := p.Index()
if err != nil {
return nil
}
if index < s.start || index >= s.end {
return nil
}
return s.next
}
// Decide always returns false because this is not a matcher
func (s ExploreRange) Decide(n ipld.Node) bool {
return false
}
// ParseExploreRange assembles a Selector
// from a ExploreRange selector node
func 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")
}
startNode, err := n.TraverseField(startKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: start field must be present in ExploreRange selector")
}
startValue, err := startNode.AsInt()
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: start field must be a number in ExploreRange selector")
}
endNode, err := n.TraverseField(endKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: end field must be present in ExploreRange selector")
}
endValue, err := endNode.AsInt()
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: end field must be a number in ExploreRange selector")
}
if startValue >= endValue {
return nil, fmt.Errorf("selector spec parse rejected: end field must be greater than start field in ExploreRange selector")
}
next, err := n.TraverseField(nextSelectorKey)
if err != nil {
return nil, fmt.Errorf("selector spec parse rejected: next field must be present in ExploreRange selector")
}
selector, err := ParseSelector(next)
if err != nil {
return nil, err
}
x := ExploreRange{
selector,
startValue,
endValue,
make([]PathSegment, 0, endValue-startValue),
}
for i := startValue; i < endValue; i++ {
x.interest = append(x.interest, PathSegmentInt{I: i})
}
return x, nil
}
package selector
import (
"fmt"
"testing"
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
. "github.com/warpfork/go-wish"
)
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)
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(startKey), vnb.CreateInt(2))
mb.Insert(knb.CreateString(endKey), vnb.CreateInt(3))
})
_, err := 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) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
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) {}))
}))
})
_, err := 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) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(startKey), vnb.CreateString("cheese"))
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) {}))
}))
})
_, err := 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) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(startKey), 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) {}))
}))
})
_, err := 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) {
sn := fnb.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.CreateString("cheese"))
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 := 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) {
sn := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString(startKey), vnb.CreateInt(3))
mb.Insert(knb.CreateString(endKey), 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) {}))
}))
})
_, err := 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) {
sn := fnb.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.CreateInt(0))
})
_, err := ParseExploreRange(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) {
sn := fnb.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) {}))
}))
})
s, err := ParseExploreRange(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreRange{Matcher{}, 2, 3, []PathSegment{PathSegmentInt{I: 2}}})
})
}
func TestExploreRangeExplore(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
s := ExploreRange{Matcher{}, 3, 4, []PathSegment{PathSegmentInt{I: 3}}}
t.Run("exploring should return nil unless node is a list", func(t *testing.T) {
n := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
returnedSelector := s.Explore(n, PathSegmentInt{I: 3})
Wish(t, returnedSelector, ShouldEqual, nil)
})
t.Run("exploring should return nil when given a path segment out of range", func(t *testing.T) {
n := fnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.AppendAll([]ipld.Node{fnb.CreateInt(0), fnb.CreateInt(1), fnb.CreateInt(2), fnb.CreateInt(3)})
})
returnedSelector := s.Explore(n, PathSegmentInt{I: 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) {
n := fnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.AppendAll([]ipld.Node{fnb.CreateInt(0), fnb.CreateInt(1), fnb.CreateInt(2), fnb.CreateInt(3)})
})
returnedSelector := s.Explore(n, PathSegmentString{S: "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) {
n := fnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.AppendAll([]ipld.Node{fnb.CreateInt(0), fnb.CreateInt(1), fnb.CreateInt(2), fnb.CreateInt(3)})
})
returnedSelector := s.Explore(n, PathSegmentInt{I: 3})
Wish(t, returnedSelector, ShouldEqual, Matcher{})
})
}
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// ExploreUnion allows selection to continue with two or more distinct selectors
// while exploring the same tree of data.
//
// ExploreUnion can be used to apply a Matcher on one node (causing it to
// be considered part of a (possibly labelled) result set), while simultaneously
// continuing to explore deeper parts of the tree with another selector,
// for example.
type ExploreUnion struct {
Members []Selector
}
// Interests for ExploreUnion is:
// - nil (aka all) if any member selector has nil interests
// - the union of values returned by all member selectors otherwise
func (s ExploreUnion) Interests() []PathSegment {
// Check for any high-cardinality selectors first; if so, shortcircuit.
// (n.b. we're assuming the 'Interests' method is cheap here.)
for _, m := range s.Members {
if m.Interests() == nil {
return nil
}
}
// Accumulate the whitelist of interesting path segments.
// TODO: Dedup?
v := []PathSegment{}
for _, m := range s.Members {
v = append(v, m.Interests()...)
}
return v
}
// Explore for a Union selector calls explore for each member selector
// and returns:
// - 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 PathSegment) Selector {
// TODO: memory efficient?
nonNilResults := make([]Selector, 0, len(s.Members))
for _, member := range s.Members {
resultSelector := member.Explore(n, p)
if resultSelector != nil {
nonNilResults = append(nonNilResults, resultSelector)
}
}
if len(nonNilResults) == 0 {
return nil
}
if len(nonNilResults) == 1 {
return nonNilResults[0]
}
return ExploreUnion{nonNilResults}
}
// Decide returns true for a Union selector if any of the member selectors
// return true
func (s ExploreUnion) Decide(n ipld.Node) bool {
for _, m := range s.Members {
if m.Decide(n) {
return true
}
}
return false
}
// ParseExploreUnion assembles a Selector
// from an ExploreUnion selector node
func 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")
}
x := ExploreUnion{
make([]Selector, 0, n.Length()),
}
for itr := n.ListIterator(); !itr.Done(); {
_, v, err := itr.Next()
if err != nil {
return nil, fmt.Errorf("error during selector spec parse: %s", err)
}
member, err := ParseSelector(v)
if err != nil {
return nil, err
}
x.Members = append(x.Members, member)
}
return x, nil
}
package selector
import (
"fmt"
"testing"
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
. "github.com/warpfork/go-wish"
)
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)
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) {
sn := fnb.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.CreateInt(2))
})
_, err := ParseExploreUnion(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) {
sn := fnb.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) {}))
}))
}))
}))
})
s, err := ParseExploreUnion(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}}}})
})
}
func TestExploreUnionExplore(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
n := fnb.CreateList(func(lb fluent.ListBuilder, vnb fluent.NodeBuilder) {
lb.AppendAll([]ipld.Node{fnb.CreateInt(0), fnb.CreateInt(1), fnb.CreateInt(2), fnb.CreateInt(3)})
})
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]PathSegment{PathSegmentInt{I: 2}}}}}
returnedSelector := s.Explore(n, PathSegmentInt{I: 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]PathSegment{PathSegmentInt{I: 2}}}}}
returnedSelector := s.Explore(n, PathSegmentInt{I: 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) {
s := ExploreUnion{[]Selector{
Matcher{},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
ExploreRange{Matcher{}, 2, 3, []PathSegment{PathSegmentInt{I: 2}}},
ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []PathSegment{PathSegmentString{S: "applesauce"}}},
}}
returnedSelector := s.Explore(n, PathSegmentInt{I: 2})
Wish(t, returnedSelector, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, Matcher{}}})
})
}
func TestExploreUnionInterests(t *testing.T) {
t.Run("if any member selector is high-cardinality, interests should be high-cardinality", func(t *testing.T) {
s := ExploreUnion{[]Selector{
ExploreAll{Matcher{}},
Matcher{},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
}}
Wish(t, s.Interests(), ShouldEqual, []PathSegment(nil))
})
t.Run("if no member selector is high-cardinality, interests should be combination of member selectors interests", func(t *testing.T) {
s := ExploreUnion{[]Selector{
ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []PathSegment{PathSegmentString{S: "applesauce"}}},
Matcher{},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
}}
Wish(t, s.Interests(), ShouldEqual, []PathSegment{PathSegmentString{S: "applesauce"}, PathSegmentInt{I: 2}})
})
}
func TestExploreUnionDecide(t *testing.T) {
fnb := fluent.WrapNodeBuilder(ipldfree.NodeBuilder()) // just for the other fixture building
n := fnb.CreateInt(2)
t.Run("if any member selector returns true, decide should be true", func(t *testing.T) {
s := ExploreUnion{[]Selector{
ExploreAll{Matcher{}},
Matcher{},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
}}
Wish(t, s.Decide(n), ShouldEqual, true)
})
t.Run("if no member selector returns true, decide should be false", func(t *testing.T) {
s := ExploreUnion{[]Selector{
ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []PathSegment{PathSegmentString{S: "applesauce"}}},
ExploreAll{Matcher{}},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
}}
Wish(t, s.Decide(n), ShouldEqual, false)
})
}
package selector
const (
matcherKey = "."
exploreAllKey = "a"
exploreFieldsKey = "f"
exploreIndexKey = "i"
exploreRangeKey = "r"
exploreRecursiveKey = "R"
exploreUnionKey = "|"
exploreConditionalKey = "&"
exploreRecursiveEdgeKey = "@"
nextSelectorKey = ">"
fieldsKey = "f>"
indexKey = "i"
startKey = "^"
endKey = "$"
sequenceKey = ":>"
maxDepthKey = "d"
stopAtKey = "!"
conditionKey = "&"
// not filling conditional keys since it's not complete
)
\ No newline at end of file
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// Matcher marks a node to be included in the "result" set.
// (All nodes traversed by a selector are in the "covered" set (which is a.k.a.
// "the merkle proof"); the "result" set is a subset of the "covered" set.)
//
// In libraries using selectors, the "result" set is typically provided to
// some user-specified callback.
//
// A selector tree with only "explore*"-type selectors and no Matcher selectors
// is valid; it will just generate a "covered" set of nodes and no "result" set.
// TODO: From spec: implement conditions and labels
type Matcher struct{}
// Interests are empty for a matcher (for now) because
// It is always just there to match, not explore further
func (s Matcher) Interests() []PathSegment {
return []PathSegment{}
}
// Explore will return nil because a matcher is a terminal selector
func (s Matcher) Explore(n ipld.Node, p PathSegment) Selector {
return nil
}
// Decide is always true for a match cause it's in the result set
// TODO: Implement boolean logic for conditionals
func (s Matcher) Decide(n ipld.Node) bool {
return true
}
// ParseMatcher assembles a Selector
// from a matcher selector node
// TODO: Parse labels and conditions
func 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")
}
return Matcher{}, nil
}
package selector
import (
ipld "github.com/ipld/go-ipld-prime"
)
// SelectAll is a non-recursive kleene-star match (e.g., it's `./*`).
// If SelectAll is a leaf in a Selector tree, it will match all content;
// if it has a 'next' selector (e.g., it's like `./*/foo`), it'll yield
// that next selector for explore of any and all pathsegments.
type SelectAll struct {
next Selector // set to SelectTrue at parse time if appropriate.
}
func (s SelectAll) Interests() []PathSegment {
return nil
}
func (s SelectAll) Explore(n ipld.Node, p PathSegment) Selector {
return s.next
}
func (s SelectAll) Decide(n ipld.Node) bool {
return false // this is an intermediate selector: it doesn't itself call for a thing, only indirectly does so by sometimes returning SelectTrue.
}
package selector
import (
"fmt"
ipld "github.com/ipld/go-ipld-prime"
)
// SelectFields selects some fields by name (or index),
// and may contain more nested selectors per field.
//
// If you're familiar with GraphQL queries, you can thing of SelectFields
// as similar to the basic unit of composition in GraphQL queries.
//
// SelectFields also works for selecting specific elements out of a list;
// if the "field" is a base-10 int, it will be coerced and do the right thing.
// SelectIndexes is more appropriate, however, and should be preferred.
type SelectFields struct {
selections map[string]Selector
interests []PathSegment // keys of above; already boxed as that's the only way we consume them
}
func (s SelectFields) Interests() []PathSegment {
return s.interests
}
func (s SelectFields) Explore(n ipld.Node, p PathSegment) Selector {
return s.selections[p.String()]
}
func (s SelectFields) Decide(n ipld.Node) bool {
return false // this is an intermediate selector: it doesn't itself call for a thing, only indirectly does so by sometimes returning SelectTrue.
}
func ParseSelectFields(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")
}
x := SelectFields{
make(map[string]Selector, n.Length()),
make([]PathSegment, 0, n.Length()),
}
for itr := n.MapIterator(); !itr.Done(); {
kn, v, err := itr.Next()
if err != nil {
return nil, fmt.Errorf("error during selector spec parse: %s", err)
}
kstr, _ := kn.AsString()
x.interests = append(x.interests, PathSegmentString{kstr})
switch v.ReprKind() {
case ipld.ReprKind_Map: // deeper!
x.selections[kstr], err = ParseSelector(v)
if err != nil {
return nil, err
}
case ipld.ReprKind_Bool:
b, _ := v.AsBool()
if !b {
// FUTURE: boolean-as-unit is not currently expressible in the schema spec; might be something we want, just for human ergonomics.
return nil, fmt.Errorf("selector spec parse rejected: entries in selectFields must be either a nested selector or the value 'true'")
}
x.selections[kstr] = SelectTrue{}
}
}
return x, nil
}
package selector
import (
ipld "github.com/ipld/go-ipld-prime"
)
// SelectAll is a dummy selector that other selectors can return to say
// "the content at this path? definitely this".
type SelectTrue struct{}
func (s SelectTrue) Interests() []PathSegment {
return []PathSegment{}
}
func (s SelectTrue) Explore(n ipld.Node, p PathSegment) Selector {
return nil
}
func (s SelectTrue) Decide(n ipld.Node) bool {
return true
}
package selector
import (
ipld "github.com/ipld/go-ipld-prime"
)
// implementation note: union selectors can be generated at selector evaluation time!
// for example, globstar selectors do this: `**/foo` implicitly generates a union
// after the first depth in order to check deeper star as well as the 'foo' match.
//
// imagine this example:
//
// a selector like `**/?oo/bar` is applied...
//
// ```
// ./zot/ -- prefix matches **
// ./zot/zoo/ -- prefix matches **, prefix matches **/?oo
// ./zot/zoo/foo -- prefix matches **, prefix matches **/?oo
// ./zot/zoo/foo/bar -- prefix matches **, FULL MATCH **/?oo/bar
// ./zot/zoo/foo/bar/baz -- prefix matches **
// ```
//
// as you can see, a union selector reasonably expresses the intermediate state
// needed during handling several of these paths.
// SelectUnion combines two or more other selectors and aggregates their behavior;
// if something is matched by any of the composed selectors, it's matched by the union.
type SelectUnion struct {
Members []Selector
}
func (s SelectUnion) Interests() []PathSegment {
// Check for any high-cardinality selectors first; if so, shortcircuit.
// (n.b. we're assuming the 'Interests' method is cheap here.)
for _, m := range s.Members {
if m.Interests() == nil {
return nil
}
}
// Accumulate the whitelist of interesting path segments.
v := []PathSegment{}
for _, m := range s.Members {
v = append(v, m.Interests()...)
}
return v
}
func (s SelectUnion) Explore(n ipld.Node, p PathSegment) Selector {
// this needs to call Explore for each member,
// and if more than one member returns a selector,
// we compose them into a new union automatically and return that.
panic("TODO")
}
func (s SelectUnion) Decide(n ipld.Node) bool {
for _, m := range s.Members {
if m.Decide(n) {
return true
}
}
return false
}
......@@ -7,12 +7,15 @@ import (
ipld "github.com/ipld/go-ipld-prime"
)
// Selector is the programmatic representation of an IPLD Selector Node
// and can be applied to traverse a given IPLD DAG
type Selector interface {
Interests() []PathSegment // returns the segments we're likely interested in **or nil** if we're a high-cardinality or expression based matcher and need all segments proposed to us.
Explore(ipld.Node, PathSegment) Selector // explore one step -- iteration comes from outside (either whole node, or by following suggestions of Interests). returns nil if no interest. you have to traverse to the next node yourself (the selector doesn't do it for you because you might be considering multiple selection reasons at the same time).
Decide(ipld.Node) bool
}
// ParseSelector creates a Selector that can be traversed from an IPLD Selector node
func 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")
......@@ -25,30 +28,35 @@ func ParseSelector(n ipld.Node) (Selector, error) {
// Switch over the single key to determine which selector body comes next.
// (This switch is where the keyed union discriminators concretely happen.)
switch kstr {
case "f":
return ParseSelectFields(v)
// FUTURE:
// case "a":
// return ParseSelectAll(v)
// case "i":
// return ParseSelectIndexes(v)
// case "r":
// return ParseSelectRange(v)
// case "+":
// return ParseSelectTrue(v)
case exploreFieldsKey:
return ParseExploreFields(v)
case exploreAllKey:
return ParseExploreAll(v)
case exploreIndexKey:
return ParseExploreIndex(v)
case exploreRangeKey:
return ParseExploreRange(v)
case exploreUnionKey:
return ParseExploreUnion(v)
case matcherKey:
return ParseMatcher(v)
default:
return nil, fmt.Errorf("selector spec parse rejected: %q is not a known member of the selector union", kstr)
}
}
// 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
Index() (int, error)
}
// PathSegmentString represents a PathSegment with an underlying string
type PathSegmentString struct {
S string
}
// PathSegmentInt represents a PathSegment with an underlying int
type PathSegmentInt struct {
I int
}
......@@ -56,12 +64,17 @@ type PathSegmentInt struct {
func (ps PathSegmentString) String() string {
return ps.S
}
// Index attempts to parse a string as an int for a PathSegmentString
func (ps PathSegmentString) Index() (int, error) {
return strconv.Atoi(ps.S)
}
func (ps PathSegmentInt) String() string {
return strconv.Itoa(ps.I)
}
// Index is always just the underlying int for a PathSegmentInt
func (ps PathSegmentInt) Index() (int, error) {
return ps.I, nil
}
......@@ -43,7 +43,7 @@ var (
// all cases here use one already-loaded Node; no link-loading exercised.
func TestTraverse(t *testing.T) {
t.Run("traverse selecting true should visit the root", func(t *testing.T) {
err := traversal.Traverse(fnb.CreateString("x"), selector.SelectTrue{}, 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, tp.Path.String(), ShouldEqual, ipld.Path{}.String())
return nil
......@@ -51,7 +51,7 @@ func TestTraverse(t *testing.T) {
Wish(t, err, ShouldEqual, nil)
})
t.Run("traverse selecting true should visit only the root and no deeper", func(t *testing.T) {
err := traversal.Traverse(middleMapNode, selector.SelectTrue{}, func(tp traversal.TraversalProgress, n ipld.Node) error {
err := traversal.Traverse(middleMapNode, selector.Matcher{}, func(tp traversal.TraversalProgress, n ipld.Node) error {
Wish(t, n, ShouldEqual, middleMapNode)
Wish(t, tp.Path.String(), ShouldEqual, ipld.Path{}.String())
return nil
......@@ -61,8 +61,14 @@ func TestTraverse(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) {
mb.Insert(knb.CreateString("f"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("foo"), vnb.CreateBool(true))
mb.Insert(knb.CreateString("bar"), vnb.CreateBool(true))
mb.Insert(knb.CreateString("f>"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
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)
......@@ -86,10 +92,18 @@ func TestTraverse(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) {
mb.Insert(knb.CreateString("f"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
mb.Insert(knb.CreateString("foo"), vnb.CreateBool(true))
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("nonlink"), vnb.CreateBool(true))
mb.Insert(knb.CreateString("f>"), vnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {
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("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) {}))
}))
}))
}))
}))
}))
}))
......
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