Skip to content

Commit

Permalink
feat: add list amender tests + other fixes/cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
smrz2001 committed Jul 15, 2023
1 parent e3c116a commit 5de44d7
Show file tree
Hide file tree
Showing 8 changed files with 513 additions and 95 deletions.
23 changes: 16 additions & 7 deletions datamodel/amender.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ type containerAmender interface {
Empty() bool
Length() int64
Clear()
Values() (Node, error) // returns a list node with the values
Values() (Node, error) // returns a list Node with the values

NodeAmender
}

// MapAmender adds a map-like interface to NodeAmender
type MapAmender interface {
Put(key Node, value Node) error
Get(key Node) (Node, error)
Remove(key Node) (bool, error)
Keys() (Node, error) // returns a list node with the keys
Put(key string, value Node) error
Get(key string) (Node, error)
Remove(key string) (bool, error)
Keys() (Node, error) // returns a list Node with the keys

containerAmender
}
Expand All @@ -39,8 +39,17 @@ type MapAmender interface {
type ListAmender interface {
Get(idx int64) (Node, error)
Remove(idx int64) error
Append(values Node) error // accepts a list node
Insert(idx int64, values Node) error // accepts a list node
// Append will add Node(s) to the end of the list. It can accept a list Node with multiple values to append.
Append(value Node) error
// Insert will add Node(s) at the specified index and shift subsequent elements to the right. It can accept a list
// Node with multiple values to insert.
// Passing an index equal to the length of the list will add Node(s) to the end of the list like Append.
Insert(idx int64, value Node) error
// Set will add Node(s) at the specified index and shift subsequent elements to the right. It can accept a list Node
// with multiple values to insert.
// Passing an index equal to the length of the list will add Node(s) to the end of the list like Append.
// Set is different from Insert in that it will start its insertion at the specified index, overwriting it in the
// process, while Insert will only add the Node(s).
Set(idx int64, value Node) error

containerAmender
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/multiformats/go-multihash v0.2.3
github.com/polydawn/refmt v0.89.0
github.com/warpfork/go-testmark v0.12.1
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
gopkg.in/yaml.v2 v2.4.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvS
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
Expand Down
80 changes: 56 additions & 24 deletions node/basicnode/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"reflect"

"golang.org/x/exp/slices"

"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)
Expand Down Expand Up @@ -160,6 +162,10 @@ func (nb *plainList__Builder) Reset() {
// -- NodeAmender -->

func (nb *plainList__Builder) Transform(path datamodel.Path, transform datamodel.AmendFn) (datamodel.Node, error) {
return nb.transform(path, transform, true)
}

func (nb *plainList__Builder) transform(path datamodel.Path, transform datamodel.AmendFn, replace bool) (datamodel.Node, error) {
// Can only transform the root of the node or an immediate child.
if path.Len() > 1 {
panic("misuse")
Expand Down Expand Up @@ -217,20 +223,20 @@ func (nb *plainList__Builder) Transform(path datamodel.Path, transform datamodel
if newChildVal, err := transform(prevChildVal); err != nil {
return nil, err
} else if newChildVal == nil {
newX := make([]datamodel.NodeAmender, nb.w.Length()-1)
copy(newX, nb.w.x[:childIdx])
copy(newX[:childIdx], nb.w.x[childIdx+1:])
nb.w.x = newX
} else if err = nb.storeChildAmender(childIdx, newChildVal); err != nil {
// Ref: https://pkg.go.dev/golang.org/x/exp/slices#Delete
idx := int(childIdx)
nb.w.x[idx] = nil
nb.w.x = slices.Delete(nb.w.x, idx, idx+1)
} else if err = nb.storeChildAmender(childIdx, newChildVal, replace); err != nil {
return nil, err
}
return prevChildVal, nil
}

func (nb *plainList__Builder) storeChildAmender(childIdx int64, a datamodel.NodeAmender) error {
func (nb *plainList__Builder) storeChildAmender(childIdx int64, a datamodel.NodeAmender, replace bool) error {
var elems []datamodel.NodeAmender
n := a.Build()
if (n.Kind() == datamodel.Kind_List) && (n.Length() > 0) {
if n.Kind() == datamodel.Kind_List {
elems = make([]datamodel.NodeAmender, n.Length())
// The following logic uses a transformed list (if there is one) to perform both insertions (needed by JSON
// Patch) and replacements (needed by `focus` and `walk`), while also providing the flexibility to insert more
Expand Down Expand Up @@ -258,14 +264,36 @@ func (nb *plainList__Builder) storeChildAmender(childIdx int64, a datamodel.Node
elems = []datamodel.NodeAmender{Prototype.Any.AmendingBuilder(n)}
}
if childIdx == nb.w.Length() {
// Operations at the end of the list are straightforward - just append, and we're done.
nb.w.x = append(nb.w.x, elems...)
} else {
numElems := int64(len(elems))
newX := make([]datamodel.NodeAmender, nb.w.Length()+numElems-1)
copy(newX, nb.w.x[:childIdx])
copy(newX[childIdx:], elems)
copy(newX[childIdx+numElems:], nb.w.x[childIdx+1:])
nb.w.x = newX
return nil
}
numElems := len(elems)
if numElems > 0 {
if nb.w.x == nil {
// Allocate storage space
nb.w.x = make([]datamodel.NodeAmender, numElems)
} else {
// Allocate storage space
numElemsToAdd := numElems
if replace {
// We'll be replacing an existing element and need one slot less than the total number of elements being
// added.
numElemsToAdd--
}
nb.w.x = slices.Grow(nb.w.x, numElemsToAdd)
}
copyStartIdx := 0
if replace {
// Use the first passed element to replace the element currently at the specified index
nb.w.x[childIdx] = elems[0]
copyStartIdx++
}
if !replace || (numElems > 1) {
// If more elements were specified, insert them after the specified index.
// Ref: https://pkg.go.dev/golang.org/x/exp/slices#Insert
nb.w.x = slices.Insert(nb.w.x, int(childIdx)+copyStartIdx, elems[copyStartIdx:]...)
}
}
return nil
}
Expand All @@ -284,31 +312,35 @@ func (nb *plainList__Builder) Remove(idx int64) error {
return err
}

func (nb *plainList__Builder) Append(values datamodel.Node) error {
// Passing an index equal to the length of the list will append the passed values to the end of the list
return nb.Insert(nb.Length(), values)
func (nb *plainList__Builder) Append(value datamodel.Node) error {
return nb.Set(nb.Length(), value)
}

func (nb *plainList__Builder) Insert(idx int64, value datamodel.Node) error {
return nb.addElements(idx, value, false)
}

func (nb *plainList__Builder) Set(idx int64, value datamodel.Node) error {
return nb.addElements(idx, value, true)
}

func (nb *plainList__Builder) Insert(idx int64, values datamodel.Node) error {
func (nb *plainList__Builder) addElements(idx int64, value datamodel.Node, replaced bool) error {
var ps datamodel.PathSegment
if idx == nb.Length() {
ps = datamodel.PathSegmentOfString("-") // indicates appending to the end of the list
} else {
ps = datamodel.PathSegmentOfInt(idx)
}
_, err := nb.Transform(
_, err := nb.transform(
datamodel.NewPath([]datamodel.PathSegment{ps}),
func(_ datamodel.Node) (datamodel.NodeAmender, error) {
return Prototype.Any.AmendingBuilder(values), nil
return Prototype.Any.AmendingBuilder(value), nil
},
replaced,
)
return err
}

func (nb *plainList__Builder) Set(idx int64, value datamodel.Node) error {
return nb.Insert(idx, value)
}

func (nb *plainList__Builder) Empty() bool {
return nb.Length() == 0
}
Expand Down
Loading

0 comments on commit 5de44d7

Please sign in to comment.