浏览代码

parser delta to rust

appflowy 3 年之前
父节点
当前提交
0edd442562

+ 2 - 0
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);
   }
 }

+ 1 - 1
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,

+ 6 - 12
rust-lib/flowy-net/src/response/response_serde.rs

@@ -21,8 +21,7 @@
 //             type Value = FlowyResponse<T>;
 //
 //             fn expecting(&self, formatter: &mut fmt::Formatter) ->
-// fmt::Result {                 formatter.write_str("struct Duration")
-//             }
+// fmt::Result { formatter.write_str("struct Duration") }
 //
 //             fn visit_map<V>(self, mut map: V) -> Result<Self::Value,
 // V::Error>             where
@@ -50,7 +49,7 @@
 //                                 return
 // Err(de::Error::duplicate_field("data"));                             }
 //                             data = match
-// MapAccess::next_value::<DeserializeWith<T>>(&mut map) {
+// MapAccess::next_value::<DeserializeWith<T>>(&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<T>;
 //
 //         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<E>(self, value: &str) -> Result<Self::Value, E>
 //         where

+ 2 - 0
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]

+ 1 - 1
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;

+ 74 - 136
rust-lib/flowy-ot/src/core/attributes/attribute.rs

@@ -1,93 +1,10 @@
 #![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};
-
-lazy_static! {
-    static ref BLOCK_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
-        AttributeKey::Header,
-        AttributeKey::LeftAlignment,
-        AttributeKey::CenterAlignment,
-        AttributeKey::RightAlignment,
-        AttributeKey::JustifyAlignment,
-        AttributeKey::Indent,
-        AttributeKey::Align,
-        AttributeKey::CodeBlock,
-        AttributeKey::List,
-        AttributeKey::Bullet,
-        AttributeKey::Ordered,
-        AttributeKey::Checked,
-        AttributeKey::UnChecked,
-        AttributeKey::QuoteBlock,
-    ]);
-    static ref INLINE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
-        AttributeKey::Bold,
-        AttributeKey::Italic,
-        AttributeKey::Underline,
-        AttributeKey::StrikeThrough,
-        AttributeKey::Link,
-        AttributeKey::Color,
-        AttributeKey::Font,
-        AttributeKey::Size,
-        AttributeKey::Background,
-    ]);
-    static ref INGORE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![AttributeKey::Width, AttributeKey::Height,]);
-}
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum AttributeScope {
-    Inline,
-    Block,
-    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,
-            }
-        }
-    };
-}
-
+use strum_macros::Display;
 #[derive(Debug, Clone)]
 pub struct Attribute {
     pub key: AttributeKey,
@@ -109,18 +26,14 @@ impl Attribute {
 
     // 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!(Indent, usize);
     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!(CodeBlock, bool);
     block_attribute!(QuoteBlock, bool);
 
     // ignore
@@ -130,7 +43,7 @@ impl Attribute {
 
 impl fmt::Display for Attribute {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let s = format!("{:?}:{} {:?}", self.key, self.value.as_ref(), self.scope);
+        let s = format!("{:?}:{:?} {:?}", self.key, self.value.0, self.scope);
         f.write_str(&s)
     }
 }
@@ -144,66 +57,56 @@ impl std::convert::Into<Attributes> for Attribute {
 }
 
 #[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
-#[serde(rename_all = "camelCase")]
+// serde.rs/variant-attrs.html
+// #[serde(rename_all = "snake_case")]
 pub enum AttributeKey {
-    #[display(fmt = "bold")]
+    #[serde(rename = "bold")]
     Bold,
-    #[display(fmt = "italic")]
+    #[serde(rename = "italic")]
     Italic,
-    #[display(fmt = "underline")]
+    #[serde(rename = "underline")]
     Underline,
-    #[display(fmt = "strike_through")]
+    #[serde(rename = "strikethrough")]
     StrikeThrough,
-    #[display(fmt = "font")]
+    #[serde(rename = "font")]
     Font,
-    #[display(fmt = "size")]
+    #[serde(rename = "size")]
     Size,
-    #[display(fmt = "link")]
+    #[serde(rename = "link")]
     Link,
-    #[display(fmt = "color")]
+    #[serde(rename = "color")]
     Color,
-    #[display(fmt = "background")]
+    #[serde(rename = "background")]
     Background,
-    #[display(fmt = "indent")]
+    #[serde(rename = "indent")]
     Indent,
-    #[display(fmt = "align")]
+    #[serde(rename = "align")]
     Align,
-    #[display(fmt = "code_block")]
+    #[serde(rename = "code_block")]
     CodeBlock,
-    #[display(fmt = "list")]
+    #[serde(rename = "list")]
     List,
-    #[display(fmt = "quote_block")]
+    #[serde(rename = "quote_block")]
     QuoteBlock,
-    #[display(fmt = "width")]
+    #[serde(rename = "width")]
     Width,
-    #[display(fmt = "height")]
+    #[serde(rename = "height")]
     Height,
-    #[display(fmt = "header")]
+    #[serde(rename = "header")]
     Header,
-    #[display(fmt = "left")]
-    LeftAlignment,
-    #[display(fmt = "center")]
-    CenterAlignment,
-    #[display(fmt = "right")]
-    RightAlignment,
-    #[display(fmt = "justify")]
-    JustifyAlignment,
-    #[display(fmt = "bullet")]
+    #[serde(rename = "bullet")]
     Bullet,
-    #[display(fmt = "ordered")]
+    #[serde(rename = "ordered")]
     Ordered,
-    #[display(fmt = "checked")]
+    #[serde(rename = "checked")]
     Checked,
-    #[display(fmt = "unchecked")]
+    #[serde(rename = "unchecked")]
     UnChecked,
 }
 
+// pub trait AttributeValueData<'a>: Serialize + Deserialize<'a> {}
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct AttributeValue(pub(crate) String);
-
-impl AsRef<str> for AttributeValue {
-    fn as_ref(&self) -> &str { &self.0 }
-}
+pub struct AttributeValue(pub(crate) Option<String>);
 
 impl std::convert::From<&usize> for AttributeValue {
     fn from(val: &usize) -> Self { AttributeValue::from(*val) }
@@ -212,19 +115,19 @@ impl std::convert::From<&usize> for AttributeValue {
 impl std::convert::From<usize> for AttributeValue {
     fn from(val: usize) -> Self {
         if val > (0 as usize) {
-            AttributeValue(format!("{}", val))
+            AttributeValue(Some(format!("{}", val)))
         } else {
-            AttributeValue(format!(""))
+            AttributeValue(None)
         }
     }
 }
 
 impl std::convert::From<&str> for AttributeValue {
-    fn from(val: &str) -> Self { AttributeValue(val.to_owned()) }
+    fn from(val: &str) -> Self { AttributeValue(Some(val.to_owned())) }
 }
 
 impl std::convert::From<String> for AttributeValue {
-    fn from(val: String) -> Self { AttributeValue(val) }
+    fn from(val: String) -> Self { AttributeValue(Some(val)) }
 }
 
 impl std::convert::From<&bool> for AttributeValue {
@@ -234,10 +137,10 @@ impl std::convert::From<&bool> for AttributeValue {
 impl std::convert::From<bool> for AttributeValue {
     fn from(val: bool) -> Self {
         let val = match val {
-            true => "true",
-            false => "",
+            true => Some("true".to_owned()),
+            false => None,
         };
-        AttributeValue(val.to_owned())
+        AttributeValue(val)
     }
 }
 
@@ -247,3 +150,38 @@ pub fn is_block_except_header(k: &AttributeKey) -> bool {
     }
     BLOCK_KEYS.contains(k)
 }
+
+lazy_static! {
+    static ref BLOCK_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
+        AttributeKey::Header,
+        AttributeKey::Indent,
+        AttributeKey::Align,
+        AttributeKey::CodeBlock,
+        AttributeKey::List,
+        AttributeKey::Bullet,
+        AttributeKey::Ordered,
+        AttributeKey::Checked,
+        AttributeKey::UnChecked,
+        AttributeKey::QuoteBlock,
+    ]);
+    static ref INLINE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
+        AttributeKey::Bold,
+        AttributeKey::Italic,
+        AttributeKey::Underline,
+        AttributeKey::StrikeThrough,
+        AttributeKey::Link,
+        AttributeKey::Color,
+        AttributeKey::Font,
+        AttributeKey::Size,
+        AttributeKey::Background,
+    ]);
+    static ref INGORE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![AttributeKey::Width, AttributeKey::Height,]);
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum AttributeScope {
+    Inline,
+    Block,
+    Embeds,
+    Ignore,
+}

+ 11 - 15
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<AttributeKey, AttributeValue>,
 }
 
