Frontend/backend project structure
+ Base implementation of a differential synchronization based on Neil Fraser article/talk See https://www.youtube.com/watch?v=S2Hp_1jqpY8
This commit is contained in:
42
server/internal/diffsync/json/diff_patch.go
Normal file
42
server/internal/diffsync/json/diff_patch.go
Normal file
@ -0,0 +1,42 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/wpetit/guesstimate/internal/diffsync"
|
||||
"github.com/pkg/errors"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
)
|
||||
|
||||
type Patcher struct{}
|
||||
|
||||
func (p *Patcher) Patch(content []byte, ops diffsync.Ops) ([]byte, error) {
|
||||
patch, ok := ops.([]byte)
|
||||
if !ok {
|
||||
return nil, diffsync.ErrUnexpectedOpsFormat
|
||||
}
|
||||
|
||||
modified, err := jsonpatch.MergePatch(content, patch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not apply patch")
|
||||
}
|
||||
|
||||
return modified, nil
|
||||
}
|
||||
|
||||
func NewPatcher() *Patcher {
|
||||
return &Patcher{}
|
||||
}
|
||||
|
||||
type Differ struct{}
|
||||
|
||||
func (d *Differ) Diff(new, old []byte) (diffsync.Ops, error) {
|
||||
ops, err := jsonpatch.CreateMergePatch(old, new)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not compute diff")
|
||||
}
|
||||
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
func NewDiffer() *Differ {
|
||||
return &Differ{}
|
||||
}
|
169
server/internal/diffsync/json/json_test.go
Normal file
169
server/internal/diffsync/json/json_test.go
Normal file
@ -0,0 +1,169 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/wpetit/guesstimate/internal/diffsync"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func TestJSONSync(t *testing.T) {
|
||||
doc1 := diffsync.NewDocument(
|
||||
[]byte("{}"),
|
||||
WithJSONSync(),
|
||||
)
|
||||
|
||||
doc2 := diffsync.NewDocument(
|
||||
[]byte("{}"),
|
||||
WithJSONSync(),
|
||||
)
|
||||
|
||||
p1 := doc1.NewPeer()
|
||||
p2 := doc2.NewPeer()
|
||||
|
||||
log.Printf("p1 shadow: %s", p1.Shadow().Content())
|
||||
log.Printf("p2 shadow: %s", p2.Shadow().Content())
|
||||
|
||||
newContent1 := []byte(`{"hello":"world"}`)
|
||||
|
||||
log.Printf("updating doc1 with '%s'", newContent1)
|
||||
|
||||
stack1, err := p1.Update(newContent1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
log.Printf("applying stack1 to doc2 '%s'", doc2.Content())
|
||||
|
||||
if err := p2.Apply(stack1); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
log.Printf("new doc2 content: '%s'", doc2.Content())
|
||||
|
||||
if g, e := doc2.Content(), doc1.Content(); !jsonEqual(e, g) {
|
||||
t.Errorf("doc2.Content(): expected '%s', got '%s'", e, g)
|
||||
}
|
||||
}
|
||||
|
||||
type testStep struct {
|
||||
Local *diffsync.Peer
|
||||
Remote *diffsync.Peer
|
||||
Update string
|
||||
MatchLocal bool
|
||||
MatchRemote bool
|
||||
}
|
||||
|
||||
func TestBidirectionnalUpdate(t *testing.T) {
|
||||
doc1 := diffsync.NewDocument([]byte(`{}`), WithJSONSync())
|
||||
doc2 := diffsync.NewDocument([]byte(`{}`), WithJSONSync())
|
||||
|
||||
p1 := doc1.NewPeer()
|
||||
p2 := doc2.NewPeer()
|
||||
|
||||
var cases = []testStep{
|
||||
{
|
||||
Local: p1,
|
||||
Remote: p2,
|
||||
Update: `{"hello":"world"}`,
|
||||
MatchLocal: true,
|
||||
MatchRemote: true,
|
||||
},
|
||||
{
|
||||
Local: p2,
|
||||
Remote: p1,
|
||||
Update: `{"hello":"world","foo":"bar"}`,
|
||||
MatchLocal: true,
|
||||
MatchRemote: true,
|
||||
},
|
||||
{
|
||||
Local: p1,
|
||||
Remote: p2,
|
||||
Update: `{"hello":1,"foo":"bar"}`,
|
||||
MatchLocal: true,
|
||||
MatchRemote: true,
|
||||
},
|
||||
{
|
||||
Local: p1,
|
||||
Remote: nil,
|
||||
Update: `{"hello":1,"foo":"bar", "test":{"bar": "baz"}}`,
|
||||
MatchLocal: true,
|
||||
MatchRemote: false,
|
||||
},
|
||||
{
|
||||
Local: p2,
|
||||
Remote: p1,
|
||||
Update: `{"hello":2,"foo":"bar", "test":{"bar": "baz","world":"hello"}}`,
|
||||
MatchLocal: true,
|
||||
MatchRemote: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, step := range cases {
|
||||
func(step testStep, i int) {
|
||||
t.Run(fmt.Sprintf("Step %d", i), func(t *testing.T) {
|
||||
log.Printf("local document before update: '%s'", step.Local.Document().Content())
|
||||
|
||||
stack, err := step.Local.Update([]byte(step.Update))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
log.Printf("local document after update: '%s'", step.Local.Document().Content())
|
||||
|
||||
log.Printf("resulting stack: '%s'", spew.Sdump(stack))
|
||||
|
||||
if step.MatchLocal {
|
||||
if e, g := step.Local.Document().Content(), []byte(step.Update); !jsonEqual(e, g) {
|
||||
t.Errorf("local.Document().Content(): expected '%s', got '%s'", e, g)
|
||||
}
|
||||
}
|
||||
|
||||
if step.Remote != nil {
|
||||
log.Printf("remote document before apply: '%s'", step.Remote.Document().Content())
|
||||
if err := step.Remote.Apply(stack); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
log.Printf("remote document after apply: '%s'", step.Remote.Document().Content())
|
||||
}
|
||||
|
||||
if step.MatchRemote {
|
||||
if e, g := step.Remote.Document().Content(), []byte(step.Update); !jsonEqual(e, g) {
|
||||
t.Errorf("remote.Document().Content(): expected '%s', got '%s'", e, g)
|
||||
}
|
||||
}
|
||||
|
||||
if step.MatchLocal && step.MatchRemote {
|
||||
if e, g := step.Local.Document().Content(), step.Remote.Document().Content(); !jsonEqual(e, g) {
|
||||
t.Errorf("local.Document().Content() should match remote.Document().Content() ! Got '%s' and '%s'", e, g)
|
||||
}
|
||||
}
|
||||
})
|
||||
}(step, i)
|
||||
}
|
||||
}
|
||||
|
||||
func jsonEqual(s1, s2 []byte) bool {
|
||||
var (
|
||||
o1 interface{}
|
||||
o2 interface{}
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
err = json.Unmarshal(s1, &o1)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error mashalling []byte 1 :: %s", err.Error()))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(s2, &o2)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error mashalling []byte 2 :: %s", err.Error()))
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(o1, o2)
|
||||
}
|
10
server/internal/diffsync/json/option.go
Normal file
10
server/internal/diffsync/json/option.go
Normal file
@ -0,0 +1,10 @@
|
||||
package json
|
||||
|
||||
import "forge.cadoles.com/wpetit/guesstimate/internal/diffsync"
|
||||
|
||||
func WithJSONSync() diffsync.OptionFunc {
|
||||
return func(opt *diffsync.Option) {
|
||||
opt.Differ = NewDiffer()
|
||||
opt.Patcher = NewPatcher()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user