Commit 5dfad59f authored by Eric Myhre's avatar Eric Myhre

Add traversal.Get function.

parent d3ccd3c3
...@@ -17,6 +17,17 @@ func Focus(n ipld.Node, p ipld.Path, fn VisitFn) error { ...@@ -17,6 +17,17 @@ func Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
return Progress{}.Focus(n, p, fn) return Progress{}.Focus(n, p, fn)
} }
// Get is the equivalent of Focus, but returns the reached node (rather than invoking a callback at the target),
// and does not yield Progress information.
//
// This function is a helper function which starts a new traversal with default configuration.
// It cannot cross links automatically (since this requires configuration).
// Use the equivalent Get function on the Progress structure
// for more advanced and configurable walks.
func Get(n ipld.Node, p ipld.Path) (ipld.Node, error) {
return Progress{}.Get(n, p)
}
// FocusedTransform traverses an ipld.Node graph, reaches a single Node, // FocusedTransform traverses an ipld.Node graph, reaches a single Node,
// and calls the given TransformFn to decide what new node to replace the visited node with. // and calls the given TransformFn to decide what new node to replace the visited node with.
// A new Node tree will be returned (the original is unchanged). // A new Node tree will be returned (the original is unchanged).
...@@ -45,6 +56,32 @@ func FocusedTransform(n ipld.Node, p ipld.Path, fn TransformFn) (ipld.Node, erro ...@@ -45,6 +56,32 @@ func FocusedTransform(n ipld.Node, p ipld.Path, fn TransformFn) (ipld.Node, erro
// the Path recorded of the traversal so far will continue to be extended, // the Path recorded of the traversal so far will continue to be extended,
// and thus continued nested uses of Walk and Focus will see the fully contextualized Path. // and thus continued nested uses of Walk and Focus will see the fully contextualized Path.
func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error { func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
n, err := prog.get(n, p, true)
if err != nil {
return err
}
return fn(prog, n)
}
// Get is the equivalent of Focus, but returns the reached node (rather than invoking a callback at the target),
// and does not yield Progress information.
//
// Provide configuration to this process using the Config field in the Progress object.
//
// This walk will automatically cross links, but requires some configuration
// with link loading functions to do so.
//
// If doing several traversals which are nested, consider using the Focus funcion in preference to Get;
// the Focus functions provide updated Progress objects which can be used to do nested traversals while keeping consistent track of progress,
// such that continued nested uses of Walk or Focus or Get will see the fully contextualized Path.
func (prog Progress) Get(n ipld.Node, p ipld.Path) (ipld.Node, error) {
return prog.get(n, p, false)
}
// get is the internal implementation for Focus and Get.
// It *mutates* the Progress object it's called on, and returns reached nodes.
// For Get calls, trackProgress=false, which avoids some allocations for state tracking that's not needed by that call.
func (prog *Progress) get(n ipld.Node, p ipld.Path, trackProgress bool) (ipld.Node, error) {
prog.init() prog.init()
segments := p.Segments() segments := p.Segments()
var prev ipld.Node // for LinkContext var prev ipld.Node // for LinkContext
...@@ -56,21 +93,21 @@ func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error { ...@@ -56,21 +93,21 @@ func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
case ipld.ReprKind_Map: case ipld.ReprKind_Map:
next, err := n.LookupByString(seg.String()) next, err := n.LookupByString(seg.String())
if err != nil { if err != nil {
return fmt.Errorf("error traversing segment %q on node at %q: %s", seg, p.Truncate(i), err) return nil, fmt.Errorf("error traversing segment %q on node at %q: %s", seg, p.Truncate(i), err)
} }
prev, n = n, next prev, n = n, next
case ipld.ReprKind_List: case ipld.ReprKind_List:
intSeg, err := seg.Index() intSeg, err := seg.Index()
if err != nil { 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)) return nil, 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))
} }
next, err := n.LookupByIndex(intSeg) next, err := n.LookupByIndex(intSeg)
if err != nil { if err != nil {
return fmt.Errorf("error traversing segment %q on node at %q: %s", seg, p.Truncate(i), err) return nil, fmt.Errorf("error traversing segment %q on node at %q: %s", seg, p.Truncate(i), err)
} }
prev, n = n, next prev, n = n, next
default: default:
return fmt.Errorf("cannot traverse node at %q: %s", p.Truncate(i), fmt.Errorf("cannot traverse terminals")) return nil, fmt.Errorf("cannot traverse node at %q: %s", p.Truncate(i), fmt.Errorf("cannot traverse terminals"))
} }
// Dereference any links. // Dereference any links.
for n.ReprKind() == ipld.ReprKind_Link { for n.ReprKind() == ipld.ReprKind_Link {
...@@ -84,7 +121,7 @@ func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error { ...@@ -84,7 +121,7 @@ func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
// Pick what in-memory format we will build. // Pick what in-memory format we will build.
np, err := prog.Cfg.LinkTargetNodePrototypeChooser(lnk, lnkCtx) np, err := prog.Cfg.LinkTargetNodePrototypeChooser(lnk, lnkCtx)
if err != nil { if err != nil {
return fmt.Errorf("error traversing node at %q: could not load link %q: %s", p.Truncate(i+1), lnk, err) return nil, fmt.Errorf("error traversing node at %q: could not load link %q: %s", p.Truncate(i+1), lnk, err)
} }
nb := np.NewBuilder() nb := np.NewBuilder()
// Load link! // Load link!
...@@ -95,15 +132,19 @@ func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error { ...@@ -95,15 +132,19 @@ func (prog Progress) Focus(n ipld.Node, p ipld.Path, fn VisitFn) error {
prog.Cfg.LinkLoader, prog.Cfg.LinkLoader,
) )
if err != nil { if err != nil {
return fmt.Errorf("error traversing node at %q: could not load link %q: %s", p.Truncate(i+1), lnk, err) return nil, fmt.Errorf("error traversing node at %q: could not load link %q: %s", p.Truncate(i+1), lnk, err)
}
if trackProgress {
prog.LastBlock.Path = p.Truncate(i + 1)
prog.LastBlock.Link = lnk
} }
prog.LastBlock.Path = p.Truncate(i + 1)
prog.LastBlock.Link = lnk
prev, n = n, nb.Build() prev, n = n, nb.Build()
} }
} }
prog.Path = prog.Path.Join(p) if trackProgress {
return fn(prog, n) prog.Path = prog.Path.Join(p)
}
return n, nil
} }
// FocusedTransform traverses an ipld.Node graph, reaches a single Node, // FocusedTransform traverses an ipld.Node graph, reaches a single Node,
......
...@@ -123,6 +123,26 @@ func TestFocusSingleTree(t *testing.T) { ...@@ -123,6 +123,26 @@ func TestFocusSingleTree(t *testing.T) {
}) })
} }
// covers Get used on one already-loaded Node; no link-loading exercised.
// same fixtures as the test for Focus; just has fewer assertions, since Get does no progress tracking.
func TestGetSingleTree(t *testing.T) {
t.Run("empty path on scalar node returns start node", func(t *testing.T) {
n, err := traversal.Get(basicnode.NewString("x"), ipld.Path{})
Wish(t, err, ShouldEqual, nil)
Wish(t, n, ShouldEqual, basicnode.NewString("x"))
})
t.Run("one step path on map node works", func(t *testing.T) {
n, err := traversal.Get(middleMapNode, ipld.ParsePath("foo"))
Wish(t, err, ShouldEqual, nil)
Wish(t, n, ShouldEqual, basicnode.NewBool(true))
})
t.Run("two step path on map node works", func(t *testing.T) {
n, err := traversal.Get(middleMapNode, ipld.ParsePath("nested/nonlink"))
Wish(t, err, ShouldEqual, nil)
Wish(t, n, ShouldEqual, basicnode.NewString("zoo"))
})
}
func TestFocusWithLinkLoading(t *testing.T) { func TestFocusWithLinkLoading(t *testing.T) {
t.Run("link traversal with no configured loader should fail", func(t *testing.T) { t.Run("link traversal with no configured loader should fail", func(t *testing.T) {
t.Run("terminal link should fail", func(t *testing.T) { t.Run("terminal link should fail", func(t *testing.T) {
......
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