Skip to content

Commit

Permalink
amend: initial
Browse files Browse the repository at this point in the history
  • Loading branch information
smrz2001 committed May 8, 2022
1 parent af16a2f commit 556c381
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .ipld
Submodule .ipld updated from 862bda to 841194
148 changes: 148 additions & 0 deletions traversal/amend/amender.go
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
}
83 changes: 83 additions & 0 deletions traversal/amend/amender_test.go
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))
})
}
}
33 changes: 33 additions & 0 deletions traversal/amend/eval.go
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")
}
}

0 comments on commit 556c381

Please sign in to comment.