diff --git a/api/converter/converter_test.go b/api/converter/converter_test.go index c5d00c09c..4412a9587 100644 --- a/api/converter/converter_test.go +++ b/api/converter/converter_test.go @@ -36,13 +36,8 @@ import ( func TestConverter(t *testing.T) { t.Run("snapshot simple test", func(t *testing.T) { - obj, err := converter.BytesToObject(nil) - assert.NoError(t, err) - assert.Equal(t, "{}", obj.Marshal()) - doc := document.New("d1") - - err = doc.Update(func(root *json.Object, p *presence.Presence) error { + err := doc.Update(func(root *json.Object, p *presence.Presence) error { root.SetNewText("k1").Edit(0, 0, "A") return nil }) @@ -59,7 +54,7 @@ func TestConverter(t *testing.T) { bytes, err := converter.ObjectToBytes(doc.RootObject()) assert.NoError(t, err) - obj, err = converter.BytesToObject(bytes) + obj, err := converter.BytesToObject(bytes) assert.NoError(t, err) assert.Equal(t, `{"k1":[{"val":"B"}]}`, obj.Marshal()) }) @@ -243,6 +238,28 @@ func TestConverter(t *testing.T) { assert.Equal(t, tree.ToXML(), clone.ToXML()) }) + t.Run("array converting to bytes test", func(t *testing.T) { + root := helper.TestRoot() + ctx := helper.TextChangeContext(root) + + treeList := crdt.NewRGATreeList() + arr := crdt.NewArray(treeList, ctx.IssueTimeTicket()) + primitive, _ := crdt.NewPrimitive("1", ctx.IssueTimeTicket()) + _ = arr.Add(primitive) + primitive, _ = crdt.NewPrimitive("2", ctx.IssueTimeTicket()) + _ = arr.Add(primitive) + primitive, _ = crdt.NewPrimitive("3", ctx.IssueTimeTicket()) + _ = arr.Add(primitive) + + bytes, err := converter.ArrayToBytes(arr) + assert.NoError(t, err) + clone, err := converter.BytesToArray(bytes) + assert.NoError(t, err) + + assert.Equal(t, `["1","2","3"]`, arr.Marshal()) + assert.Equal(t, `["1","2","3"]`, clone.Marshal()) + }) + t.Run("empty presence converting test", func(t *testing.T) { change, err := innerpresence.NewChangeFromJSON(`{"ChangeType":"put","Presence":{}}`) assert.NoError(t, err) diff --git a/api/converter/from_bytes.go b/api/converter/from_bytes.go index 4aa5b4bfb..ee6580fa2 100644 --- a/api/converter/from_bytes.go +++ b/api/converter/from_bytes.go @@ -51,7 +51,7 @@ func BytesToSnapshot(snapshot []byte) (*crdt.Object, *innerpresence.Map, error) // BytesToObject creates an Object from the given byte array. func BytesToObject(snapshot []byte) (*crdt.Object, error) { if snapshot == nil { - return crdt.NewObject(crdt.NewElementRHT(), time.InitialTicket), nil + return nil, errors.New("snapshot should not be nil") } pbElem := &api.JSONElement{} @@ -67,6 +67,25 @@ func BytesToObject(snapshot []byte) (*crdt.Object, error) { return obj, nil } +// BytesToArray creates a Array from the given byte array. +func BytesToArray(snapshot []byte) (*crdt.Array, error) { + if snapshot == nil { + return nil, errors.New("snapshot should not be nil") + } + + pbArray := &api.JSONElement{} + if err := proto.Unmarshal(snapshot, pbArray); err != nil { + return nil, fmt.Errorf("unmarshal array: %w", err) + } + + array, err := fromJSONArray(pbArray.GetJsonArray()) + if err != nil { + return nil, err + } + + return array, nil +} + // BytesToTree creates a Tree from the given byte array. func BytesToTree(snapshot []byte) (*crdt.Tree, error) { if snapshot == nil { diff --git a/api/converter/from_pb.go b/api/converter/from_pb.go index d6161c719..a08a6a1f4 100644 --- a/api/converter/from_pb.go +++ b/api/converter/from_pb.go @@ -728,21 +728,27 @@ func fromTimeTicket(pbTicket *api.TimeTicket) (*time.Ticket, error) { func fromElement(pbElement *api.JSONElementSimple) (crdt.Element, error) { switch pbType := pbElement.Type; pbType { case api.ValueType_VALUE_TYPE_JSON_OBJECT: - createdAt, err := fromTimeTicket(pbElement.CreatedAt) - if err != nil { - return nil, err + if pbElement.Value == nil { + createdAt, err := fromTimeTicket(pbElement.CreatedAt) + if err != nil { + return nil, err + } + return crdt.NewObject( + crdt.NewElementRHT(), + createdAt, + ), nil } - return crdt.NewObject( - crdt.NewElementRHT(), - createdAt, - ), nil + return BytesToObject(pbElement.Value) case api.ValueType_VALUE_TYPE_JSON_ARRAY: - createdAt, err := fromTimeTicket(pbElement.CreatedAt) - if err != nil { - return nil, err + if pbElement.Value == nil { + createdAt, err := fromTimeTicket(pbElement.CreatedAt) + if err != nil { + return nil, err + } + elements := crdt.NewRGATreeList() + return crdt.NewArray(elements, createdAt), nil } - elements := crdt.NewRGATreeList() - return crdt.NewArray(elements, createdAt), nil + return BytesToArray(pbElement.Value) case api.ValueType_VALUE_TYPE_NULL: fallthrough case api.ValueType_VALUE_TYPE_BOOLEAN: diff --git a/api/converter/to_bytes.go b/api/converter/to_bytes.go index 13016b4e9..048cdb3e1 100644 --- a/api/converter/to_bytes.go +++ b/api/converter/to_bytes.go @@ -62,6 +62,19 @@ func ObjectToBytes(obj *crdt.Object) ([]byte, error) { return bytes, nil } +// ArrayToBytes converts the given array to byte array. +func ArrayToBytes(array *crdt.Array) ([]byte, error) { + pbArray, err := toJSONArray(array) + if err != nil { + return nil, err + } + bytes, err := proto.Marshal(pbArray) + if err != nil { + return nil, fmt.Errorf("marshal Array to bytes: %w", err) + } + return bytes, nil +} + // TreeToBytes converts the given tree to byte array. func TreeToBytes(tree *crdt.Tree) ([]byte, error) { pbTree := toTree(tree) diff --git a/api/converter/to_pb.go b/api/converter/to_pb.go index 11c195e4c..3091b1342 100644 --- a/api/converter/to_pb.go +++ b/api/converter/to_pb.go @@ -409,14 +409,24 @@ func toTreeStyle(style *operations.TreeStyle) (*api.Operation_TreeStyle_, error) func toJSONElementSimple(elem crdt.Element) (*api.JSONElementSimple, error) { switch elem := elem.(type) { case *crdt.Object: + bytes, err := ObjectToBytes(elem) + if err != nil { + return nil, err + } return &api.JSONElementSimple{ Type: api.ValueType_VALUE_TYPE_JSON_OBJECT, CreatedAt: ToTimeTicket(elem.CreatedAt()), + Value: bytes, }, nil case *crdt.Array: + bytes, err := ArrayToBytes(elem) + if err != nil { + return nil, err + } return &api.JSONElementSimple{ Type: api.ValueType_VALUE_TYPE_JSON_ARRAY, CreatedAt: ToTimeTicket(elem.CreatedAt()), + Value: bytes, }, nil case *crdt.Primitive: pbValueType, err := toValueType(elem.ValueType())