Commit d0edb867 authored by Eric Myhre's avatar Eric Myhre

Move Path to the root package.

Yes, this one has moved about a bunch now.  Hopefully this is the last time.
It's true and elegant that paths really only emerge as descriptions of traversal
progress; however, this misses a few other practicalities.
There are other kinds of traversal (other than the traversal package, whoa!) out
there: see the typesystem packages, which had grown a custom path implementation
simply for error reporting messages!
In general, we seem to want to have Path around for logging and errors, which
will make it increasingly desirable to have available in the root package when
we begin to clean up towards strongly typed errors.
And we also need Path for LinkContext -- and wherever that comes to rest, it
definitely need to not be an import cycle problem, which it *is* if Path is
in traversal and we wanted LinkContext to be *anywhere* else.
All of these point to moving Path back up to the root, and the errors concern
in particular cinches it.

Drop the Path.traverse method.  As has been noted in its comment for a while
now, that method wasn't useful for much, having been replaced by features
in the traversal package.

(Also drop the tests specific to the Path.traverse method.  We should write
more tests against the features now implemented by travesral.Focus... but at
this point, it'd be easier to start over.  The tests we're dropping are against
a different model of traversal (returns rather than visitors) and are also
built against the old hacky ipldfree mutable model which is deprecated and
soon to be dropped.)

