From 0edd442562eebb57abe2105e3eefe6b1d70afa97 Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 15 Sep 2021 15:01:24 +0800 Subject: [PATCH] parser delta to rust --- app_flowy/lib/workspace/domain/i_doc.dart | 2 + .../flowy-document/src/services/doc_cache.rs | 2 +- .../flowy-net/src/response/response_serde.rs | 18 +- rust-lib/flowy-ot/Cargo.toml | 2 + .../insert/reset_format_on_new_line.rs | 2 +- .../flowy-ot/src/core/attributes/attribute.rs | 360 ++++++++---------- .../src/core/attributes/attributes.rs | 26 +- .../src/core/attributes/attributes_serde.rs | 184 ++++++++- .../flowy-ot/src/core/attributes/macros.rs | 47 +++ rust-lib/flowy-ot/src/core/attributes/mod.rs | 3 + rust-lib/flowy-ot/src/core/delta/delta.rs | 4 +- rust-lib/flowy-ot/tests/helper/mod.rs | 2 +- rust-lib/flowy-ot/tests/serde_test.rs | 35 +- 13 files changed, 433 insertions(+), 254 deletions(-) create mode 100644 rust-lib/flowy-ot/src/core/attributes/macros.rs diff --git a/app_flowy/lib/workspace/domain/i_doc.dart b/app_flowy/lib/workspace/domain/i_doc.dart index 5a2891dda0..388fe114df 100644 --- a/app_flowy/lib/workspace/domain/i_doc.dart +++ b/app_flowy/lib/workspace/domain/i_doc.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flowy_editor/flowy_editor.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_editor/src/model/quill_delta.dart'; +import 'package:flowy_log/flowy_log.dart'; import 'package:flowy_sdk/protobuf/flowy-document/doc.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart'; @@ -19,6 +20,7 @@ class FlowyDoc implements EditorChangesetSender { @override void sendDelta(Delta delta) { final json = jsonEncode(delta.toJson()); + Log.debug("Send json: $json"); iDocImpl.applyChangeset(json: json); } } diff --git a/rust-lib/flowy-document/src/services/doc_cache.rs b/rust-lib/flowy-document/src/services/doc_cache.rs index 0c4d0fc74b..1d485fed52 100644 --- a/rust-lib/flowy-document/src/services/doc_cache.rs +++ b/rust-lib/flowy-document/src/services/doc_cache.rs @@ -1,5 +1,5 @@ use crate::errors::{DocError, ErrorBuilder, ErrorCode}; -use dashmap::{mapref::one::Ref, DashMap}; +use dashmap::DashMap; use flowy_ot::{ client::{Document, FlowyDoc}, core::Delta, diff --git a/rust-lib/flowy-net/src/response/response_serde.rs b/rust-lib/flowy-net/src/response/response_serde.rs index 5e1669b744..2564945f32 100644 --- a/rust-lib/flowy-net/src/response/response_serde.rs +++ b/rust-lib/flowy-net/src/response/response_serde.rs @@ -21,8 +21,7 @@ // type Value = FlowyResponse; // // fn expecting(&self, formatter: &mut fmt::Formatter) -> -// fmt::Result { formatter.write_str("struct Duration") -// } +// fmt::Result { formatter.write_str("struct Duration") } // // fn visit_map(self, mut map: V) -> Result where @@ -50,7 +49,7 @@ // return // Err(de::Error::duplicate_field("data")); } // data = match -// MapAccess::next_value::>(&mut map) { +// MapAccess::next_value::>(&mut map) { // Ok(wrapper) => wrapper.value, Err(err) => // return Err(err), }; // }, @@ -59,16 +58,12 @@ // } // let msg = msg.ok_or_else(|| // de::Error::missing_field("msg"))?; let code = -// code.ok_or_else(|| de::Error::missing_field("code"))?; +// code.ok_or_else(|| de::Error::missing_field("code"))?; // Ok(Self::Value::new(data, msg, code)) } // } // const FIELDS: &'static [&'static str] = &["msg", "code", "data"]; -// deserializer.deserialize_struct( -// "ServerResponse", -// FIELDS, -// ServerResponseVisitor(PhantomData), -// ) -// } +// deserializer.deserialize_struct("ServerResponse", FIELDS, +// ServerResponseVisitor(PhantomData)) } // } // // struct DeserializeWith<'de, T: ServerData<'de>> { @@ -101,8 +96,7 @@ // type Value = Option; // // fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { -// formatter.write_str("string or struct impl deserialize") -// } +// formatter.write_str("string or struct impl deserialize") } // // fn visit_str(self, value: &str) -> Result // where diff --git a/rust-lib/flowy-ot/Cargo.toml b/rust-lib/flowy-ot/Cargo.toml index e804e400d8..019919c9b2 100644 --- a/rust-lib/flowy-ot/Cargo.toml +++ b/rust-lib/flowy-ot/Cargo.toml @@ -15,6 +15,8 @@ color-eyre = { version = "0.5", default-features = false } chrono = "0.4.19" lazy_static = "1.4.0" url = "2.2" +strum = "0.21" +strum_macros = "0.21" [dev-dependencies] diff --git a/rust-lib/flowy-ot/src/client/extensions/insert/reset_format_on_new_line.rs b/rust-lib/flowy-ot/src/client/extensions/insert/reset_format_on_new_line.rs index f99947a921..2e69590137 100644 --- a/rust-lib/flowy-ot/src/client/extensions/insert/reset_format_on_new_line.rs +++ b/rust-lib/flowy-ot/src/client/extensions/insert/reset_format_on_new_line.rs @@ -21,7 +21,7 @@ impl InsertExt for ResetLineFormatOnNewLine { let mut reset_attribute = Attributes::new(); if next_op.get_attributes().contains_key(&AttributeKey::Header) { - reset_attribute.mark_as_removed(&AttributeKey::Header); + reset_attribute.delete(&AttributeKey::Header); } let len = index + replace_len; diff --git a/rust-lib/flowy-ot/src/core/attributes/attribute.rs b/rust-lib/flowy-ot/src/core/attributes/attribute.rs index f4bd56555d..5882fcb0d6 100644 --- a/rust-lib/flowy-ot/src/core/attributes/attribute.rs +++ b/rust-lib/flowy-ot/src/core/attributes/attribute.rs @@ -1,16 +1,159 @@ #![allow(non_snake_case)] -use crate::core::Attributes; -use derive_more::Display; + +use crate::{block_attribute, core::Attributes, ignore_attribute, inline_attribute}; use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator}; +use strum_macros::Display; +#[derive(Debug, Clone)] +pub struct Attribute { + pub key: AttributeKey, + pub value: AttributeValue, + pub scope: AttributeScope, +} + +impl Attribute { + // inline + inline_attribute!(Bold, bool); + inline_attribute!(Italic, bool); + inline_attribute!(Underline, bool); + inline_attribute!(StrikeThrough, bool); + inline_attribute!(Link, &str); + inline_attribute!(Color, String); + inline_attribute!(Font, usize); + inline_attribute!(Size, usize); + inline_attribute!(Background, String); + + // block + block_attribute!(Header, usize); + block_attribute!(Indent, usize); + block_attribute!(Align, String); + block_attribute!(List, String); + block_attribute!(Bullet, bool); + block_attribute!(Ordered, bool); + block_attribute!(Checked, bool); + block_attribute!(UnChecked, bool); + block_attribute!(CodeBlock, bool); + block_attribute!(QuoteBlock, bool); + + // ignore + ignore_attribute!(Width, usize); + ignore_attribute!(Height, usize); +} + +impl fmt::Display for Attribute { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let s = format!("{:?}:{:?} {:?}", self.key, self.value.0, self.scope); + f.write_str(&s) + } +} + +impl std::convert::Into for Attribute { + fn into(self) -> Attributes { + let mut attributes = Attributes::new(); + attributes.add(self); + attributes + } +} + +#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +// serde.rs/variant-attrs.html +// #[serde(rename_all = "snake_case")] +pub enum AttributeKey { + #[serde(rename = "bold")] + Bold, + #[serde(rename = "italic")] + Italic, + #[serde(rename = "underline")] + Underline, + #[serde(rename = "strikethrough")] + StrikeThrough, + #[serde(rename = "font")] + Font, + #[serde(rename = "size")] + Size, + #[serde(rename = "link")] + Link, + #[serde(rename = "color")] + Color, + #[serde(rename = "background")] + Background, + #[serde(rename = "indent")] + Indent, + #[serde(rename = "align")] + Align, + #[serde(rename = "code_block")] + CodeBlock, + #[serde(rename = "list")] + List, + #[serde(rename = "quote_block")] + QuoteBlock, + #[serde(rename = "width")] + Width, + #[serde(rename = "height")] + Height, + #[serde(rename = "header")] + Header, + #[serde(rename = "bullet")] + Bullet, + #[serde(rename = "ordered")] + Ordered, + #[serde(rename = "checked")] + Checked, + #[serde(rename = "unchecked")] + UnChecked, +} + +// pub trait AttributeValueData<'a>: Serialize + Deserialize<'a> {} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AttributeValue(pub(crate) Option); + +impl std::convert::From<&usize> for AttributeValue { + fn from(val: &usize) -> Self { AttributeValue::from(*val) } +} + +impl std::convert::From for AttributeValue { + fn from(val: usize) -> Self { + if val > (0 as usize) { + AttributeValue(Some(format!("{}", val))) + } else { + AttributeValue(None) + } + } +} + +impl std::convert::From<&str> for AttributeValue { + fn from(val: &str) -> Self { AttributeValue(Some(val.to_owned())) } +} + +impl std::convert::From for AttributeValue { + fn from(val: String) -> Self { AttributeValue(Some(val)) } +} + +impl std::convert::From<&bool> for AttributeValue { + fn from(val: &bool) -> Self { AttributeValue::from(*val) } +} + +impl std::convert::From for AttributeValue { + fn from(val: bool) -> Self { + let val = match val { + true => Some("true".to_owned()), + false => None, + }; + AttributeValue(val) + } +} + +pub fn is_block_except_header(k: &AttributeKey) -> bool { + if k == &AttributeKey::Header { + return false; + } + BLOCK_KEYS.contains(k) +} lazy_static! { static ref BLOCK_KEYS: HashSet = HashSet::from_iter(vec![ AttributeKey::Header, - AttributeKey::LeftAlignment, - AttributeKey::CenterAlignment, - AttributeKey::RightAlignment, - AttributeKey::JustifyAlignment, AttributeKey::Indent, AttributeKey::Align, AttributeKey::CodeBlock, @@ -42,208 +185,3 @@ pub enum AttributeScope { Embeds, Ignore, } - -macro_rules! inline_attribute { - ( - $key: ident, - $value: ty - ) => { - pub fn $key(value: $value) -> Self { - Self { - key: AttributeKey::$key, - value: value.into(), - scope: AttributeScope::Inline, - } - } - }; -} - -macro_rules! block_attribute { - ( - $key: ident, - $value: ident - ) => { - pub fn $key(value: $value) -> Self { - Self { - key: AttributeKey::$key, - value: value.into(), - scope: AttributeScope::Block, - } - } - }; -} - -macro_rules! ignore_attribute { - ( - $key: ident, - $value: ident - ) => { - pub fn $key(value: $value) -> Self { - Self { - key: AttributeKey::$key, - value: value.into(), - scope: AttributeScope::Ignore, - } - } - }; -} - -#[derive(Debug, Clone)] -pub struct Attribute { - pub key: AttributeKey, - pub value: AttributeValue, - pub scope: AttributeScope, -} - -impl Attribute { - // inline - inline_attribute!(Bold, bool); - inline_attribute!(Italic, bool); - inline_attribute!(Underline, bool); - inline_attribute!(StrikeThrough, bool); - inline_attribute!(Link, &str); - inline_attribute!(Color, String); - inline_attribute!(Font, usize); - inline_attribute!(Size, usize); - inline_attribute!(Background, String); - - // block - block_attribute!(Header, usize); - block_attribute!(LeftAlignment, usize); - block_attribute!(CenterAlignment, usize); - block_attribute!(RightAlignment, usize); - block_attribute!(JustifyAlignment, bool); - block_attribute!(Indent, String); - block_attribute!(Align, String); - block_attribute!(CodeBlock, String); - block_attribute!(List, String); - block_attribute!(Bullet, bool); - block_attribute!(Ordered, bool); - block_attribute!(Checked, bool); - block_attribute!(UnChecked, bool); - block_attribute!(QuoteBlock, bool); - - // ignore - ignore_attribute!(Width, usize); - ignore_attribute!(Height, usize); -} - -impl fmt::Display for Attribute { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let s = format!("{:?}:{} {:?}", self.key, self.value.as_ref(), self.scope); - f.write_str(&s) - } -} - -impl std::convert::Into for Attribute { - fn into(self) -> Attributes { - let mut attributes = Attributes::new(); - attributes.add(self); - attributes - } -} - -#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum AttributeKey { - #[display(fmt = "bold")] - Bold, - #[display(fmt = "italic")] - Italic, - #[display(fmt = "underline")] - Underline, - #[display(fmt = "strike_through")] - StrikeThrough, - #[display(fmt = "font")] - Font, - #[display(fmt = "size")] - Size, - #[display(fmt = "link")] - Link, - #[display(fmt = "color")] - Color, - #[display(fmt = "background")] - Background, - #[display(fmt = "indent")] - Indent, - #[display(fmt = "align")] - Align, - #[display(fmt = "code_block")] - CodeBlock, - #[display(fmt = "list")] - List, - #[display(fmt = "quote_block")] - QuoteBlock, - #[display(fmt = "width")] - Width, - #[display(fmt = "height")] - Height, - #[display(fmt = "header")] - Header, - #[display(fmt = "left")] - LeftAlignment, - #[display(fmt = "center")] - CenterAlignment, - #[display(fmt = "right")] - RightAlignment, - #[display(fmt = "justify")] - JustifyAlignment, - #[display(fmt = "bullet")] - Bullet, - #[display(fmt = "ordered")] - Ordered, - #[display(fmt = "checked")] - Checked, - #[display(fmt = "unchecked")] - UnChecked, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AttributeValue(pub(crate) String); - -impl AsRef for AttributeValue { - fn as_ref(&self) -> &str { &self.0 } -} - -impl std::convert::From<&usize> for AttributeValue { - fn from(val: &usize) -> Self { AttributeValue::from(*val) } -} - -impl std::convert::From for AttributeValue { - fn from(val: usize) -> Self { - if val > (0 as usize) { - AttributeValue(format!("{}", val)) - } else { - AttributeValue(format!("")) - } - } -} - -impl std::convert::From<&str> for AttributeValue { - fn from(val: &str) -> Self { AttributeValue(val.to_owned()) } -} - -impl std::convert::From for AttributeValue { - fn from(val: String) -> Self { AttributeValue(val) } -} - -impl std::convert::From<&bool> for AttributeValue { - fn from(val: &bool) -> Self { AttributeValue::from(*val) } -} - -impl std::convert::From for AttributeValue { - fn from(val: bool) -> Self { - let val = match val { - true => "true", - false => "", - }; - AttributeValue(val.to_owned()) - } -} - -pub fn is_block_except_header(k: &AttributeKey) -> bool { - if k == &AttributeKey::Header { - return false; - } - BLOCK_KEYS.contains(k) -} diff --git a/rust-lib/flowy-ot/src/core/attributes/attributes.rs b/rust-lib/flowy-ot/src/core/attributes/attributes.rs index 20582786e7..caf7b6c113 100644 --- a/rust-lib/flowy-ot/src/core/attributes/attributes.rs +++ b/rust-lib/flowy-ot/src/core/attributes/attributes.rs @@ -1,13 +1,10 @@ use crate::core::{Attribute, AttributeKey, AttributeValue, Operation}; use std::{collections::HashMap, fmt}; -pub const REMOVE_FLAG: &'static str = ""; -pub(crate) fn should_remove(val: &AttributeValue) -> bool { val.0 == REMOVE_FLAG } - -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq)] pub struct Attributes { - #[serde(skip_serializing_if = "HashMap::is_empty")] - #[serde(flatten)] + // #[serde(skip_serializing_if = "HashMap::is_empty")] + // #[serde(flatten)] pub(crate) inner: HashMap, } @@ -35,20 +32,19 @@ impl Attributes { self.inner.insert(key, value); } - pub fn mark_as_removed(&mut self, key: &AttributeKey) { - let value: AttributeValue = REMOVE_FLAG.into(); - self.inner.insert(key.clone(), value); - } + pub fn add_kv(&mut self, key: AttributeKey, value: AttributeValue) { self.inner.insert(key, value); } + + pub fn delete(&mut self, key: &AttributeKey) { self.inner.insert(key.clone(), AttributeValue(None)); } pub fn mark_all_as_removed_except(&mut self, attribute: Option) { match attribute { None => { - self.inner.iter_mut().for_each(|(_k, v)| v.0 = REMOVE_FLAG.into()); + self.inner.iter_mut().for_each(|(_k, v)| v.0 = None); }, Some(attribute) => { self.inner.iter_mut().for_each(|(k, v)| { if k != &attribute { - v.0 = REMOVE_FLAG.into(); + v.0 = None; } }); }, @@ -68,8 +64,8 @@ impl Attributes { // new_attributes // } - // Remove the empty attribute which value is empty. e.g. {bold: ""}. - pub fn remove_empty(&mut self) { self.inner.retain(|_, v| !should_remove(v)); } + // Remove the empty attribute which value is None. + pub fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); } pub fn extend(&mut self, other: Attributes) { self.inner.extend(other.inner); } @@ -167,7 +163,7 @@ pub fn invert_attributes(attr: Attributes, base: Attributes) -> Attributes { let inverted = attr.iter().fold(base_inverted, |mut attributes, (k, _)| { if base.get(k) != attr.get(k) && !base.contains_key(k) { - attributes.mark_as_removed(k); + attributes.delete(k); } attributes }); diff --git a/rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs b/rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs index 6b5be1ffd7..5221b0a101 100644 --- a/rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs +++ b/rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs @@ -1,13 +1,104 @@ +#[rustfmt::skip] use crate::core::AttributeValue; -use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; +use crate::core::{Attribute, AttributeKey, Attributes}; +use serde::{ + de, + de::{MapAccess, Visitor}, + ser::SerializeMap, + Deserialize, + Deserializer, + Serialize, + Serializer, +}; +use std::{collections::HashMap, fmt, marker::PhantomData, str::ParseBoolError}; + +impl Serialize for Attributes { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.is_empty() { + return serializer.serialize_none(); + } + + let mut map = serializer.serialize_map(Some(self.inner.len()))?; + for (k, v) in &self.inner { + if let Some(v) = &v.0 { + match k { + AttributeKey::Bold + | AttributeKey::Italic + | AttributeKey::Underline + | AttributeKey::StrikeThrough + | AttributeKey::CodeBlock + | AttributeKey::QuoteBlock => match &v.parse::() { + Ok(value) => map.serialize_entry(k, value)?, + Err(e) => log::error!("Serial {:?} failed. {:?}", k, e), + }, + + AttributeKey::Font + | AttributeKey::Size + | AttributeKey::Header + | AttributeKey::Indent + | AttributeKey::Width + | AttributeKey::Height => match &v.parse::() { + Ok(value) => map.serialize_entry(k, value)?, + Err(e) => log::error!("Serial {:?} failed. {:?}", k, e), + }, + + AttributeKey::Link + | AttributeKey::Color + | AttributeKey::Background + | AttributeKey::Align + | AttributeKey::Bullet + | AttributeKey::Ordered + | AttributeKey::Checked + | AttributeKey::UnChecked + | AttributeKey::List => { + map.serialize_entry(k, v)?; + }, + } + } + } + map.end() + } +} + +impl<'de> Deserialize<'de> for Attributes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AttributesVisitor; + impl<'de> Visitor<'de> for AttributesVisitor { + type Value = Attributes; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("Expect map") } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut attributes = Attributes::new(); + while let Some(key) = map.next_key::()? { + let value = map.next_value::()?; + attributes.add_kv(key, value); + } + + Ok(attributes) + } + } + deserializer.deserialize_map(AttributesVisitor {}) + } +} impl Serialize for AttributeValue { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&self.0) + match &self.0 { + None => serializer.serialize_none(), + Some(val) => serializer.serialize_str(val), + } } } @@ -16,22 +107,95 @@ impl<'de> Deserialize<'de> for AttributeValue { where D: Deserializer<'de>, { - struct OperationSeqVisitor; - - impl<'de> Visitor<'de> for OperationSeqVisitor { + struct AttributeValueVisitor; + impl<'de> Visitor<'de> for AttributeValueVisitor { type Value = AttributeValue; + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("Can't find any visit handler") } + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + Ok(value.into()) + } - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string") } + fn visit_i8(self, value: i8) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } + + fn visit_i16(self, value: i16) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } + + fn visit_i32(self, value: i32) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } + + fn visit_u8(self, value: u8) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } + + fn visit_u16(self, value: u16) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } + + fn visit_u32(self, value: u32) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(AttributeValue(Some(format!("{}", value)))) + } fn visit_str(self, s: &str) -> Result where E: de::Error, { - let attribute_value = AttributeValue(s.to_owned()); - Ok(attribute_value) + Ok(s.into()) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(AttributeValue(None)) + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(AttributeValue(None)) } } - deserializer.deserialize_str(OperationSeqVisitor) + deserializer.deserialize_any(AttributeValueVisitor) } } diff --git a/rust-lib/flowy-ot/src/core/attributes/macros.rs b/rust-lib/flowy-ot/src/core/attributes/macros.rs new file mode 100644 index 0000000000..971efd19f2 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/attributes/macros.rs @@ -0,0 +1,47 @@ +#[macro_export] +macro_rules! inline_attribute { + ( + $key: ident, + $value: ty + ) => { + pub fn $key(value: $value) -> Self { + Self { + key: AttributeKey::$key, + value: value.into(), + scope: AttributeScope::Inline, + } + } + }; +} + +#[macro_export] +macro_rules! block_attribute { + ( + $key: ident, + $value: ident + ) => { + pub fn $key(value: $value) -> Self { + Self { + key: AttributeKey::$key, + value: value.into(), + scope: AttributeScope::Block, + } + } + }; +} + +#[macro_export] +macro_rules! ignore_attribute { + ( + $key: ident, + $value: ident + ) => { + pub fn $key(value: $value) -> Self { + Self { + key: AttributeKey::$key, + value: value.into(), + scope: AttributeScope::Ignore, + } + } + }; +} diff --git a/rust-lib/flowy-ot/src/core/attributes/mod.rs b/rust-lib/flowy-ot/src/core/attributes/mod.rs index e10bc7f85e..4b55377fac 100644 --- a/rust-lib/flowy-ot/src/core/attributes/mod.rs +++ b/rust-lib/flowy-ot/src/core/attributes/mod.rs @@ -3,6 +3,9 @@ mod attributes; mod attributes_serde; mod builder; +#[macro_use] +mod macros; + pub use attribute::*; pub use attributes::*; pub use builder::*; diff --git a/rust-lib/flowy-ot/src/core/delta/delta.rs b/rust-lib/flowy-ot/src/core/delta/delta.rs index 0a2943e171..44f21001a2 100644 --- a/rust-lib/flowy-ot/src/core/delta/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta/delta.rs @@ -77,8 +77,8 @@ impl Delta { pub fn from_json(json: &str) -> Result { let delta: Delta = serde_json::from_str(json).map_err(|e| { - log::error!("Deserialize Delta failed: {:?}", e); - log::error!("{:?}", json); + log::trace!("Deserialize failed: {:?}", e); + log::trace!("{:?}", json); e })?; Ok(delta) diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index eb5cae0343..f0562f4ed9 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -6,7 +6,7 @@ use flowy_ot::{ use rand::{prelude::*, Rng as WrappedRng}; use std::{sync::Once, time::Duration}; -const LEVEL: &'static str = "info"; +const LEVEL: &'static str = "debug"; #[derive(Clone, Debug, Display)] pub enum TestOp { diff --git a/rust-lib/flowy-ot/tests/serde_test.rs b/rust-lib/flowy-ot/tests/serde_test.rs index 136d82df0a..14c9d08016 100644 --- a/rust-lib/flowy-ot/tests/serde_test.rs +++ b/rust-lib/flowy-ot/tests/serde_test.rs @@ -35,7 +35,19 @@ fn operation_delete_serialize_test() { } #[test] -fn delta_serialize_test() { +fn attributes_serialize_test() { + let attributes = AttributeBuilder::new() + .add(Attribute::Bold(true)) + .add(Attribute::Italic(true)) + .build(); + let retain = OpBuilder::insert("123").attributes(attributes).build(); + + let json = serde_json::to_string(&retain).unwrap(); + eprintln!("{}", json); +} + +#[test] +fn delta_serialize_multi_attribute_test() { let mut delta = Delta::default(); let attributes = AttributeBuilder::new() @@ -55,6 +67,27 @@ fn delta_serialize_test() { assert_eq!(delta_from_json, delta); } +#[test] +fn delta_deserialize_test() { + let json = r#"[ + {"retain":2,"attributes":{"italic":true}}, + {"retain":2,"attributes":{"italic":123}}, + {"retain":2,"attributes":{"italic":"true","bold":"true"}}, + {"retain":2,"attributes":{"italic":true,"bold":true}} + ]"#; + let delta = Delta::from_json(json).unwrap(); + eprintln!("{}", delta); +} + +#[test] +fn delta_deserialize_null_test() { + let json = r#"[ + {"retain":2,"attributes":{"italic":null}} + ]"#; + let delta = Delta::from_json(json).unwrap(); + eprintln!("{}", delta); +} + #[test] fn document_insert_serde_test() { let mut document = Document::new::();