-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
1,447 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package amend | ||
|
||
import "github.com/ipld/go-ipld-prime/datamodel" | ||
|
||
type Amender interface { | ||
// Get returns the node at the specified path. It will not create any intermediate nodes because this is just a | ||
// retrieval and not a modification operation. | ||
Get(path datamodel.Path) (datamodel.Node, error) | ||
|
||
// Add will add the specified Node at the specified path. If `createParents = true`, any missing parents will be | ||
// created, otherwise this function will return an error. | ||
Add(path datamodel.Path, value datamodel.Node, createParents bool) error | ||
|
||
// Remove will remove the node at the specified path and return its value. This is useful for implementing a "move" | ||
// operation, where a node can be "removed" and then "added" at a different path. | ||
Remove(path datamodel.Path) (datamodel.Node, error) | ||
|
||
// Replace will do an in-place replacement of the node at the specified path and return its previous value. | ||
Replace(path datamodel.Path, value datamodel.Node) (datamodel.Node, error) | ||
|
||
// Build returns a traversable node that can be used with existing codec implementations. An `Amender` does not | ||
// *have* to be a `Node` although currently, all `Amender` implementations are also `Node`s. | ||
Build() datamodel.Node | ||
} | ||
|
||
// NewAmender returns a new amender of the right "type" (i.e. map, list, any) using the specified base node. | ||
func NewAmender(base datamodel.Node) Amender { | ||
// Do not allow externally creating a new amender without a base node to refer to. Amendment assumes that there is | ||
// something to amend. | ||
if base == nil { | ||
panic("misuse") | ||
} | ||
return newAmender(base, nil, base.Kind(), false) | ||
} | ||
|
||
func newAmender(base datamodel.Node, parent Amender, kind datamodel.Kind, create bool) Amender { | ||
if kind == datamodel.Kind_Map { | ||
return newMapAmender(base, parent, create) | ||
} else if kind == datamodel.Kind_List { | ||
return newListAmender(base, parent, create) | ||
} else { | ||
return newAnyAmender(base, parent, create) | ||
} | ||
} | ||
|
||
func isCreated(a Amender) bool { | ||
if ma, castOk := a.(*mapAmender); castOk { | ||
return ma.created | ||
} else if la, castOk := a.(*listAmender); castOk { | ||
return la.created | ||
} else if aa, castOk := a.(*anyAmender); castOk { | ||
return aa.created | ||
} | ||
panic("misuse") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package amend | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"os" | ||
"strings" | ||
"testing" | ||
|
||
qt "github.com/frankban/quicktest" | ||
"github.com/warpfork/go-testmark" | ||
|
||
"github.com/ipld/go-ipld-prime" | ||
"github.com/ipld/go-ipld-prime/codec" | ||
"github.com/ipld/go-ipld-prime/codec/dagjson" | ||
"github.com/ipld/go-ipld-prime/traversal/patch" | ||
) | ||
|
||
func TestSpecFixtures(t *testing.T) { | ||
dir := "../../.ipld/specs/patch/fixtures/" | ||
testOneSpecFixtureFile(t, dir+"fixtures-1.md") | ||
} | ||
|
||
func testOneSpecFixtureFile(t *testing.T, filename string) { | ||
doc, err := testmark.ReadFile(filename) | ||
if os.IsNotExist(err) { | ||
t.Skipf("not running spec suite: %s (did you clone the submodule with the data?)", err) | ||
} | ||
if err != nil { | ||
t.Fatalf("spec file parse failed?!: %s", err) | ||
} | ||
|
||
// Data hunk in this spec file are in "directories" of a test scenario each. | ||
doc.BuildDirIndex() | ||
|
||
for _, dir := range doc.DirEnt.ChildrenList { | ||
t.Run(dir.Name, func(t *testing.T) { | ||
// Grab all the data hunks. | ||
// Each "directory" contains three piece of data: | ||
// - `initial` -- this is the "block". It's arbitrary example data. They're all in json (or dag-json) format, for simplicity. | ||
// - `patch` -- this is a list of patch ops. Again, as json. | ||
// - `result` -- this is the expected result object. Again, as json. | ||
initialBlob := dir.Children["initial"].Hunk.Body | ||
patchBlob := dir.Children["patch"].Hunk.Body | ||
resultBlob := dir.Children["result"].Hunk.Body | ||
|
||
// Parse everything. | ||
initial, err := ipld.Decode(initialBlob, dagjson.Decode) | ||
if err != nil { | ||
t.Fatalf("failed to parse fixture data: %s", err) | ||
} | ||
ops, err := patch.ParseBytes(patchBlob, dagjson.Decode) | ||
if err != nil { | ||
t.Fatalf("failed to parse fixture patch: %s", err) | ||
} | ||
// We don't actually keep the decoded result object. We're just gonna serialize the result and textually diff that instead. | ||
_, err = ipld.Decode(resultBlob, dagjson.Decode) | ||
if err != nil { | ||
t.Fatalf("failed to parse fixture data: %s", err) | ||
} | ||
|
||
// Do the thing! | ||
actualResult, err := Eval(initial, ops) | ||
if strings.HasSuffix(dir.Name, "-fail") { | ||
if err == nil { | ||
t.Fatalf("patch was expected to fail") | ||
} else { | ||
return | ||
} | ||
} else { | ||
if err != nil { | ||
t.Fatalf("patch did not apply: %s", err) | ||
} | ||
} | ||
|
||
// Serialize (and pretty print) result, so that we can diff it. | ||
actualResultBlob, err := ipld.Encode(actualResult, dagjson.EncodeOptions{ | ||
EncodeLinks: true, | ||
EncodeBytes: true, | ||
MapSortMode: codec.MapSortMode_None, | ||
}.Encode) | ||
if err != nil { | ||
t.Errorf("failed to reserialize result: %s", err) | ||
} | ||
var actualResultBlobPretty bytes.Buffer | ||
json.Indent(&actualResultBlobPretty, actualResultBlob, "", "\t") | ||
|
||
// Diff! | ||
qt.Assert(t, actualResultBlobPretty.String()+"\n", qt.Equals, string(resultBlob)) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package amend | ||
|
||
import ( | ||
"github.com/ipld/go-ipld-prime/datamodel" | ||
) | ||
|
||
var ( | ||
_ datamodel.Node = &anyAmender{} | ||
_ Amender = &anyAmender{} | ||
) | ||
|
||
type anyAmender struct { | ||
base datamodel.Node | ||
parent Amender | ||
created bool | ||
} | ||
|
||
func newAnyAmender(base datamodel.Node, parent Amender, create bool) Amender { | ||
return &anyAmender{base, parent, create} | ||
} | ||
|
||
func (a *anyAmender) Build() datamodel.Node { | ||
// `anyAmender` is also a `Node`. | ||
return (datamodel.Node)(a) | ||
} | ||
|
||
func (a *anyAmender) Kind() datamodel.Kind { | ||
return a.base.Kind() | ||
} | ||
|
||
func (a *anyAmender) LookupByString(key string) (datamodel.Node, error) { | ||
return a.base.LookupByString(key) | ||
} | ||
|
||
func (a *anyAmender) LookupByNode(key datamodel.Node) (datamodel.Node, error) { | ||
return a.base.LookupByNode(key) | ||
} | ||
|
||
func (a *anyAmender) LookupByIndex(idx int64) (datamodel.Node, error) { | ||
return a.base.LookupByIndex(idx) | ||
} | ||
|
||
func (a *anyAmender) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) { | ||
return a.base.LookupBySegment(seg) | ||
} | ||
|
||
func (a *anyAmender) MapIterator() datamodel.MapIterator { | ||
return a.base.MapIterator() | ||
} | ||
|
||
func (a *anyAmender) ListIterator() datamodel.ListIterator { | ||
return a.base.ListIterator() | ||
} | ||
|
||
func (a *anyAmender) Length() int64 { | ||
return a.base.Length() | ||
} | ||
|
||
func (a *anyAmender) IsAbsent() bool { | ||
return a.base.IsAbsent() | ||
} | ||
|
||
func (a *anyAmender) IsNull() bool { | ||
return a.base.IsNull() | ||
} | ||
|
||
func (a *anyAmender) AsBool() (bool, error) { | ||
return a.base.AsBool() | ||
} | ||
|
||
func (a *anyAmender) AsInt() (int64, error) { | ||
return a.base.AsInt() | ||
} | ||
|
||
func (a *anyAmender) AsFloat() (float64, error) { | ||
return a.base.AsFloat() | ||
} | ||
|
||
func (a *anyAmender) AsString() (string, error) { | ||
return a.base.AsString() | ||
} | ||
|
||
func (a *anyAmender) AsBytes() ([]byte, error) { | ||
return a.base.AsBytes() | ||
} | ||
|
||
func (a *anyAmender) AsLink() (datamodel.Link, error) { | ||
return a.base.AsLink() | ||
} | ||
|
||
func (a *anyAmender) Prototype() datamodel.NodePrototype { | ||
return a.base.Prototype() | ||
} | ||
|
||
func (a *anyAmender) Get(path datamodel.Path) (datamodel.Node, error) { | ||
// If the base node is an amender, use it, otherwise panic. | ||
if amd, castOk := a.base.(Amender); castOk { | ||
return amd.Get(path) | ||
} | ||
panic("misuse") | ||
} | ||
|
||
func (a *anyAmender) Add(path datamodel.Path, value datamodel.Node, createParents bool) error { | ||
// If the base node is an amender, use it, otherwise panic. | ||
if amd, castOk := a.base.(Amender); castOk { | ||
return amd.Add(path, value, createParents) | ||
} | ||
panic("misuse") | ||
} | ||
|
||
func (a *anyAmender) Remove(path datamodel.Path) (datamodel.Node, error) { | ||
// If the base node is an amender, use it, otherwise panic. | ||
if amd, castOk := a.base.(Amender); castOk { | ||
return amd.Remove(path) | ||
} | ||
panic("misuse") | ||
} | ||
|
||
func (a *anyAmender) Replace(path datamodel.Path, value datamodel.Node) (datamodel.Node, error) { | ||
// If the base node is an amender, use it, otherwise panic. | ||
if amd, castOk := a.base.(Amender); castOk { | ||
return amd.Replace(path, value) | ||
} | ||
panic("misuse") | ||
} |
Oops, something went wrong.