Also, a small docs fix: drop description of this Path implementation as a
"merklepath" -- it is not.  These paths are all relative, and do not contain
an innate understanding of hashed object identifiers at their first segment.
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent 561e2a49
package traversal
package ipld
import (
"fmt"
"strconv"
"strings"
ipld "github.com/ipld/go-ipld-prime"
)
// Path represents a MerklePath. TODO:standards-doc-link.
//
// Paths are used in describing progress in a traversal;
// Path is used in describing progress in a traversal;
// and can also be used as an instruction for a specific traverse.
//
// IPLD Paths can only go down: that is, each segment must traverse one node.
......@@ -82,37 +76,7 @@ func (p Path) Parent() Path {
return Path{p.segments[0 : len(p.segments)-1]}
}
// traverse makes a simple direct walk over a sequence of nodes,
// using each segment of the path to get the next node,
// proceding until all path segments have been consumed.
//
// This method may be removed. It doesn't know about link loading;
// and this limits its usefulness.
func (p Path) traverse(tp TraversalProgress, start ipld.Node) (_ TraversalProgress, reached ipld.Node, err error) {
for i, seg := range p.segments {
switch start.Kind() {
case ipld.ReprKind_Invalid:
return TraversalProgress{}, nil, fmt.Errorf("cannot traverse node at %q: it is undefined", Path{p.segments[0:i]})
case ipld.ReprKind_Map:
next, err := start.TraverseField(seg)
if err != nil {
return TraversalProgress{}, nil, fmt.Errorf("error traversing node at %q: %s", Path{p.segments[0:i]}, err)
}
start = next
case ipld.ReprKind_List:
intSeg, err := strconv.Atoi(seg)
if err != nil {
return TraversalProgress{}, nil, fmt.Errorf("cannot traverse node at %q: the next path segment (%q) cannot be parsed as a number and the node is a list", Path{p.segments[0:i]}, seg)
}
next, err := start.TraverseIndex(intSeg)
if err != nil {
return TraversalProgress{}, nil, fmt.Errorf("error traversing node at %q: %s", Path{p.segments[0:i]}, err)
}
start = next
default:
return TraversalProgress{}, nil, fmt.Errorf("error traversing node at %q: %s", Path{p.segments[0:i]}, fmt.Errorf("cannot traverse terminals"))
}
}
tp.Path = tp.Path.Join(p)
return tp, start, nil
// Truncate returns a path with only as many segments remaining as requested.
func (p Path) Truncate(i int) Path {
return Path{p.segments[0:i]}
}
package traversal
package ipld
import (
"testing"
......
......@@ -13,7 +13,7 @@ import (
// This might be used to do a traversal which looks at all directory objects,
// but not file contents, for example.
type LinkContext struct {
LinkPath Path // whoops, nice cycle
LinkPath ipld.Path
LinkNode ipld.Node // has the cid again, but also might have type info // always zero for writing new nodes, for obvi reasons.
ParentNode ipld.Node
}
......@@ -44,9 +44,9 @@ type TraversalReason byte // enum = SelectionMatch | SelectionParent | Selection
type TraversalProgress struct {
*TraversalConfig
Path Path // Path is how we reached the current point in the traversal.
LastBlock struct { // LastBlock stores the Path and CID of the last block edge we had to load. (It will always be zero in traversals with no linkloader.)
Path
Path ipld.Path // Path is how we reached the current point in the traversal.
LastBlock struct { // LastBlock stores the Path and CID of the last block edge we had to load. (It will always be zero in traversals with no linkloader.)
ipld.Path
cid.Cid
}
}
......
......@@ -10,14 +10,14 @@ import (
// Focus is a shortcut for kicking off
// TraversalProgress.Focus with an empty initial state
// (e.g. the Node given here is the "root" node of your operation).
func Focus(n ipld.Node, p Path, fn VisitFn) error {
func Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
return TraversalProgress{}.Focus(n, p, fn)
}
// FocusedTransform is a shortcut for kicking off
// TraversalProgress.FocusedTransform with an empty initial state
// (e.g. the Node given here is the "root" node of your operation).
func FocusedTransform(n ipld.Node, p Path, fn TransformFn) (ipld.Node, error) {
func FocusedTransform(n ipld.Node, p ipld.Path, fn TransformFn) (ipld.Node, error) {
return TraversalProgress{}.FocusedTransform(n, p, fn)
}
......@@ -31,32 +31,33 @@ func FocusedTransform(n ipld.Node, p Path, fn TransformFn) (ipld.Node, error) {
// By using the TraversalProgress handed to the VisitFn, the traversal Path
// so far will continue to be extended, so continued nested uses of Focus
// will see a fully contextualized Path.
func (tp TraversalProgress) Focus(n ipld.Node, p Path, fn VisitFn) error {
for i, seg := range p.segments {
func (tp TraversalProgress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
segments := p.Segments()
for i, seg := range segments {
switch n.Kind() {
case ipld.ReprKind_Invalid:
return fmt.Errorf("cannot traverse node at %q: it is undefined", Path{p.segments[0:i]})
return fmt.Errorf("cannot traverse node at %q: it is undefined", p.Truncate(i))
case ipld.ReprKind_Map:
next, err := n.TraverseField(seg)
if err != nil {
return fmt.Errorf("error traversing node at %q: %s", Path{p.segments[0:i]}, err)
return fmt.Errorf("error traversing node at %q: %s", p.Truncate(i), err)
}
n = next
case ipld.ReprKind_List:
intSeg, err := strconv.Atoi(seg)
if err != nil {
return fmt.Errorf("cannot traverse node at %q: the next path segment (%q) cannot be parsed as a number and the node is a list", Path{p.segments[0:i]}, seg)
return fmt.Errorf("cannot traverse node at %q: the next path segment (%q) cannot be parsed as a number and the node is a list", p.Truncate(i), seg)
}
next, err := n.TraverseIndex(intSeg)
if err != nil {
return fmt.Errorf("error traversing node at %q: %s", Path{p.segments[0:i]}, err)
return fmt.Errorf("error traversing node at %q: %s", p.Truncate(i), err)
}
n = next
case ipld.ReprKind_Link:
panic("NYI link loading") // TODO
// this would set a progress marker in `tp` as well
default:
return fmt.Errorf("error traversing node at %q: %s", Path{p.segments[0:i]}, fmt.Errorf("cannot traverse terminals"))
return fmt.Errorf("error traversing node at %q: %s", p.Truncate(i), fmt.Errorf("cannot traverse terminals"))
}
}
tp.Path = tp.Path.Join(p)
......@@ -91,6 +92,6 @@ func (tp TraversalProgress) Focus(n ipld.Node, p Path, fn VisitFn) error {
// do with regular Node and NodeBuilder usage directly. Transform just
// does a large amount of the intermediate bookkeeping that's useful when
// creating new values which are partial updates to existing values.
func (tp TraversalProgress) FocusedTransform(n ipld.Node, p Path, fn TransformFn) (ipld.Node, error) {
func (tp TraversalProgress) FocusedTransform(n ipld.Node, p ipld.Path, fn TransformFn) (ipld.Node, error) {
panic("TODO") // TODO surprisingly different from Focus -- need to store nodes we traversed, and able do building.
}
package traversal
import (
"fmt"
"testing"
. "github.com/warpfork/go-wish"
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
)
func TestPathTraversal(t *testing.T) {
t.Run("traversing list", func(t *testing.T) {
n := &ipldfree.Node{}
n0 := &ipldfree.Node{}
n0.SetString("asdf")
n.SetIndex(0, n0)
tp, nn, e := ParsePath("0").traverse(TraversalProgress{}, n)
Wish(t, nn, ShouldEqual, n0)
Wish(t, tp.Path, ShouldEqual, ParsePath("0"))
Wish(t, e, ShouldEqual, nil)
})
t.Run("traversing map", func(t *testing.T) {
n := &ipldfree.Node{}
n0 := &ipldfree.Node{}
n0.SetString("asdf")
n.SetField("foo", n0)
tp, nn, e := ParsePath("foo").traverse(TraversalProgress{}, n)
Wish(t, nn, ShouldEqual, n0)
Wish(t, tp.Path, ShouldEqual, ParsePath("foo"))
Wish(t, e, ShouldEqual, nil)
})
t.Run("traversing deeper", func(t *testing.T) {
n := &ipldfree.Node{}
n0 := &ipldfree.Node{}
n01 := &ipldfree.Node{}
n010 := &ipldfree.Node{}
n010.SetString("asdf")
n01.SetField("bar", n010)
n0.SetIndex(1, n01)
n.SetField("foo", n0)
tp, nn, e := ParsePath("foo/1/bar").traverse(TraversalProgress{}, n)
Wish(t, nn, ShouldEqual, n010)
Wish(t, tp.Path, ShouldEqual, ParsePath("foo/1/bar"))
Wish(t, e, ShouldEqual, nil)
})
t.Run("traversal error on unexpected terminals", func(t *testing.T) {
n := &ipldfree.Node{}
n0 := &ipldfree.Node{}
n01 := &ipldfree.Node{}
n010 := &ipldfree.Node{}
n010.SetString("asdf")
n01.SetField("bar", n010)
n0.SetIndex(1, n01)
n.SetField("foo", n0)
t.Run("deep terminal", func(t *testing.T) {
tp, nn, e := ParsePath("foo/1/bar/drat").traverse(TraversalProgress{}, n)
Wish(t, nn, ShouldEqual, nil)
Wish(t, tp.Path, ShouldEqual, Path{})
Wish(t, e, ShouldEqual, fmt.Errorf(`error traversing node at "foo/1/bar": cannot traverse terminals`))
})
t.Run("immediate terminal", func(t *testing.T) {
tp, nn, e := ParsePath("drat").traverse(TraversalProgress{}, n010)
Wish(t, nn, ShouldEqual, nil)
Wish(t, tp.Path, ShouldEqual, Path{})
Wish(t, e, ShouldEqual, fmt.Errorf(`error traversing node at "": cannot traverse terminals`))
})
})
t.Run("traversal error and partial progress on missing members", func(t *testing.T) {
n := &ipldfree.Node{}
n0 := &ipldfree.Node{}
n01 := &ipldfree.Node{}
n010 := &ipldfree.Node{}
n010.SetString("asdf")
n01.SetField("bar", n010)
n0.SetIndex(1, n01)
n.SetField("foo", n0)
tp, nn, e := ParsePath("foo/1/drat").traverse(TraversalProgress{}, n)
Wish(t, nn, ShouldEqual, nil)
Wish(t, tp.Path, ShouldEqual, Path{})
Wish(t, e, ShouldEqual, fmt.Errorf(`error traversing node at "foo/1": 404`))
})
}
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