Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
ld
go-ld-prime
Commits
39cddf71
Commit
39cddf71
authored
Dec 27, 2020
by
Eric Myhre
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement traversal.FocusedTransform.
And a few new accessors for Path that are helpful and reasonable.
parent
8fa241ea
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
433 additions
and
6 deletions
+433
-6
path.go
path.go
+26
-1
traversal/common.go
traversal/common.go
+21
-0
traversal/focus.go
traversal/focus.go
+192
-5
traversal/focus_test.go
traversal/focus_test.go
+194
-0
No files found.
path.go
View file @
39cddf71
...
...
@@ -147,13 +147,21 @@ func (p Path) String() string {
return
sb
.
String
()
}
// Seg
e
ments returns a slice of the path segment strings.
// Segments returns a slice of the path segment strings.
//
// It is not lawful to mutate nor append the returned slice.
func
(
p
Path
)
Segments
()
[]
PathSegment
{
return
p
.
segments
}
// Len returns the number of segments in this path.
//
// Zero segments means the path refers to "the current node".
// One segment means it refers to a child of the current node; etc.
func
(
p
Path
)
Len
()
int
{
return
len
(
p
.
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
([]
PathSegment
,
len
(
p
.
segments
)
+
len
(
p2
.
segments
))
...
...
@@ -191,3 +199,20 @@ func (p Path) Parent() Path {
func
(
p
Path
)
Truncate
(
i
int
)
Path
{
return
Path
{
p
.
segments
[
0
:
i
]}
}
// Last returns the trailing segment of the path.
func
(
p
Path
)
Last
()
PathSegment
{
if
len
(
p
.
segments
)
<
1
{
return
PathSegment
{}
}
return
p
.
segments
[
len
(
p
.
segments
)
-
1
]
}
// Shift returns the first segment of the path together with the remaining path after that first segment.
// If applied to a zero-length path, it returns an empty segment and the same zero-length path.
func
(
p
Path
)
Shift
()
(
PathSegment
,
Path
)
{
if
len
(
p
.
segments
)
<
1
{
return
PathSegment
{},
Path
{}
}
return
p
.
segments
[
0
],
Path
{
p
.
segments
[
1
:
]}
}
traversal/common.go
View file @
39cddf71
...
...
@@ -45,3 +45,24 @@ func (prog *Progress) init() {
}
prog
.
Cfg
.
init
()
}
// asPathSegment figures out how to coerce a node into a PathSegment.
// If it's a typed node: we take its representation. (Could be a struct with some string representation.)
// If it's a string or an int, that's it.
// Any other case will panic. (If you're using this one keys returned by a MapIterator, though, you can ignore this possibility;
// any compliant map implementation should've already rejected that data long ago, and should not be able to yield it to you from an iterator.)
func
asPathSegment
(
n
ipld
.
Node
)
ipld
.
PathSegment
{
if
n2
,
ok
:=
n
.
(
schema
.
TypedNode
);
ok
{
n
=
n2
.
Representation
()
}
switch
n
.
Kind
()
{
case
ipld
.
Kind_String
:
s
,
_
:=
n
.
AsString
()
return
ipld
.
PathSegmentOfString
(
s
)
case
ipld
.
Kind_Int
:
i
,
_
:=
n
.
AsInt
()
return
ipld
.
PathSegmentOfInt
(
i
)
default
:
panic
(
fmt
.
Errorf
(
"cannot get pathsegment from a %s"
,
n
.
Kind
()))
}
}
traversal/focus.go
View file @
39cddf71
...
...
@@ -36,8 +36,8 @@ func Get(n ipld.Node, p ipld.Path) (ipld.Node, error) {
// It cannot cross links automatically (since this requires configuration).
// Use the equivalent FocusedTransform function on the Progress structure
// for more advanced and configurable walks.
func
FocusedTransform
(
n
ipld
.
Node
,
p
ipld
.
Path
,
fn
TransformFn
)
(
ipld
.
Node
,
error
)
{
return
Progress
{}
.
FocusedTransform
(
n
,
p
,
fn
)
func
FocusedTransform
(
n
ipld
.
Node
,
p
ipld
.
Path
,
fn
TransformFn
,
createParents
bool
)
(
ipld
.
Node
,
error
)
{
return
Progress
{}
.
FocusedTransform
(
n
,
p
,
fn
,
createParents
)
}
// Focus traverses a Node graph according to a path, reaches a single Node,
...
...
@@ -171,13 +171,200 @@ func (prog *Progress) get(n ipld.Node, p ipld.Path, trackProgress bool) (ipld.No
// using more TransformFn calls as desired to produce the replacement elements
// if it so happens that those replacement elements are easiest to construct
// by regarding them as incremental updates to the previous values.
// (This approach can also be used when doing other modifications like insertion
// or reordering -- which would otherwise be tricky to define, since
// each operation could change the meaning of subsequently used indexes.)
//
// As a special case, list appending is supported by using the path segment "-".
// (This is determined by the node it applies to -- if that path segment
// is applied to a map, it's just a regular map key of the string of dash.)
//
// Note that anything you can do with the Transform function, you can also
// 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.
//
// This feature is not yet implemented.
func
(
prog
Progress
)
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.
func
(
prog
Progress
)
FocusedTransform
(
n
ipld
.
Node
,
p
ipld
.
Path
,
fn
TransformFn
,
createParents
bool
)
(
ipld
.
Node
,
error
)
{
prog
.
init
()
nb
:=
n
.
Prototype
()
.
NewBuilder
()
if
err
:=
prog
.
focusedTransform
(
n
,
nb
,
p
,
fn
,
createParents
);
err
!=
nil
{
return
nil
,
err
}
return
nb
.
Build
(),
nil
}
// focusedTransform assumes that an update will actually happen, and as it recurses deeper,
// begins building an updated node tree.
//
// As implemented, this is not actually efficient if the update will be a no-op; it won't notice until it gets there.
func
(
prog
Progress
)
focusedTransform
(
n
ipld
.
Node
,
na
ipld
.
NodeAssembler
,
p
ipld
.
Path
,
fn
TransformFn
,
createParents
bool
)
error
{
if
p
.
Len
()
==
0
{
n2
,
err
:=
fn
(
prog
,
n
)
if
err
!=
nil
{
return
err
}
return
na
.
ConvertFrom
(
n2
)
}
seg
,
p2
:=
p
.
Shift
()
// Special branch for if we've entered createParent mode in an earlier step.
// This needs slightly different logic because there's no prior node to reference
// (and we wouldn't want to waste time creating a dummy one).
if
n
==
nil
{
ma
,
err
:=
na
.
BeginMap
(
1
)
if
err
!=
nil
{
return
err
}
prog
.
Path
=
prog
.
Path
.
AppendSegment
(
seg
)
if
err
:=
ma
.
AssembleKey
()
.
AssignString
(
seg
.
String
());
err
!=
nil
{
return
err
}
if
err
:=
prog
.
focusedTransform
(
nil
,
ma
.
AssembleValue
(),
p2
,
fn
,
createParents
);
err
!=
nil
{
return
err
}
return
ma
.
Finish
()
}
// Handle node based on kind.
// If it's a recursive kind (map or list), we'll be recursing on it.
// If it's a link, load it! And recurse on it.
// If it's a scalar kind (any of the rest), we'll... be erroring, actually;
// if we're at the end, it was already handled at the top of the function,
// so we only get to this case if we were expecting to go deeper.
switch
n
.
Kind
()
{
case
ipld
.
Kind_Map
:
ma
,
err
:=
na
.
BeginMap
(
n
.
Length
())
if
err
!=
nil
{
return
err
}
// Copy children over. Replace the target (preserving its current position!) while doing this, if found.
// Note that we don't recurse into copying children (assuming ConvertFrom doesn't); this is as shallow/COW as the ConvertFrom implementation permits.
var
replaced
bool
for
itr
:=
n
.
MapIterator
();
!
itr
.
Done
();
{
k
,
v
,
err
:=
itr
.
Next
()
if
err
!=
nil
{
return
err
}
if
err
:=
ma
.
AssembleKey
()
.
ConvertFrom
(
k
);
err
!=
nil
{
return
err
}
if
asPathSegment
(
k
)
.
Equals
(
seg
)
{
prog
.
Path
=
prog
.
Path
.
AppendSegment
(
seg
)
if
err
:=
prog
.
focusedTransform
(
v
,
ma
.
AssembleValue
(),
p2
,
fn
,
createParents
);
err
!=
nil
{
return
err
}
replaced
=
true
}
else
{
if
err
:=
ma
.
AssembleValue
()
.
ConvertFrom
(
v
);
err
!=
nil
{
return
err
}
}
}
if
replaced
{
return
ma
.
Finish
()
}
// If we didn't find the target yet: append it.
// If we're at the end, always do this;
// if we're in the middle, only do this if createParents mode is enabled.
prog
.
Path
=
prog
.
Path
.
AppendSegment
(
seg
)
if
p
.
Len
()
>
1
&&
!
createParents
{
return
fmt
.
Errorf
(
"transform: parent position at %q did not exist (and createParents was false)"
,
prog
.
Path
)
}
if
err
:=
ma
.
AssembleKey
()
.
AssignString
(
seg
.
String
());
err
!=
nil
{
return
err
}
if
err
:=
prog
.
focusedTransform
(
nil
,
ma
.
AssembleValue
(),
p2
,
fn
,
createParents
);
err
!=
nil
{
return
err
}
return
ma
.
Finish
()
case
ipld
.
Kind_List
:
la
,
err
:=
na
.
BeginList
(
n
.
Length
())
if
err
!=
nil
{
return
err
}
// First figure out if this path segment can apply to a list sanely at all.
// Simultaneously, get it in numeric format, so subsequent operations are cheaper.
ti
,
err
:=
seg
.
Index
()
if
err
!=
nil
{
if
seg
.
String
()
==
"-"
{
ti
=
-
1
}
else
{
return
fmt
.
Errorf
(
"transform: cannot navigate path segment %q at %q because a list is here"
,
seg
,
prog
.
Path
)
}
}
// Copy children over. Replace the target (preserving its current position!) while doing this, if found.
// Note that we don't recurse into copying children (assuming ConvertFrom doesn't); this is as shallow/COW as the ConvertFrom implementation permits.
var
replaced
bool
for
itr
:=
n
.
ListIterator
();
!
itr
.
Done
();
{
i
,
v
,
err
:=
itr
.
Next
()
if
err
!=
nil
{
return
err
}
if
ti
==
i
{
prog
.
Path
=
prog
.
Path
.
AppendSegment
(
seg
)
if
err
:=
prog
.
focusedTransform
(
v
,
la
.
AssembleValue
(),
p2
,
fn
,
createParents
);
err
!=
nil
{
return
err
}
replaced
=
true
}
else
{
if
err
:=
la
.
AssembleValue
()
.
ConvertFrom
(
v
);
err
!=
nil
{
return
err
}
}
}
if
replaced
{
return
la
.
Finish
()
}
// If we didn't find the target yet: hopefully this was an append operation;
// if it wasn't, then it's index out of bounds. We don't arbitrarily extend lists with filler.
if
ti
>=
0
{
return
fmt
.
Errorf
(
"transform: cannot navigate path segment %q at %q because it is beyond the list bounds"
,
seg
,
prog
.
Path
)
}
prog
.
Path
=
prog
.
Path
.
AppendSegment
(
ipld
.
PathSegmentOfInt
(
n
.
Length
()))
if
err
:=
prog
.
focusedTransform
(
nil
,
la
.
AssembleValue
(),
p2
,
fn
,
createParents
);
err
!=
nil
{
return
err
}
return
la
.
Finish
()
case
ipld
.
Kind_Link
:
lnkCtx
:=
ipld
.
LinkContext
{
LinkPath
:
prog
.
Path
,
LinkNode
:
n
,
ParentNode
:
nil
,
// TODO inconvenient that we don't have this. maybe this whole case should be a helper function.
}
lnk
,
_
:=
n
.
AsLink
()
// Pick what in-memory format we will build.
np
,
err
:=
prog
.
Cfg
.
LinkTargetNodePrototypeChooser
(
lnk
,
lnkCtx
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"transform: error traversing node at %q: could not load link %q: %s"
,
prog
.
Path
,
lnk
,
err
)
}
nb
:=
np
.
NewBuilder
()
// Load link!
err
=
lnk
.
Load
(
prog
.
Cfg
.
Ctx
,
lnkCtx
,
nb
,
prog
.
Cfg
.
LinkLoader
,
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"transform: error traversing node at %q: could not load link %q: %s"
,
prog
.
Path
,
lnk
,
err
)
}
prog
.
LastBlock
.
Path
=
prog
.
Path
prog
.
LastBlock
.
Link
=
lnk
n
=
nb
.
Build
()
// Recurse.
// Start a new builder for this, using the same prototype we just used for loading the link.
// (Or more specifically: this is an opportunity for just resetting a builder and reusing memory!)
// When we come back... we'll have to engage serialization and storage on the new node!
// Path isn't updated here (neither progress nor to-go).
nb
.
Reset
()
if
err
:=
prog
.
focusedTransform
(
n
,
nb
,
p
,
fn
,
createParents
);
err
!=
nil
{
return
err
}
n
=
nb
.
Build
()
lnk
,
err
=
lnk
.
LinkBuilder
()
.
Build
(
prog
.
Cfg
.
Ctx
,
lnkCtx
,
n
,
prog
.
Cfg
.
LinkStorer
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"transform: error storing transformed node at %q: %s"
,
prog
.
Path
,
err
)
}
return
na
.
AssignLink
(
lnk
)
default
:
return
fmt
.
Errorf
(
"transform: parent position at %q was a scalar, cannot go deeper"
,
prog
.
Path
)
}
}
traversal/focus_test.go
View file @
39cddf71
...
...
@@ -13,6 +13,7 @@ import (
cid
"github.com/ipfs/go-cid"
ipld
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/must"
_
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/fluent"
...
...
@@ -207,3 +208,196 @@ func TestGetWithLinkLoading(t *testing.T) {
Wish
(
t
,
n
,
ShouldEqual
,
basicnode
.
NewString
(
"zoo"
))
})
}
func
TestFocusedTransform
(
t
*
testing
.
T
)
{
t
.
Run
(
"UpdateMapEntry"
,
func
(
t
*
testing
.
T
)
{
n
,
err
:=
traversal
.
FocusedTransform
(
rootNode
,
ipld
.
ParsePath
(
"plain"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"plain"
)
Wish
(
t
,
must
.
String
(
prev
),
ShouldEqual
,
"olde string"
)
nb
:=
prev
.
Prototype
()
.
NewBuilder
()
nb
.
AssignString
(
"new string!"
)
return
nb
.
Build
(),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_Map
)
// updated value should be there
Wish
(
t
,
must
.
Node
(
n
.
LookupByString
(
"plain"
)),
ShouldEqual
,
basicnode
.
NewString
(
"new string!"
))
// everything else should be there
Wish
(
t
,
must
.
Node
(
n
.
LookupByString
(
"linkedString"
)),
ShouldEqual
,
must
.
Node
(
rootNode
.
LookupByString
(
"linkedString"
)))
Wish
(
t
,
must
.
Node
(
n
.
LookupByString
(
"linkedMap"
)),
ShouldEqual
,
must
.
Node
(
rootNode
.
LookupByString
(
"linkedMap"
)))
Wish
(
t
,
must
.
Node
(
n
.
LookupByString
(
"linkedList"
)),
ShouldEqual
,
must
.
Node
(
rootNode
.
LookupByString
(
"linkedList"
)))
// everything should still be in the same order
Wish
(
t
,
keys
(
n
),
ShouldEqual
,
[]
string
{
"plain"
,
"linkedString"
,
"linkedMap"
,
"linkedList"
})
})
t
.
Run
(
"UpdateDeeperMap"
,
func
(
t
*
testing
.
T
)
{
n
,
err
:=
traversal
.
FocusedTransform
(
middleMapNode
,
ipld
.
ParsePath
(
"nested/alink"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"nested/alink"
)
Wish
(
t
,
prev
,
ShouldEqual
,
basicnode
.
NewLink
(
leafAlphaLnk
))
return
basicnode
.
NewString
(
"new string!"
),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_Map
)
// updated value should be there
Wish
(
t
,
must
.
Node
(
must
.
Node
(
n
.
LookupByString
(
"nested"
))
.
LookupByString
(
"alink"
)),
ShouldEqual
,
basicnode
.
NewString
(
"new string!"
))
// everything else in the parent map should should be there!
Wish
(
t
,
must
.
Node
(
n
.
LookupByString
(
"foo"
)),
ShouldEqual
,
must
.
Node
(
middleMapNode
.
LookupByString
(
"foo"
)))
Wish
(
t
,
must
.
Node
(
n
.
LookupByString
(
"bar"
)),
ShouldEqual
,
must
.
Node
(
middleMapNode
.
LookupByString
(
"bar"
)))
// everything should still be in the same order
Wish
(
t
,
keys
(
n
),
ShouldEqual
,
[]
string
{
"foo"
,
"bar"
,
"nested"
})
})
t
.
Run
(
"AppendIfNotExists"
,
func
(
t
*
testing
.
T
)
{
n
,
err
:=
traversal
.
FocusedTransform
(
rootNode
,
ipld
.
ParsePath
(
"newpart"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"newpart"
)
Wish
(
t
,
prev
,
ShouldEqual
,
nil
)
// REVIEW: should ipld.Absent be used here? I lean towards "no" but am unsure what's least surprising here.
// An interesting thing to note about inserting a value this way is that you have no `prev.Prototype().NewBuilder()` to use if you wanted to.
// But if that's an issue, then what you do is a focus or walk (transforming or not) to the parent node, get its child prototypes, and go from there.
return
basicnode
.
NewString
(
"new string!"
),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_Map
)
// updated value should be there
Wish
(
t
,
must
.
Node
(
n
.
LookupByString
(
"newpart"
)),
ShouldEqual
,
basicnode
.
NewString
(
"new string!"
))
// everything should still be in the same order... with the new entry at the end.
Wish
(
t
,
keys
(
n
),
ShouldEqual
,
[]
string
{
"plain"
,
"linkedString"
,
"linkedMap"
,
"linkedList"
,
"newpart"
})
})
t
.
Run
(
"CreateParents"
,
func
(
t
*
testing
.
T
)
{
n
,
err
:=
traversal
.
FocusedTransform
(
rootNode
,
ipld
.
ParsePath
(
"newsection/newpart"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"newsection/newpart"
)
Wish
(
t
,
prev
,
ShouldEqual
,
nil
)
// REVIEW: should ipld.Absent be used here? I lean towards "no" but am unsure what's least surprising here.
return
basicnode
.
NewString
(
"new string!"
),
nil
},
true
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_Map
)
// a new map node in the middle should've been created
n2
:=
must
.
Node
(
n
.
LookupByString
(
"newsection"
))
Wish
(
t
,
n2
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_Map
)
// updated value should in there
Wish
(
t
,
must
.
Node
(
n2
.
LookupByString
(
"newpart"
)),
ShouldEqual
,
basicnode
.
NewString
(
"new string!"
))
// everything in the root map should still be in the same order... with the new entry at the end.
Wish
(
t
,
keys
(
n
),
ShouldEqual
,
[]
string
{
"plain"
,
"linkedString"
,
"linkedMap"
,
"linkedList"
,
"newsection"
})
// and the created intermediate map of course has just one entry.
Wish
(
t
,
keys
(
n2
),
ShouldEqual
,
[]
string
{
"newpart"
})
})
t
.
Run
(
"CreateParentsRequiresPermission"
,
func
(
t
*
testing
.
T
)
{
_
,
err
:=
traversal
.
FocusedTransform
(
rootNode
,
ipld
.
ParsePath
(
"newsection/newpart"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
true
,
ShouldEqual
,
false
)
// ought not be reached
return
nil
,
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
fmt
.
Errorf
(
"transform: parent position at
\"
newsection
\"
did not exist (and createParents was false)"
))
})
t
.
Run
(
"UpdateListEntry"
,
func
(
t
*
testing
.
T
)
{
n
,
err
:=
traversal
.
FocusedTransform
(
middleListNode
,
ipld
.
ParsePath
(
"2"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"2"
)
Wish
(
t
,
prev
,
ShouldEqual
,
basicnode
.
NewLink
(
leafBetaLnk
))
return
basicnode
.
NewString
(
"new string!"
),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_List
)
// updated value should be there
Wish
(
t
,
must
.
Node
(
n
.
LookupByIndex
(
2
)),
ShouldEqual
,
basicnode
.
NewString
(
"new string!"
))
// everything else should be there
Wish
(
t
,
n
.
Length
(),
ShouldEqual
,
int64
(
4
))
Wish
(
t
,
must
.
Node
(
n
.
LookupByIndex
(
0
)),
ShouldEqual
,
basicnode
.
NewLink
(
leafAlphaLnk
))
Wish
(
t
,
must
.
Node
(
n
.
LookupByIndex
(
1
)),
ShouldEqual
,
basicnode
.
NewLink
(
leafAlphaLnk
))
Wish
(
t
,
must
.
Node
(
n
.
LookupByIndex
(
3
)),
ShouldEqual
,
basicnode
.
NewLink
(
leafAlphaLnk
))
})
t
.
Run
(
"AppendToList"
,
func
(
t
*
testing
.
T
)
{
n
,
err
:=
traversal
.
FocusedTransform
(
middleListNode
,
ipld
.
ParsePath
(
"-"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"4"
)
Wish
(
t
,
prev
,
ShouldEqual
,
nil
)
return
basicnode
.
NewString
(
"new string!"
),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_List
)
// updated value should be there
Wish
(
t
,
must
.
Node
(
n
.
LookupByIndex
(
4
)),
ShouldEqual
,
basicnode
.
NewString
(
"new string!"
))
// everything else should be there
Wish
(
t
,
n
.
Length
(),
ShouldEqual
,
int64
(
5
))
})
t
.
Run
(
"ListBounds"
,
func
(
t
*
testing
.
T
)
{
_
,
err
:=
traversal
.
FocusedTransform
(
middleListNode
,
ipld
.
ParsePath
(
"4"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
true
,
ShouldEqual
,
false
)
// ought not be reached
return
nil
,
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
fmt
.
Errorf
(
"transform: cannot navigate path segment
\"
4
\"
at
\"\"
because it is beyond the list bounds"
))
})
t
.
Run
(
"ReplaceRoot"
,
func
(
t
*
testing
.
T
)
{
// a fairly degenerate case and no reason to do this, but should work.
n
,
err
:=
traversal
.
FocusedTransform
(
middleListNode
,
ipld
.
ParsePath
(
""
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
""
)
Wish
(
t
,
prev
,
ShouldEqual
,
middleListNode
)
nb
:=
basicnode
.
Prototype
.
Any
.
NewBuilder
()
la
,
_
:=
nb
.
BeginList
(
0
)
la
.
Finish
()
return
nb
.
Build
(),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_List
)
Wish
(
t
,
n
.
Length
(),
ShouldEqual
,
int64
(
0
))
})
}
func
TestFocusedTransformWithLinks
(
t
*
testing
.
T
)
{
var
storage2
=
make
(
map
[
ipld
.
Link
][]
byte
)
cfg
:=
traversal
.
Config
{
LinkLoader
:
func
(
lnk
ipld
.
Link
,
_
ipld
.
LinkContext
)
(
io
.
Reader
,
error
)
{
return
bytes
.
NewReader
(
storage
[
lnk
]),
nil
},
LinkTargetNodePrototypeChooser
:
func
(
_
ipld
.
Link
,
_
ipld
.
LinkContext
)
(
ipld
.
NodePrototype
,
error
)
{
return
basicnode
.
Prototype
.
Any
,
nil
},
LinkStorer
:
func
(
lnkCtx
ipld
.
LinkContext
)
(
io
.
Writer
,
ipld
.
StoreCommitter
,
error
)
{
wr
:=
bytes
.
Buffer
{}
return
&
wr
,
func
(
link
ipld
.
Link
)
error
{
storage2
[
link
]
=
wr
.
Bytes
()
return
nil
},
nil
},
}
t
.
Run
(
"UpdateMapBeyondLink"
,
func
(
t
*
testing
.
T
)
{
n
,
err
:=
traversal
.
Progress
{
Cfg
:
&
cfg
,
}
.
FocusedTransform
(
rootNode
,
ipld
.
ParsePath
(
"linkedMap/nested/nonlink"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"linkedMap/nested/nonlink"
)
Wish
(
t
,
must
.
String
(
prev
),
ShouldEqual
,
"zoo"
)
Wish
(
t
,
progress
.
LastBlock
.
Path
.
String
(),
ShouldEqual
,
"linkedMap"
)
Wish
(
t
,
progress
.
LastBlock
.
Link
.
String
(),
ShouldEqual
,
"baguqefye7xlxqda"
)
nb
:=
prev
.
Prototype
()
.
NewBuilder
()
nb
.
AssignString
(
"new string!"
)
return
nb
.
Build
(),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_Map
)
// there should be a new object in our new storage!
Wish
(
t
,
len
(
storage2
),
ShouldEqual
,
1
)
// cleanup for next test
storage2
=
make
(
map
[
ipld
.
Link
][]
byte
)
})
t
.
Run
(
"UpdateNotBeyondLink"
,
func
(
t
*
testing
.
T
)
{
// This is replacing a link with a non-link. Doing so shouldn't hit storage.
n
,
err
:=
traversal
.
Progress
{
Cfg
:
&
cfg
,
}
.
FocusedTransform
(
rootNode
,
ipld
.
ParsePath
(
"linkedMap"
),
func
(
progress
traversal
.
Progress
,
prev
ipld
.
Node
)
(
ipld
.
Node
,
error
)
{
Wish
(
t
,
progress
.
Path
.
String
(),
ShouldEqual
,
"linkedMap"
)
nb
:=
prev
.
Prototype
()
.
NewBuilder
()
nb
.
AssignString
(
"new string!"
)
return
nb
.
Build
(),
nil
},
false
)
Wish
(
t
,
err
,
ShouldEqual
,
nil
)
Wish
(
t
,
n
.
Kind
(),
ShouldEqual
,
ipld
.
Kind_Map
)
// there should be no new objects in our new storage!
Wish
(
t
,
len
(
storage2
),
ShouldEqual
,
0
)
// cleanup for next test
storage2
=
make
(
map
[
ipld
.
Link
][]
byte
)
})
// link traverse to scalar // this is unspecifiable using the current path syntax! you'll just end up replacing the link with the scalar!
}
func
keys
(
n
ipld
.
Node
)
[]
string
{
v
:=
make
([]
string
,
0
,
n
.
Length
())
for
itr
:=
n
.
MapIterator
();
!
itr
.
Done
();
{
k
,
_
,
_
:=
itr
.
Next
()
v
=
append
(
v
,
must
.
String
(
k
))
}
return
v
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment