diff --git a/shared-lib/lib-ot/src/core/document/attributes.rs b/shared-lib/lib-ot/src/core/document/attributes.rs index 5ad89fbee9..0c79bbc307 100644 --- a/shared-lib/lib-ot/src/core/document/attributes.rs +++ b/shared-lib/lib-ot/src/core/document/attributes.rs @@ -5,15 +5,9 @@ use std::collections::HashMap; pub type AttributeMap = HashMap; -#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] +#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] pub struct NodeAttributes(AttributeMap); -impl Default for NodeAttributes { - fn default() -> Self { - Self::new() - } -} - impl std::ops::Deref for NodeAttributes { type Target = AttributeMap; @@ -37,14 +31,14 @@ impl NodeAttributes { Self(attribute_map) } - pub fn to_inner(&self) -> AttributeMap { - self.0.clone() - } - pub fn insert>(&mut self, key: K, value: V) { self.0.insert(key.to_string(), value.into()); } + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + pub fn delete(&mut self, key: &AttributeKey) { self.insert(key.clone(), AttributeValue(None)); } diff --git a/shared-lib/lib-ot/src/core/document/mod.rs b/shared-lib/lib-ot/src/core/document/mod.rs index a0eced6c24..d01bb9502c 100644 --- a/shared-lib/lib-ot/src/core/document/mod.rs +++ b/shared-lib/lib-ot/src/core/document/mod.rs @@ -3,6 +3,7 @@ mod attributes; mod node; mod node_tree; mod operation; +mod operation_serde; mod path; mod transaction; diff --git a/shared-lib/lib-ot/src/core/document/node.rs b/shared-lib/lib-ot/src/core/document/node.rs index 8b2daec19b..eff18e2164 100644 --- a/shared-lib/lib-ot/src/core/document/node.rs +++ b/shared-lib/lib-ot/src/core/document/node.rs @@ -1,11 +1,145 @@ -use crate::core::{NodeAttributes, TextDelta}; +use crate::core::NodeBody::Delta; +use crate::core::{AttributeKey, AttributeValue, NodeAttributes, OperationTransform, TextDelta}; +use crate::errors::OTError; use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct Node { + #[serde(rename = "type")] + pub node_type: String, + + #[serde(skip_serializing_if = "NodeAttributes::is_empty")] + pub attributes: NodeAttributes, + + #[serde(skip_serializing_if = "NodeBody::is_empty")] + pub body: NodeBody, + + #[serde(skip_serializing_if = "Vec::is_empty")] + pub children: Vec, +} + +impl Node { + pub fn new(node_type: T) -> Node { + Node { + node_type: node_type.to_string(), + ..Default::default() + } + } +} + +pub struct NodeBuilder { + node: Node, +} + +impl NodeBuilder { + pub fn new(node_type: T) -> Self { + Self { + node: Node::new(node_type.to_string()), + } + } + + pub fn add_node(mut self, node: Node) -> Self { + self.node.children.push(node); + self + } + + pub fn insert_attribute(mut self, key: AttributeKey, value: AttributeValue) -> Self { + self.node.attributes.insert(key, value); + self + } + + pub fn set_body(mut self, body: NodeBody) -> Self { + self.node.body = body; + self + } + pub fn build(self) -> Node { + self.node + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum NodeBody { + Empty, + Delta(TextDelta), +} + +impl std::default::Default for NodeBody { + fn default() -> Self { + NodeBody::Empty + } +} + +impl NodeBody { + fn is_empty(&self) -> bool { + match self { + NodeBody::Empty => true, + _ => false, + } + } +} + +impl OperationTransform for NodeBody { + fn compose(&self, other: &Self) -> Result + where + Self: Sized, + { + match (self, other) { + (Delta(a), Delta(b)) => a.compose(b).map(|delta| Delta(delta)), + (NodeBody::Empty, NodeBody::Empty) => Ok(NodeBody::Empty), + (l, r) => { + let msg = format!("{:?} can not compose {:?}", l, r); + Err(OTError::internal().context(msg)) + } + } + } + + fn transform(&self, other: &Self) -> Result<(Self, Self), OTError> + where + Self: Sized, + { + match (self, other) { + (Delta(l), Delta(r)) => l.transform(r).map(|(ta, tb)| (Delta(ta), Delta(tb))), + (NodeBody::Empty, NodeBody::Empty) => Ok((NodeBody::Empty, NodeBody::Empty)), + (l, r) => { + let msg = format!("{:?} can not compose {:?}", l, r); + Err(OTError::internal().context(msg)) + } + } + } + + fn invert(&self, other: &Self) -> Self { + match (self, other) { + (Delta(l), Delta(r)) => Delta(l.invert(r)), + (NodeBody::Empty, NodeBody::Empty) => NodeBody::Empty, + (l, r) => { + tracing::error!("{:?} can not compose {:?}", l, r); + l.clone() + } + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum NodeBodyChangeset { + Delta { delta: TextDelta, inverted: TextDelta }, +} + +impl NodeBodyChangeset { + pub fn inverted(&self) -> NodeBodyChangeset { + match self { + NodeBodyChangeset::Delta { delta, inverted } => NodeBodyChangeset::Delta { + delta: inverted.clone(), + inverted: delta.clone(), + }, + } + } +} + #[derive(Clone, Eq, PartialEq, Debug)] pub struct NodeData { pub node_type: String, + pub body: NodeBody, pub attributes: NodeAttributes, - pub delta: Option, } impl NodeData { @@ -13,7 +147,16 @@ impl NodeData { NodeData { node_type: node_type.into(), attributes: NodeAttributes::new(), - delta: None, + body: NodeBody::Empty, + } + } + + pub fn apply_body_changeset(&mut self, changeset: &NodeBodyChangeset) { + match changeset { + NodeBodyChangeset::Delta { delta, inverted: _ } => match self.body.compose(&Delta(delta.clone())) { + Ok(new_body) => self.body = new_body, + Err(e) => tracing::error!("{:?}", e), + }, } } } @@ -21,9 +164,9 @@ impl NodeData { impl std::convert::From for NodeData { fn from(node: Node) -> Self { Self { - node_type: node.note_type, + node_type: node.node_type, attributes: node.attributes, - delta: node.delta, + body: node.body, } } } @@ -31,34 +174,9 @@ impl std::convert::From for NodeData { impl std::convert::From<&Node> for NodeData { fn from(node: &Node) -> Self { Self { - node_type: node.note_type.clone(), + node_type: node.node_type.clone(), attributes: node.attributes.clone(), - delta: node.delta.clone(), - } - } -} - -#[derive(Clone, Serialize, Deserialize, Eq, PartialEq)] -pub struct Node { - #[serde(rename = "type")] - pub note_type: String, - - pub attributes: NodeAttributes, - - #[serde(skip_serializing_if = "Option::is_none")] - pub delta: Option, - - #[serde(skip_serializing_if = "Vec::is_empty")] - pub children: Vec, -} - -impl Node { - pub fn new(node_type: &str) -> Node { - Node { - note_type: node_type.into(), - attributes: NodeAttributes::new(), - delta: None, - children: Vec::new(), + body: node.body.clone(), } } } diff --git a/shared-lib/lib-ot/src/core/document/node_tree.rs b/shared-lib/lib-ot/src/core/document/node_tree.rs index 2d1483d59f..04765de358 100644 --- a/shared-lib/lib-ot/src/core/document/node_tree.rs +++ b/shared-lib/lib-ot/src/core/document/node_tree.rs @@ -1,5 +1,5 @@ use crate::core::document::path::Path; -use crate::core::{Node, NodeAttributes, NodeData, NodeOperation, OperationTransform, TextDelta, Transaction}; +use crate::core::{Node, NodeAttributes, NodeBodyChangeset, NodeData, NodeOperation, OperationTransform, Transaction}; use crate::errors::{ErrorBuilder, OTError, OTErrorCode}; use indextree::{Arena, Children, FollowingSiblings, NodeId}; @@ -30,7 +30,7 @@ impl NodeTree { /// /// ``` /// use lib_ot::core::{NodeOperation, NodeTree, Node, Path}; - /// let nodes = vec![Node::new("text")]; + /// let nodes = vec![Node::new("text".to_string())]; /// let root_path: Path = vec![0].into(); /// let op = NodeOperation::Insert {path: root_path.clone(),nodes }; /// @@ -92,7 +92,7 @@ impl NodeTree { /// /// ``` /// use lib_ot::core::{NodeOperation, NodeTree, Node, Path}; - /// let node = Node::new("text"); + /// let node = Node::new("text".to_string()); /// let inserted_path: Path = vec![0].into(); /// /// let mut node_tree = NodeTree::new(); @@ -100,7 +100,7 @@ impl NodeTree { /// /// let inserted_note = node_tree.node_at_path(&inserted_path).unwrap(); /// let inserted_data = node_tree.get_node_data(inserted_note).unwrap(); - /// assert_eq!(inserted_data.node_type, node.note_type); + /// assert_eq!(inserted_data.node_type, node.node_type); /// ``` pub fn child_from_node_at_index(&self, node_id: NodeId, index: usize) -> Option { let children = node_id.children(&self.arena); @@ -147,7 +147,7 @@ impl NodeTree { NodeOperation::Insert { path, nodes } => self.insert_nodes(path, nodes), NodeOperation::Update { path, attributes, .. } => self.update_node(path, attributes), NodeOperation::Delete { path, nodes } => self.delete_node(path, nodes), - NodeOperation::TextEdit { path, delta, .. } => self.update_delta(path, delta), + NodeOperation::EditBody { path, changeset: body } => self.update_body(path, body), } } @@ -216,6 +216,7 @@ impl NodeTree { let mut update_node = self .node_at_path(path) .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; + for _ in 0..nodes.len() { let next = update_node.following_siblings(&self.arena).next(); update_node.remove_subtree(&mut self.arena); @@ -228,19 +229,11 @@ impl NodeTree { Ok(()) } - fn update_delta(&mut self, path: &Path, delta: &TextDelta) -> Result<(), OTError> { + fn update_body(&mut self, path: &Path, changeset: &NodeBodyChangeset) -> Result<(), OTError> { self.mut_node_at_path(path, |node_data| { - if node_data.delta.is_none() { - let msg = format!("The delta of the node at path:{:?} should not be empty", path); - tracing::error!("{}", msg); - return Err(OTError::new(OTErrorCode::UnexpectedEmpty, msg)); - } - let new_delta = node_data.delta.as_ref().unwrap().compose(delta)?; - let _ = node_data.delta = Some(new_delta); + node_data.apply_body_changeset(changeset); Ok(()) - })?; - - Ok(()) + }) } fn mut_node_at_path(&mut self, path: &Path, f: F) -> Result<(), OTError> diff --git a/shared-lib/lib-ot/src/core/document/operation.rs b/shared-lib/lib-ot/src/core/document/operation.rs index be250a8a19..a805f06d20 100644 --- a/shared-lib/lib-ot/src/core/document/operation.rs +++ b/shared-lib/lib-ot/src/core/document/operation.rs @@ -1,11 +1,13 @@ +use crate::core::document::operation_serde::*; use crate::core::document::path::Path; -use crate::core::{Node, NodeAttributes, TextDelta}; +use crate::core::{Node, NodeAttributes, NodeBodyChangeset}; #[derive(Clone, serde::Serialize, serde::Deserialize)] #[serde(tag = "op")] pub enum NodeOperation { #[serde(rename = "insert")] Insert { path: Path, nodes: Vec }, + #[serde(rename = "update")] Update { path: Path, @@ -13,14 +15,14 @@ pub enum NodeOperation { #[serde(rename = "oldAttributes")] old_attributes: NodeAttributes, }, + #[serde(rename = "delete")] Delete { path: Path, nodes: Vec }, - #[serde(rename = "text-edit")] - TextEdit { - path: Path, - delta: TextDelta, - inverted: TextDelta, - }, + + #[serde(rename = "edit-body")] + #[serde(serialize_with = "serialize_edit_body")] + // #[serde(deserialize_with = "operation_serde::deserialize_edit_body")] + EditBody { path: Path, changeset: NodeBodyChangeset }, } impl NodeOperation { @@ -29,7 +31,7 @@ impl NodeOperation { NodeOperation::Insert { path, .. } => path, NodeOperation::Update { path, .. } => path, NodeOperation::Delete { path, .. } => path, - NodeOperation::TextEdit { path, .. } => path, + NodeOperation::EditBody { path, .. } => path, } } pub fn invert(&self) -> NodeOperation { @@ -51,10 +53,9 @@ impl NodeOperation { path: path.clone(), nodes: nodes.clone(), }, - NodeOperation::TextEdit { path, delta, inverted } => NodeOperation::TextEdit { + NodeOperation::EditBody { path, changeset: body } => NodeOperation::EditBody { path: path.clone(), - delta: inverted.clone(), - inverted: delta.clone(), + changeset: body.inverted(), }, } } @@ -77,10 +78,9 @@ impl NodeOperation { path, nodes: nodes.clone(), }, - NodeOperation::TextEdit { delta, inverted, .. } => NodeOperation::TextEdit { - path, - delta: delta.clone(), - inverted: inverted.clone(), + NodeOperation::EditBody { path, changeset: body } => NodeOperation::EditBody { + path: path.clone(), + changeset: body.clone(), }, } } @@ -101,35 +101,27 @@ impl NodeOperation { #[cfg(test)] mod tests { - use crate::core::{Delta, Node, NodeAttributes, NodeOperation, Path}; + use crate::core::{Node, NodeAttributes, NodeBodyChangeset, NodeBuilder, NodeOperation, Path, TextDelta}; #[test] fn test_serialize_insert_operation() { let insert = NodeOperation::Insert { path: Path(vec![0, 1]), - nodes: vec![Node::new("text")], + nodes: vec![Node::new("text".to_owned())], }; let result = serde_json::to_string(&insert).unwrap(); - assert_eq!( - result, - r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","attributes":{}}]}"# - ); + assert_eq!(result, r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"#); } #[test] fn test_serialize_insert_sub_trees() { let insert = NodeOperation::Insert { path: Path(vec![0, 1]), - nodes: vec![Node { - note_type: "text".into(), - attributes: NodeAttributes::new(), - delta: None, - children: vec![Node::new("text")], - }], + nodes: vec![NodeBuilder::new("text").add_node(Node::new("text".to_owned())).build()], }; let result = serde_json::to_string(&insert).unwrap(); assert_eq!( result, - r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","attributes":{},"children":[{"type":"text","attributes":{}}]}]}"# + r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"text"}]}]}"# ); } @@ -149,12 +141,15 @@ mod tests { #[test] fn test_serialize_text_edit_operation() { - let insert = NodeOperation::TextEdit { + let changeset = NodeBodyChangeset::Delta { + delta: TextDelta::new(), + inverted: TextDelta::new(), + }; + let insert = NodeOperation::EditBody { path: Path(vec![0, 1]), - delta: Delta::new(), - inverted: Delta::new(), + changeset, }; let result = serde_json::to_string(&insert).unwrap(); - assert_eq!(result, r#"{"op":"text-edit","path":[0,1],"delta":[],"inverted":[]}"#); + assert_eq!(result, r#"{"op":"edit-body","path":[0,1],"delta":[],"inverted":[]}"#); } } diff --git a/shared-lib/lib-ot/src/core/document/operation_serde.rs b/shared-lib/lib-ot/src/core/document/operation_serde.rs new file mode 100644 index 0000000000..fddc7ecd14 --- /dev/null +++ b/shared-lib/lib-ot/src/core/document/operation_serde.rs @@ -0,0 +1,29 @@ +use crate::core::{NodeBodyChangeset, Path}; +use serde::ser::SerializeMap; +use serde::Serializer; + +pub fn serialize_edit_body(path: &Path, changeset: &NodeBodyChangeset, serializer: S) -> Result +where + S: Serializer, +{ + let mut map = serializer.serialize_map(Some(3))?; + map.serialize_key("path")?; + map.serialize_value(path)?; + + match changeset { + NodeBodyChangeset::Delta { delta, inverted } => { + map.serialize_key("delta")?; + map.serialize_value(delta)?; + map.serialize_key("inverted")?; + map.serialize_value(inverted)?; + map.end() + } + } +} + +// pub fn deserialize_edit_body<'de, D>(deserializer: D) -> Result +// where +// D: Deserializer<'de>, +// { +// todo!() +// } diff --git a/shared-lib/lib-ot/src/core/document/transaction.rs b/shared-lib/lib-ot/src/core/document/transaction.rs index 755e9419de..40a12bc40c 100644 --- a/shared-lib/lib-ot/src/core/document/transaction.rs +++ b/shared-lib/lib-ot/src/core/document/transaction.rs @@ -127,9 +127,9 @@ impl<'a> TransactionBuilder<'a> { }); Node { - note_type: node_data.node_type.clone(), + node_type: node_data.node_type.clone(), attributes: node_data.attributes.clone(), - delta: node_data.delta.clone(), + body: node_data.body.clone(), children, } } diff --git a/shared-lib/lib-ot/tests/node/test.rs b/shared-lib/lib-ot/tests/node/test.rs index aa15ff76a5..1979396838 100644 --- a/shared-lib/lib-ot/tests/node/test.rs +++ b/shared-lib/lib-ot/tests/node/test.rs @@ -1,6 +1,6 @@ use crate::node::script::NodeScript::*; use crate::node::script::NodeTest; -use lib_ot::core::{Node, NodeAttributes, Path}; +use lib_ot::core::{Node, NodeBuilder, Path}; #[test] fn node_insert_test() { @@ -23,12 +23,7 @@ fn node_insert_test() { #[test] fn node_insert_node_with_children_test() { let mut test = NodeTest::new(); - let inserted_node = Node { - note_type: "text".into(), - attributes: NodeAttributes::new(), - delta: None, - children: vec![Node::new("image")], - }; + let inserted_node = NodeBuilder::new("text").add_node(Node::new("image")).build(); let path: Path = 0.into(); let scripts = vec![ InsertNode {