forked from ipld/go-ipld-prime
-
Notifications
You must be signed in to change notification settings - Fork 0
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
3 changed files
with
264 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package amend | ||
|
||
import ( | ||
"fmt" | ||
"github.com/ipld/go-ipld-prime/datamodel" | ||
"github.com/ipld/go-ipld-prime/node/basicnode" | ||
) | ||
|
||
type Amd string | ||
|
||
const ( | ||
Amd_Add = "add" | ||
Amd_Remove = "remove" | ||
Amd_Replace = "replace" | ||
) | ||
|
||
type Amendment struct { | ||
Op Amd | ||
Path datamodel.Path | ||
Value datamodel.Node | ||
} | ||
|
||
// -- Node --> | ||
|
||
var _ datamodel.Node = (AmenderNode)(nil) | ||
|
||
type AmenderNode = *_AmenderNode | ||
|
||
type _AmenderNode struct { | ||
base datamodel.Node | ||
addOps []Amendment | ||
remOps map[string]Amendment | ||
} | ||
|
||
func NewAmender(base datamodel.Node) AmenderNode { | ||
return &_AmenderNode{base: base} | ||
} | ||
|
||
func (*_AmenderNode) Kind() datamodel.Kind { | ||
return datamodel.Kind_Map | ||
} | ||
func (*_AmenderNode) LookupByString(key string) (datamodel.Node, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) LookupByNode(datamodel.Node) (datamodel.Node, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) LookupByIndex(idx int64) (datamodel.Node, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) { | ||
panic("misuse") | ||
} | ||
func (a *_AmenderNode) MapIterator() datamodel.MapIterator { | ||
if a.base.Kind() != datamodel.Kind_Map { | ||
panic("misuse") | ||
} | ||
return &amender_Iterator{a, a.base.MapIterator(), 0} | ||
} | ||
func (*_AmenderNode) ListIterator() datamodel.ListIterator { | ||
panic("misuse") | ||
} | ||
func (a *_AmenderNode) Length() int64 { | ||
return a.base.Length() + int64(len(a.addOps)) - int64(len(a.remOps)) | ||
} | ||
func (*_AmenderNode) IsAbsent() bool { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) IsNull() bool { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) AsBool() (bool, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) AsInt() (int64, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) AsFloat() (float64, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) AsString() (string, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) AsBytes() ([]byte, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) AsLink() (datamodel.Link, error) { | ||
panic("misuse") | ||
} | ||
func (*_AmenderNode) Prototype() datamodel.NodePrototype { | ||
panic("misuse") | ||
} | ||
|
||
// -- Implementation --> | ||
|
||
type amender_Iterator struct { | ||
a AmenderNode | ||
b datamodel.MapIterator | ||
idx int | ||
} | ||
|
||
func (itr *amender_Iterator) Next() (k datamodel.Node, v datamodel.Node, _ error) { | ||
if itr.Done() { | ||
return nil, nil, datamodel.ErrIteratorOverread{} | ||
} | ||
if itr.b.Done() { | ||
seg, _ := itr.a.addOps[itr.idx].Path.Shift() | ||
key := seg.String() | ||
k = basicnode.NewString(key) | ||
v = itr.a.addOps[itr.idx].Value | ||
itr.idx++ | ||
return | ||
} else { | ||
for !itr.b.Done() { | ||
key, value, err := itr.b.Next() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
ks, _ := key.AsString() | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
if _, exists := itr.a.remOps[ks]; exists || value.IsAbsent() { | ||
continue | ||
} | ||
return key, value, err | ||
} | ||
} | ||
return nil, nil, datamodel.ErrIteratorOverread{} | ||
} | ||
func (itr *amender_Iterator) Done() bool { | ||
return itr.b.Done() && (itr.idx >= len(itr.a.addOps)) | ||
} | ||
|
||
func (a *_AmenderNode) Amend(op Amendment) error { | ||
//seg, path := op.Path.Shift() | ||
//key := seg.String() | ||
|
||
switch op.Op { | ||
case Amd_Add: | ||
{ | ||
a.addOps = append(a.addOps, op) | ||
} | ||
default: | ||
return fmt.Errorf("misuse: invalid operation") | ||
} | ||
return nil | ||
} |
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,83 @@ | ||
package amend | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"os" | ||
"testing" | ||
|
||
qt "github.com/frankban/quicktest" | ||
"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" | ||
"github.com/warpfork/go-testmark" | ||
) | ||
|
||
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() | ||
// 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 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,33 @@ | ||
package amend | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/ipld/go-ipld-prime/datamodel" | ||
"github.com/ipld/go-ipld-prime/traversal/patch" | ||
) | ||
|
||
func Eval(n datamodel.Node, ops []patch.Operation) (datamodel.Node, error) { | ||
var err error | ||
amender := &_AmenderNode{base: n} | ||
for _, op := range ops { | ||
err = EvalOne(amender, op) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
return amender, nil | ||
} | ||
|
||
func EvalOne(a AmenderNode, op patch.Operation) error { | ||
switch op.Op { | ||
case patch.Op_Add: | ||
return a.Amend(Amendment{ | ||
Op: Amd_Add, | ||
Path: op.Path, | ||
Value: op.Value, | ||
}) | ||
default: | ||
return fmt.Errorf("misuse: invalid operation") | ||
} | ||
} |