Commit fc1f83d7 authored by Eric Myhre's avatar Eric Myhre

Move selector.PathSegment up to ipld.PathSegment.

ipld.Path is now a slice of ipld.PathSegment instead of strings.

This should be an improvement in sanity: there are now several fewer
places importing "strconv", and that's just always a good thing.

We will also be free in the future to add PathSegment-based accessor
methods to ipld.Node, as has already been commented as a desire;
and, to use PathSegment in building better typed errors
(which is the specific thing that provokes this diff today and now).

The implementation of PathSegment is now also based on a struct-style
union rather than an interface style union.  There are comments about
this in the diff.  I do not wish to comment on how much time I've spent
looking at golang assembler and runtime internals while trying to find
a path to a more perfect compromise between ergonomics and performance.
tl;dr Selectors will probably get faster and trigger fewer allocations;
ipld.Path will probably take slightly more memory (another word per
path segment), but not enough to care about for any practical purposes.

I did not attempt to hoist the SegmentIterator features from the
selector package to anywhere more central.
It may be a fine idea to do so someday; I just don't presently have
a formed opinion and am not budgeting time to consider it today.
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent 4a9130c4
......@@ -39,8 +39,7 @@ type Node interface {
// If idx is out of range, a nil node and an error will be returned.
LookupIndex(idx int) (Node, error)
// LookupSegment is a convenience method that might exist... if we moved
// the PathSegment type up here to the main package!
// LookupSegment is a convenience method that should be added sometime soon.
/// LookupSegment(seg PathSegment) (Node, error)
// Note that when using codegenerated types, there may be a fifth variant
......
......@@ -18,65 +18,82 @@ import (
// Paths are representable as strings. When represented as a string, each
// segment is separated by a "/" character.
// (It follows that path segments may not themselves contain a "/" character.)
// (Note: escaping may be specified and supported in the future; currently, it is not.)
//
// Path segments are stringly typed. A path segment of "123" will be used
// as a string when traversing a node of map kind; and it will be converted
// to an integer when traversing a node of list kind.
// (If a path segment string cannot be parsed to an int when traversing a node
// of list kind, then traversal will error.)
type Path struct {
segments []string
segments []PathSegment
}
// ParsePath converts a string to an IPLD Path, parsing the string
// into a segemented Path.
// ParsePath converts a string to an IPLD Path, parsing the string into a segmented Path.
//
// Each segment of the path string should be separated by a "/" character.
//
// Multiple subsequent "/" characters will be silently collapsed.
// E.g., `"foo///bar"` will be treated equivalently to `"foo/bar"`.
// Prefixed and suffixed extraneous "/" characters are also discarded.
//
// No "cleaning" of the path occurs. See the documentation of the Path
// struct; in particular, note that ".." does not mean "go up" -- so
// correspondingly, there is nothing to "clean".
// No "cleaning" of the path occurs. See the documentation of the Path struct;
// in particular, note that ".." does not mean "go up", nor does "." mean "stay here" --
// correspondingly, there isn't anything to "clean".
func ParsePath(pth string) Path {
// FUTURE: we should probably have some escaping mechanism which makes
// it possible to encode a slash in a segment. Specification needed.
return Path{strings.FieldsFunc(pth, func(r rune) bool { return r == '/' })}
ss := strings.FieldsFunc(pth, func(r rune) bool { return r == '/' })
ssl := len(ss)
p := Path{make([]PathSegment, ssl)}
for i := 0; i < ssl; i++ {
p.segments[i] = PathSegmentOfString(ss[i])
}
return p
}
// String representation of a Path is simply the join of each segment with '/'.
// It does not include a leading nor trailing slash.
func (p Path) String() string {
return strings.Join(p.segments, "/")
l := len(p.segments)
if l == 0 {
return ""
}
sb := strings.Builder{}
for i := 0; i < l-1; i++ {
sb.WriteString(p.segments[i].String())
sb.WriteByte('/')
}
sb.WriteString(p.segments[l-1].String())
return sb.String()
}
// Segements returns a slice of the path segment strings.
//
// It is not lawful to mutate the returned slice.
func (p Path) Segments() []string {
// It is not lawful to mutate nor append the returned slice.
func (p Path) Segments() []PathSegment {
return p.segments
}
// Join creates a new path composed of the concatenation of this and the
// given path's segments.
// Join creates a new path composed of the concatenation of this and the given path's segments.
func (p Path) Join(p2 Path) Path {
combinedSegments := make([]string, len(p.segments)+len(p2.segments))
combinedSegments := make([]PathSegment, len(p.segments)+len(p2.segments))
copy(combinedSegments, p.segments)
copy(combinedSegments[len(p.segments):], p2.segments)
p.segments = combinedSegments
return p
}
// AppendSegment is as per Join, but a shortcut when appending single segments.
func (p Path) AppendSegment(ps string) Path {
// AppendSegmentString is as per Join, but a shortcut when appending single segments using strings.
func (p Path) AppendSegment(ps PathSegment) Path {
l := len(p.segments)
combinedSegments := make([]string, l+1)
combinedSegments := make([]PathSegment, l+1)
copy(combinedSegments, p.segments)
combinedSegments[l] = ps
p.segments = combinedSegments
return p
}
// AppendSegmentString is as per Join, but a shortcut when appending single segments using strings.
func (p Path) AppendSegmentString(ps string) Path {
return p.AppendSegment(PathSegmentOfString(ps))
}
// Parent returns a path with the last of its segments popped off (or
// the zero path if it's already empty).
func (p Path) Parent() Path {
......
package ipld
import (
"strconv"
)
// PathSegment can describe either a key in a map, or an index in a list.
//
// Path segments are "stringly typed" -- they may be interpreted as either strings or ints depending on context.
// A path segment of "123" will be used as a string when traversing a node of map kind;
// and it will be converted to an integer when traversing a node of list kind.
// (If a path segment string cannot be parsed to an int when traversing a node of list kind, then traversal will error.)
// It is not possible to ask which kind (string or integer) a PathSegment is, because that is not defined -- this is *only* intepreted contextually.
//
// Internally, PathSegment will store either a string or an integer,
// depending on how it was constructed,
// and will automatically convert to the other on request.
// (This means if two pieces of code communicate using PathSegment, one producing ints and the other expecting ints, they will work together efficiently.)
// PathSegment in a Path produced by ParsePath generally have all strings internally,
// because there is distinction possible when parsing a Path string
// (and attempting to pre-parse all strings into ints "in case" would waste time in almost all cases).
type PathSegment struct {
/*
A quick implementation note about the Go compiler and "union" semantics:
There are roughly two ways to do "union" semantics in Go.
The first is to make a struct with each of the values.
The second is to make an interface and use an unexported method to keep it closed.
The second tactic provides somewhat nicer semantics to the programmer.
(Namely, it's clearly impossible to have two inhabitants, which is... the point.)
The downside is... putting things in interfaces generally incurs an allocation
(grep your assembly output for "runtime.conv*").
The first tactic looks kludgier, and would seem to waste memory
(the struct reserves space for each possible value, even though the semantic is that only one may be non-zero).
However, in most cases, more *bytes* are cheaper than more *allocs* --
garbage collection costs are domininated by alloc count, not alloc size.
Because PathSegment is something we expect to put in fairly "hot" paths,
we're using the first tactic.
(We also currently get away with having no extra discriminator bit
because empty string is not considered a valid segment,
and thus we can use it as a sentinel value.
This may change if the IPLD Path spec comes to other conclusions about this.)
*/
s string
i int
}
// ParsePathSegment parses a string into a PathSegment,
// handling any escaping if present.
// (Note: there is currently no escaping specified for PathSegments,
// so this is currently functionally equivalent to PathSegmentOfString.)
func ParsePathSegment(s string) PathSegment {
return PathSegment{s: s}
}
// PathSegmentOfString boxes a string into a PathSegement.
// It does not attempt to parse any escaping; use ParsePathSegment for that.
func PathSegmentOfString(s string) PathSegment {
return PathSegment{s: s}
}
// PathSegmentOfString boxes an int into a PathSegement.
func PathSegmentOfInt(i int) PathSegment {
return PathSegment{i: i}
}
// containsString is unexported because we use it to see what our *storage* form is,
// but this is considered an implementation detail that's non-semantic.
// If it returns false, it implicitly means "containsInt", as these are the only options.
func (ps PathSegment) containsString() bool {
return ps.s != ""
}
// String returns the PathSegment as a string.
func (ps PathSegment) String() string {
switch ps.containsString() {
case true:
return ps.s
case false:
return strconv.Itoa(ps.i)
}
panic("unreachable")
}
// Index returns the PathSegment as an int,
// or returns an error if the segment is a string that can't be parsed as an int.
func (ps PathSegment) Index() (int, error) {
switch ps.containsString() {
case true:
return strconv.Atoi(ps.s)
case false:
return ps.i, nil
}
panic("unreachable")
}
// Equals checks if two PathSegment values are equal.
// This is equivalent to checking if their strings are equal --
// if one of the PathSegment values is backed by an int and the other is a string,
// they may still be "equal".
func (x PathSegment) Equals(o PathSegment) bool {
if !x.containsString() && !o.containsString() {
return x.i == o.i
}
return x.String() == o.String()
}
......@@ -8,18 +8,18 @@ import (
func TestParsePath(t *testing.T) {
t.Run("parsing one segment", func(t *testing.T) {
Wish(t, ParsePath("0").segments, ShouldEqual, []string{"0"})
Wish(t, ParsePath("0").segments, ShouldEqual, []PathSegment{{s: "0"}})
})
t.Run("parsing three segments", func(t *testing.T) {
Wish(t, ParsePath("0/foo/2").segments, ShouldEqual, []string{"0", "foo", "2"})
Wish(t, ParsePath("0/foo/2").segments, ShouldEqual, []PathSegment{{s: "0"}, {s: "foo"}, {s: "2"}})
})
t.Run("eliding empty segments", func(t *testing.T) {
Wish(t, ParsePath("0//2").segments, ShouldEqual, []string{"0", "2"})
Wish(t, ParsePath("0//2").segments, ShouldEqual, []PathSegment{{s: "0"}, {s: "2"}})
})
t.Run("eliding leading slashes", func(t *testing.T) {
Wish(t, ParsePath("/0/2").segments, ShouldEqual, []string{"0", "2"})
Wish(t, ParsePath("/0/2").segments, ShouldEqual, []PathSegment{{s: "0"}, {s: "2"}})
})
t.Run("eliding trailing", func(t *testing.T) {
Wish(t, ParsePath("0/2/").segments, ShouldEqual, []string{"0", "2"})
Wish(t, ParsePath("0/2/").segments, ShouldEqual, []PathSegment{{s: "0"}, {s: "2"}})
})
}
......@@ -2,7 +2,6 @@ package traversal
import (
"fmt"
"strconv"
ipld "github.com/ipld/go-ipld-prime"
)
......@@ -41,13 +40,13 @@ func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
case ipld.ReprKind_Invalid:
return fmt.Errorf("cannot traverse node at %q: it is undefined", p.Truncate(i))
case ipld.ReprKind_Map:
next, err := n.LookupString(seg)
next, err := n.LookupString(seg.String())
if err != nil {
return fmt.Errorf("error traversing segment %q on node at %q: %s", seg, p.Truncate(i), err)
}
prev, n = n, next
case ipld.ReprKind_List:
intSeg, err := strconv.Atoi(seg)
intSeg, err := seg.Index()
if err != nil {
return fmt.Errorf("error traversing segment %q on node at %q: the segment cannot be parsed as a number and the node is a list", seg, p.Truncate(i))
}
......
......@@ -13,12 +13,12 @@ type ExploreAll struct {
}
// Interests for ExploreAll is nil (meaning traverse everything)
func (s ExploreAll) Interests() []PathSegment {
func (s ExploreAll) Interests() []ipld.PathSegment {
return nil
}
// Explore returns the node's selector for all fields
func (s ExploreAll) Explore(n ipld.Node, p PathSegment) Selector {
func (s ExploreAll) Explore(n ipld.Node, p ipld.PathSegment) Selector {
return s.next
}
......
......@@ -19,17 +19,17 @@ import (
// 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 []ipld.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 {
func (s ExploreFields) Interests() []ipld.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 {
func (s ExploreFields) Explore(n ipld.Node, p ipld.PathSegment) Selector {
return s.selections[p.String()]
}
......@@ -53,7 +53,7 @@ func (pc ParseContext) ParseExploreFields(n ipld.Node) (Selector, error) {
}
x := ExploreFields{
make(map[string]Selector, fields.Length()),
make([]PathSegment, 0, fields.Length()),
make([]ipld.PathSegment, 0, fields.Length()),
}
for itr := fields.MapIterator(); !itr.Done(); {
kn, v, err := itr.Next()
......@@ -62,7 +62,7 @@ func (pc ParseContext) ParseExploreFields(n ipld.Node) (Selector, error) {
}
kstr, _ := kn.AsString()
x.interests = append(x.interests, PathSegmentString{kstr})
x.interests = append(x.interests, ipld.PathSegmentOfString(kstr))
x.selections[kstr], err = pc.ParseSelector(v)
if err != nil {
return nil, err
......
......@@ -4,6 +4,7 @@ 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"
......@@ -47,6 +48,6 @@ func TestParseExploreFields(t *testing.T) {
})
s, err := ParseContext{}.ParseExploreFields(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []PathSegment{PathSegmentString{S: "applesauce"}}})
Wish(t, s, ShouldEqual, ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []ipld.PathSegment{ipld.PathSegmentOfString("applesauce")}})
})
}
......@@ -9,18 +9,18 @@ import (
// 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
next Selector // selector for element we're interested in
interest [1]ipld.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 {
func (s ExploreIndex) Interests() []ipld.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 {
func (s ExploreIndex) Explore(n ipld.Node, p ipld.PathSegment) Selector {
if n.ReprKind() != ipld.ReprKind_List {
return nil
}
......@@ -59,5 +59,5 @@ func (pc ParseContext) ParseExploreIndex(n ipld.Node) (Selector, error) {
if err != nil {
return nil, err
}
return ExploreIndex{selector, [1]PathSegment{PathSegmentInt{I: indexValue}}}, nil
return ExploreIndex{selector, [1]ipld.PathSegment{ipld.PathSegmentOfInt(indexValue)}}, nil
}
......@@ -60,37 +60,37 @@ func TestParseExploreIndex(t *testing.T) {
})
s, err := ParseContext{}.ParseExploreIndex(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}})
Wish(t, s, ShouldEqual, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(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}}}
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 := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
returnedSelector := s.Explore(n, PathSegmentInt{I: 3})
returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(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})
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) {
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"})
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) {
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})
returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3))
Wish(t, returnedSelector, ShouldEqual, Matcher{})
})
}
......@@ -12,17 +12,17 @@ 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
interest []ipld.PathSegment // index of element we're interested in
}
// Interests for ExploreRange are all path segments within the iteration range
func (s ExploreRange) Interests() []PathSegment {
func (s ExploreRange) Interests() []ipld.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 {
func (s ExploreRange) Explore(n ipld.Node, p ipld.PathSegment) Selector {
if n.ReprKind() != ipld.ReprKind_List {
return nil
}
......@@ -78,10 +78,10 @@ func (pc ParseContext) ParseExploreRange(n ipld.Node) (Selector, error) {
selector,
startValue,
endValue,
make([]PathSegment, 0, endValue-startValue),
make([]ipld.PathSegment, 0, endValue-startValue),
}
for i := startValue; i < endValue; i++ {
x.interest = append(x.interest, PathSegmentInt{I: i})
x.interest = append(x.interest, ipld.PathSegmentOfInt(i))
}
return x, nil
}
......@@ -98,37 +98,37 @@ func TestParseExploreRange(t *testing.T) {
})
s, err := ParseContext{}.ParseExploreRange(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreRange{Matcher{}, 2, 3, []PathSegment{PathSegmentInt{I: 2}}})
Wish(t, s, ShouldEqual, ExploreRange{Matcher{}, 2, 3, []ipld.PathSegment{ipld.PathSegmentOfInt(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}}}
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 := fnb.CreateMap(func(mb fluent.MapBuilder, knb fluent.NodeBuilder, vnb fluent.NodeBuilder) {})
returnedSelector := s.Explore(n, PathSegmentInt{I: 3})
returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(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})
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) {
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"})
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) {
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})
returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(3))
Wish(t, returnedSelector, ShouldEqual, Matcher{})
})
}
......@@ -38,12 +38,12 @@ type ExploreRecursive struct {
}
// Interests for ExploreRecursive is empty (meaning traverse everything)
func (s ExploreRecursive) Interests() []PathSegment {
func (s ExploreRecursive) Interests() []ipld.PathSegment {
return s.current.Interests()
}
// Explore returns the node's selector for all fields
func (s ExploreRecursive) Explore(n ipld.Node, p PathSegment) Selector {
func (s ExploreRecursive) Explore(n ipld.Node, p ipld.PathSegment) Selector {
nextSelector := s.current.Explore(n, p)
maxDepth := s.maxDepth
if nextSelector == nil {
......
......@@ -17,12 +17,12 @@ import (
type ExploreRecursiveEdge struct{}
// Interests should ultimately never get called for an ExploreRecursiveEdge selector
func (s ExploreRecursiveEdge) Interests() []PathSegment {
func (s ExploreRecursiveEdge) Interests() []ipld.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 {
func (s ExploreRecursiveEdge) Explore(n ipld.Node, p ipld.PathSegment) Selector {
panic("Traversed Explore Recursive Edge Node With No Parent")
}
......
......@@ -5,6 +5,7 @@ import (
"fmt"
"testing"
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/encoding/dagjson"
"github.com/ipld/go-ipld-prime/fluent"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
......@@ -116,7 +117,7 @@ func TestExploreRecursiveExplore(t *testing.T) {
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"}}}
subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []ipld.PathSegment{ipld.PathSegmentOfString("Parents")}}
rs = ExploreRecursive{subTree, subTree, maxDepth}
nodeString := `{
"Parents": [
......@@ -136,35 +137,35 @@ func TestExploreRecursiveExplore(t *testing.T) {
`
rn, err := dagjson.Decoder(ipldfree.NodeBuilder(), bytes.NewBufferString(nodeString))
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rs = rs.Explore(rn, ipld.PathSegmentOfInt(0))
rn, err = rn.LookupIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rs = rs.Explore(rn, ipld.PathSegmentOfInt(0))
rn, err = rn.LookupIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 2})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth - 2})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rs = rs.Explore(rn, ipld.PathSegmentOfInt(0))
rn, err = rn.LookupIndex(0)
Wish(t, rs, ShouldEqual, nil)
Wish(t, err, ShouldEqual, nil)
})
t.Run("exploring should continue till we get to selector that returns nil on explore", func(t *testing.T) {
parentsSelector := ExploreIndex{recursiveEdge, [1]PathSegment{PathSegmentInt{I: 1}}}
subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []PathSegment{PathSegmentString{S: "Parents"}}}
parentsSelector := ExploreIndex{recursiveEdge, [1]ipld.PathSegment{ipld.PathSegmentOfInt(1)}}
subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []ipld.PathSegment{ipld.PathSegmentOfString("Parents")}}
rs = ExploreRecursive{subTree, subTree, maxDepth}
nodeString := `{
"Parents": {
......@@ -173,11 +174,11 @@ func TestExploreRecursiveExplore(t *testing.T) {
`
rn, err := dagjson.Decoder(ipldfree.NodeBuilder(), bytes.NewBufferString(nodeString))
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 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) {
......@@ -186,9 +187,9 @@ func TestExploreRecursiveExplore(t *testing.T) {
subTree := ExploreFields{map[string]Selector{
"Parents": parentsSelector,
"Side": ExploreRecursive{sideSelector, sideSelector, maxDepth},
}, []PathSegment{
PathSegmentString{S: "Parents"},
PathSegmentString{S: "Side"},
}, []ipld.PathSegment{
ipld.PathSegmentOfString("Parents"),
ipld.PathSegmentOfString("Side"),
},
}
s := ExploreRecursive{subTree, subTree, maxDepth}
......@@ -220,15 +221,15 @@ func TestExploreRecursiveExplore(t *testing.T) {
// traverse down Parent nodes
rn := n
rs = s
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rs = rs.Explore(rn, ipld.PathSegmentOfInt(0))
rn, err = rn.LookupIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
......@@ -236,19 +237,19 @@ func TestExploreRecursiveExplore(t *testing.T) {
// traverse down top level Side tree (nested recursion)
rn = n
rs = s
rs = rs.Explore(rn, PathSegmentString{S: "Side"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Side"))
rn, err = rn.LookupString("Side")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth}, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "real"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("real"))
rn, err = rn.LookupString("real")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 1}, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "apple"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("apple"))
rn, err = rn.LookupString("apple")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 2}, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "sauce"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("sauce"))
rn, err = rn.LookupString("sauce")
Wish(t, rs, ShouldEqual, nil)
Wish(t, err, ShouldEqual, nil)
......@@ -256,30 +257,30 @@ 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, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rs = rs.Explore(rn, ipld.PathSegmentOfInt(0))
rn, err = rn.LookupIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, subTree, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Side"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Side"))
rn, err = rn.LookupString("Side")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth}, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "cheese"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("cheese"))
rn, err = rn.LookupString("cheese")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 1}, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "whiz"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("whiz"))
rn, err = rn.LookupString("whiz")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, maxDepth - 2}, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
})
t.Run("exploring should work with explore union and recursion", func(t *testing.T) {
parentsSelector := ExploreUnion{[]Selector{ExploreAll{Matcher{}}, ExploreIndex{recursiveEdge, [1]PathSegment{PathSegmentInt{0}}}}}
subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []PathSegment{PathSegmentString{S: "Parents"}}}
parentsSelector := ExploreUnion{[]Selector{ExploreAll{Matcher{}}, ExploreIndex{recursiveEdge, [1]ipld.PathSegment{ipld.PathSegmentOfInt(0)}}}}
subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []ipld.PathSegment{ipld.PathSegmentOfString("Parents")}}
rs = ExploreRecursive{subTree, subTree, maxDepth}
nodeString := `{
"Parents": [
......@@ -299,20 +300,20 @@ func TestExploreRecursiveExplore(t *testing.T) {
`
rn, err := dagjson.Decoder(ipldfree.NodeBuilder(), bytes.NewBufferString(nodeString))
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rs = rs.Explore(rn, ipld.PathSegmentOfInt(0))
rn, err = rn.LookupIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentString{S: "Parents"})
rs = rs.Explore(rn, ipld.PathSegmentOfString("Parents"))
rn, err = rn.LookupString("Parents")
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, parentsSelector, maxDepth - 1})
Wish(t, err, ShouldEqual, nil)
rs = rs.Explore(rn, PathSegmentInt{I: 0})
rs = rs.Explore(rn, ipld.PathSegmentOfInt(0))
rn, err = rn.LookupIndex(0)
Wish(t, rs, ShouldEqual, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, maxDepth - 2})
Wish(t, err, ShouldEqual, nil)
......
......@@ -20,7 +20,7 @@ type ExploreUnion struct {
// 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 {
func (s ExploreUnion) Interests() []ipld.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 {
......@@ -30,7 +30,7 @@ func (s ExploreUnion) Interests() []PathSegment {
}
// Accumulate the whitelist of interesting path segments.
// TODO: Dedup?
v := []PathSegment{}
v := []ipld.PathSegment{}
for _, m := range s.Members {
v = append(v, m.Interests()...)
}
......@@ -42,7 +42,7 @@ func (s ExploreUnion) Interests() []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 PathSegment) Selector {
func (s ExploreUnion) Explore(n ipld.Node, p ipld.PathSegment) Selector {
// TODO: memory efficient?
nonNilResults := make([]Selector, 0, len(s.Members))
for _, member := range s.Members {
......
......@@ -44,7 +44,7 @@ func TestParseExploreUnion(t *testing.T) {
})
s, err := ParseContext{}.ParseExploreUnion(sn)
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}}}})
Wish(t, s, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}}}})
})
}
......@@ -54,26 +54,26 @@ func TestExploreUnionExplore(t *testing.T) {
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})
s := ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}}}}
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]PathSegment{PathSegmentInt{I: 2}}}}}
s := ExploreUnion{[]Selector{Matcher{}, ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}}}}
returnedSelector := s.Explore(n, PathSegmentInt{I: 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) {
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"}}},
ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}},
ExploreRange{Matcher{}, 2, 3, []ipld.PathSegment{ipld.PathSegmentOfInt(2)}},
ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []ipld.PathSegment{ipld.PathSegmentOfString("applesauce")}},
}}
returnedSelector := s.Explore(n, PathSegmentInt{I: 2})
returnedSelector := s.Explore(n, ipld.PathSegmentOfInt(2))
Wish(t, returnedSelector, ShouldEqual, ExploreUnion{[]Selector{Matcher{}, Matcher{}}})
})
}
......@@ -83,17 +83,17 @@ func TestExploreUnionInterests(t *testing.T) {
s := ExploreUnion{[]Selector{
ExploreAll{Matcher{}},
Matcher{},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}},
}}
Wish(t, s.Interests(), ShouldEqual, []PathSegment(nil))
Wish(t, s.Interests(), ShouldEqual, []ipld.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"}}},
ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []ipld.PathSegment{ipld.PathSegmentOfString("applesauce")}},
Matcher{},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}},
}}
Wish(t, s.Interests(), ShouldEqual, []PathSegment{PathSegmentString{S: "applesauce"}, PathSegmentInt{I: 2}})
Wish(t, s.Interests(), ShouldEqual, []ipld.PathSegment{ipld.PathSegmentOfString("applesauce"), ipld.PathSegmentOfInt(2)})
})
}
......@@ -104,15 +104,15 @@ func TestExploreUnionDecide(t *testing.T) {
s := ExploreUnion{[]Selector{
ExploreAll{Matcher{}},
Matcher{},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(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"}}},
ExploreFields{map[string]Selector{"applesauce": Matcher{}}, []ipld.PathSegment{ipld.PathSegmentOfString("applesauce")}},
ExploreAll{Matcher{}},
ExploreIndex{Matcher{}, [1]PathSegment{PathSegmentInt{I: 2}}},
ExploreIndex{Matcher{}, [1]ipld.PathSegment{ipld.PathSegmentOfInt(2)}},
}}
Wish(t, s.Decide(n), ShouldEqual, false)
})
......
......@@ -20,12 +20,12 @@ 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{}
func (s Matcher) Interests() []ipld.PathSegment {
return []ipld.PathSegment{}
}
// Explore will return nil because a matcher is a terminal selector
func (s Matcher) Explore(n ipld.Node, p PathSegment) Selector {
func (s Matcher) Explore(n ipld.Node, p ipld.PathSegment) Selector {
return nil
}
......
......@@ -2,7 +2,6 @@ package selector
import (
"fmt"
"strconv"
ipld "github.com/ipld/go-ipld-prime"
)
......@@ -10,8 +9,8 @@ import (
// 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).
Interests() []ipld.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, ipld.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
}
......@@ -74,44 +73,10 @@ func (pc ParseContext) PushParent(parent ParsedParent) ParseContext {
return ParseContext{parents}
}
// PathSegment can describe either an index in a list or a key in a map, as either int or a string
type PathSegment interface {
String() string
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
}
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
}
// SegmentIterator iterates either a list or a map, generating PathSegments
// instead of indexes or keys
type SegmentIterator interface {
Next() (pathSegment PathSegment, value ipld.Node, err error)
Next() (pathSegment ipld.PathSegment, value ipld.Node, err error)
Done() bool
}
......@@ -127,9 +92,9 @@ type listSegmentIterator struct {
ipld.ListIterator
}
func (lsi listSegmentIterator) Next() (pathSegment PathSegment, value ipld.Node, err error) {
func (lsi listSegmentIterator) Next() (pathSegment ipld.PathSegment, value ipld.Node, err error) {
i, v, err := lsi.ListIterator.Next()
return PathSegmentInt{i}, v, err
return ipld.PathSegmentOfInt(i), v, err
}
func (lsi listSegmentIterator) Done() bool {
......@@ -140,13 +105,13 @@ type mapSegmentIterator struct {
ipld.MapIterator
}
func (msi mapSegmentIterator) Next() (pathSegment PathSegment, value ipld.Node, err error) {
func (msi mapSegmentIterator) Next() (pathSegment ipld.PathSegment, value ipld.Node, err error) {
k, v, err := msi.MapIterator.Next()
if err != nil {
return nil, v, err
return ipld.PathSegment{}, v, err
}
kstr, _ := k.AsString()
return PathSegmentString{kstr}, v, err
return ipld.PathSegmentOfString(kstr), v, err
}
func (msi mapSegmentIterator) Done() bool {
......
......@@ -67,7 +67,7 @@ func (prog Progress) walkAdv_iterateAll(n ipld.Node, s selector.Selector, fn Adv
sNext := s.Explore(n, ps)
if sNext != nil {
progNext := prog
progNext.Path = prog.Path.AppendSegment(ps.String())
progNext.Path = prog.Path.AppendSegment(ps)
if v.ReprKind() == ipld.ReprKind_Link {
lnk, _ := v.AsLink()
progNext.LastBlock.Path = progNext.Path
......@@ -87,7 +87,7 @@ func (prog Progress) walkAdv_iterateAll(n ipld.Node, s selector.Selector, fn Adv
return nil
}
func (prog Progress) walkAdv_iterateSelective(n ipld.Node, attn []selector.PathSegment, s selector.Selector, fn AdvVisitFn) error {
func (prog Progress) walkAdv_iterateSelective(n ipld.Node, attn []ipld.PathSegment, s selector.Selector, fn AdvVisitFn) error {
for _, ps := range attn {
// TODO: Probably not the most efficient way to be doing this...
v, err := n.LookupString(ps.String())
......@@ -97,7 +97,7 @@ func (prog Progress) walkAdv_iterateSelective(n ipld.Node, attn []selector.PathS
sNext := s.Explore(n, ps)
if sNext != nil {
progNext := prog
progNext.Path = prog.Path.AppendSegment(ps.String())
progNext.Path = prog.Path.AppendSegment(ps)
if v.ReprKind() == ipld.ReprKind_Link {
lnk, _ := v.AsLink()
progNext.LastBlock.Path = progNext.Path
......
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