@@ -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<AttributeKey>) {
         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
     });

+ 174 - 10
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    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::<bool>() {
+                        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::<i32>() {
+                        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<D>(deserializer: D) -> Result<Attributes, D::Error>
+    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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+            where
+                A: MapAccess<'de>,
+            {
+                let mut attributes = Attributes::new();
+                while let Some(key) = map.next_key::<AttributeKey>()? {
+                    let value = map.next_value::<AttributeValue>()?;
+                    attributes.add_kv(key, value);
+                }
+
+                Ok(attributes)
+            }
+        }
+        deserializer.deserialize_map(AttributesVisitor {})
+    }
+}
 
 impl Serialize for AttributeValue {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     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<E>(self, value: bool) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(value.into())
+            }
+
+            fn visit_i8<E>(self, value: i8) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
+
+            fn visit_i16<E>(self, value: i16) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
+
+            fn visit_i32<E>(self, value: i32) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
+
+            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
+
+            fn visit_u8<E>(self, value: u8) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
+
+            fn visit_u16<E>(self, value: u16) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
+
+            fn visit_u32<E>(self, value: u32) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
 
-            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string") }
+            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(Some(format!("{}", value))))
+            }
 
             fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
             where
                 E: de::Error,
             {
-                let attribute_value = AttributeValue(s.to_owned());
-                Ok(attribute_value)
+                Ok(s.into())
+            }
+
+            fn visit_none<E>(self) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(None))
+            }
+
+            fn visit_unit<E>(self) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(AttributeValue(None))
             }
         }
 
-        deserializer.deserialize_str(OperationSeqVisitor)
+        deserializer.deserialize_any(AttributeValueVisitor)
     }
 }

+ 47 - 0
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,
+            }
+        }
+    };
+}

+ 3 - 0
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::*;

+ 2 - 2
rust-lib/flowy-ot/src/core/delta/delta.rs

@@ -77,8 +77,8 @@ impl Delta {
 
     pub fn from_json(json: &str) -> Result<Self, OTError> {
         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)

+ 1 - 1
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 {

+ 34 - 1
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::<PlainDoc>();