Browse Source

[rust]: fix delta serial bugs & add some tests

appflowy 3 years ago
parent
commit
ce2ccf7d0a

+ 2 - 1
app_flowy/lib/workspace/presentation/stack_page/doc/doc_page.dart

@@ -41,7 +41,8 @@ class _DocPageState extends State<DocPage> {
       ],
       ],
       child: BlocBuilder<DocBloc, DocState>(builder: (context, state) {
       child: BlocBuilder<DocBloc, DocState>(builder: (context, state) {
         return state.loadState.map(
         return state.loadState.map(
-          loading: (_) => const FlowyProgressIndicator(),
+          // loading: (_) => const FlowyProgressIndicator(),
+          loading: (_) => SizedBox.expand(child: Container(color: Colors.white)),
           finish: (result) => result.successOrFail.fold(
           finish: (result) => result.successOrFail.fold(
             (_) {
             (_) {
               if (state.forceClose) {
               if (state.forceClose) {

+ 1 - 2
app_flowy/lib/workspace/presentation/widgets/menu/menu.dart

@@ -4,7 +4,6 @@ import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -164,7 +163,6 @@ class MenuSharedState extends ChangeNotifier {
     super.addListener(() {
     super.addListener(() {
       if (_forcedOpenView != null) {
       if (_forcedOpenView != null) {
         callback(_forcedOpenView!);
         callback(_forcedOpenView!);
-        _forcedOpenView = null;
       }
       }
     });
     });
   }
   }
@@ -181,6 +179,7 @@ class MenuSharedState extends ChangeNotifier {
       selectedView = view;
       selectedView = view;
       notifyListeners();
       notifyListeners();
     }
     }
+    _forcedOpenView = null;
   }
   }
 
 
   set selectedView(View? view) {
   set selectedView(View? view) {

+ 4 - 0
rust-lib/flowy-document/src/services/doc/doc_controller.rs

@@ -74,6 +74,10 @@ impl DocController {
         Ok(())
         Ok(())
     }
     }
 
 
+    // the delta's data that contains attributes with null value will be considered
+    // as None e.g.
+    // json : {"retain":7,"attributes":{"bold":null}}
+    // deserialize delta: [ {retain: 7, attributes: {Bold: AttributeValue(None)}} ]
     #[tracing::instrument(level = "debug", skip(self, delta), err)]
     #[tracing::instrument(level = "debug", skip(self, delta), err)]
     pub(crate) async fn edit_doc(&self, delta: DocDelta) -> Result<DocDelta, DocError> {
     pub(crate) async fn edit_doc(&self, delta: DocDelta) -> Result<DocDelta, DocError> {
         let edit_doc_ctx = self.cache.get(&delta.doc_id)?;
         let edit_doc_ctx = self.cache.get(&delta.doc_id)?;

+ 2 - 1
rust-lib/flowy-document/src/services/doc/document/document.rs

@@ -68,6 +68,7 @@ impl Document {
     }
     }
 
 
     pub fn compose_delta(&mut self, delta: &Delta) -> Result<(), DocError> {
     pub fn compose_delta(&mut self, delta: &Delta) -> Result<(), DocError> {
+        log::trace!("😁 {} compose {}", &self.delta.to_json(), delta.to_json());
         let composed_delta = self.delta.compose(delta)?;
         let composed_delta = self.delta.compose(delta)?;
         let mut undo_delta = delta.invert(&self.delta);
         let mut undo_delta = delta.invert(&self.delta);
 
 
@@ -88,7 +89,7 @@ impl Document {
             self.history.record(undo_delta);
             self.history.record(undo_delta);
         }
         }
 
 
-        log::trace!("document delta: {}", &composed_delta);
+        log::trace!("😁😁 compose result: {}", composed_delta.to_json());
         self.set_delta(composed_delta);
         self.set_delta(composed_delta);
         Ok(())
         Ok(())
     }
     }

+ 2 - 2
rust-lib/flowy-document/src/services/doc/revision/model.rs

@@ -13,12 +13,12 @@ use tokio::sync::broadcast;
 pub type RevIdReceiver = broadcast::Receiver<i64>;
 pub type RevIdReceiver = broadcast::Receiver<i64>;
 pub type RevIdSender = broadcast::Sender<i64>;
 pub type RevIdSender = broadcast::Sender<i64>;
 
 
-pub struct RevisionContext {
+pub struct RevisionRecord {
     pub revision: Revision,
     pub revision: Revision,
     pub state: RevState,
     pub state: RevState,
 }
 }
 
 
-impl RevisionContext {
+impl RevisionRecord {
     pub fn new(revision: Revision) -> Self {
     pub fn new(revision: Revision) -> Self {
         Self {
         Self {
             revision,
             revision,

+ 6 - 7
rust-lib/flowy-document/src/services/doc/revision/persistence.rs

@@ -19,10 +19,10 @@ use tokio::{
 pub struct RevisionStore {
 pub struct RevisionStore {
     doc_id: String,
     doc_id: String,
     persistence: Arc<Persistence>,
     persistence: Arc<Persistence>,
-    revs_map: Arc<DashMap<i64, RevisionContext>>,
+    revs_map: Arc<DashMap<i64, RevisionRecord>>,
     pending_tx: PendingSender,
     pending_tx: PendingSender,
     pending_revs: Arc<RwLock<VecDeque<PendingRevId>>>,
     pending_revs: Arc<RwLock<VecDeque<PendingRevId>>>,
-    delay_save: RwLock<Option<JoinHandle<()>>>,
+    defer_save_oper: RwLock<Option<JoinHandle<()>>>,
     server: Arc<dyn RevisionServer>,
     server: Arc<dyn RevisionServer>,
 }
 }
 
 
@@ -45,7 +45,7 @@ impl RevisionStore {
             revs_map,
             revs_map,
             pending_revs,
             pending_revs,
             pending_tx,
             pending_tx,
-            delay_save: RwLock::new(None),
+            defer_save_oper: RwLock::new(None),
             server,
             server,
         });
         });
 
 
@@ -75,7 +75,7 @@ impl RevisionStore {
 
 
         let pending_rev = PendingRevId::new(revision.rev_id, sender);
         let pending_rev = PendingRevId::new(revision.rev_id, sender);
         self.pending_revs.write().await.push_back(pending_rev);
         self.pending_revs.write().await.push_back(pending_rev);
-        self.revs_map.insert(revision.rev_id, RevisionContext::new(revision));
+        self.revs_map.insert(revision.rev_id, RevisionRecord::new(revision));
 
 
         let _ = self.pending_tx.send(PendingMsg::Revision { ret: receiver });
         let _ = self.pending_tx.send(PendingMsg::Revision { ret: receiver });
         self.save_revisions().await;
         self.save_revisions().await;
@@ -94,7 +94,7 @@ impl RevisionStore {
     }
     }
 
 
     async fn save_revisions(&self) {
     async fn save_revisions(&self) {
-        if let Some(handler) = self.delay_save.write().await.take() {
+        if let Some(handler) = self.defer_save_oper.write().await.take() {
             handler.abort();
             handler.abort();
         }
         }
 
 
@@ -105,7 +105,7 @@ impl RevisionStore {
         let revs_map = self.revs_map.clone();
         let revs_map = self.revs_map.clone();
         let persistence = self.persistence.clone();
         let persistence = self.persistence.clone();
 
 
-        *self.delay_save.write().await = Some(tokio::spawn(async move {
+        *self.defer_save_oper.write().await = Some(tokio::spawn(async move {
             tokio::time::sleep(Duration::from_millis(300)).await;
             tokio::time::sleep(Duration::from_millis(300)).await;
             let ids = revs_map.iter().map(|kv| kv.key().clone()).collect::<Vec<i64>>();
             let ids = revs_map.iter().map(|kv| kv.key().clone()).collect::<Vec<i64>>();
             let revisions_state = revs_map
             let revisions_state = revs_map
@@ -194,7 +194,6 @@ async fn fetch_from_local(doc_id: &str, persistence: Arc<Persistence>) -> DocRes
                 },
                 },
             }
             }
         }
         }
-
         Result::<Doc, DocError>::Ok(Doc {
         Result::<Doc, DocError>::Ok(Doc {
             id: doc_id,
             id: doc_id,
             data: delta.to_json(),
             data: delta.to_json(),

+ 41 - 1
rust-lib/flowy-document/tests/editor/attribute_test.rs

@@ -1,6 +1,7 @@
 use crate::editor::{TestBuilder, TestOp::*};
 use crate::editor::{TestBuilder, TestOp::*};
 use flowy_document::services::doc::{FlowyDoc, PlainDoc};
 use flowy_document::services::doc::{FlowyDoc, PlainDoc};
-use flowy_ot::core::{Interval, NEW_LINE, WHITESPACE};
+use flowy_ot::core::{Delta, DeltaBuilder, Interval, OperationTransformable, NEW_LINE, WHITESPACE};
+use std::str::FromStr;
 
 
 #[test]
 #[test]
 fn attributes_bold_added() {
 fn attributes_bold_added() {
@@ -736,3 +737,42 @@ fn attributes_preserve_list_format_on_merge() {
 
 
     TestBuilder::new().run_script::<FlowyDoc>(ops);
     TestBuilder::new().run_script::<FlowyDoc>(ops);
 }
 }
+
+#[test]
+fn delta_compose() {
+    let mut delta = Delta::from_json(r#"[{"insert":"\n"}]"#).unwrap();
+    let deltas = vec![
+        Delta::from_json(r#"[{"retain":1,"attributes":{"list":"unchecked"}}]"#).unwrap(),
+        Delta::from_json(r#"[{"insert":"a"}]"#).unwrap(),
+        Delta::from_json(r#"[{"retain":1},{"insert":"\n","attributes":{"list":"unchecked"}}]"#).unwrap(),
+        Delta::from_json(r#"[{"retain":2},{"retain":1,"attributes":{"list":""}}]"#).unwrap(),
+    ];
+
+    for d in deltas {
+        delta = delta.compose(&d).unwrap();
+    }
+    assert_eq!(
+        delta.to_json(),
+        r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\n"}]"#
+    );
+
+    let ops = vec![
+        AssertDocJson(0, r#"[{"insert":"\n"}]"#),
+        Insert(0, "a", 0),
+        AssertDocJson(0, r#"[{"insert":"a\n"}]"#),
+        Bullet(0, Interval::new(0, 1), true),
+        AssertDocJson(0, r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
+        Insert(0, NEW_LINE, 1),
+        AssertDocJson(
+            0,
+            r#"[{"insert":"a"},{"insert":"\n\n","attributes":{"list":"bullet"}}]"#,
+        ),
+        Insert(0, NEW_LINE, 2),
+        AssertDocJson(
+            0,
+            r#"[{"insert":"a"},{"insert":"\n","attributes":{"list":"bullet"}},{"insert":"\n"}]"#,
+        ),
+    ];
+
+    TestBuilder::new().run_script::<FlowyDoc>(ops);
+}

+ 10 - 0
rust-lib/flowy-document/tests/editor/mod.rs

@@ -105,16 +105,20 @@ impl TestBuilder {
             TestOp::Insert(delta_i, s, index) => {
             TestOp::Insert(delta_i, s, index) => {
                 let document = &mut self.documents[*delta_i];
                 let document = &mut self.documents[*delta_i];
                 let delta = document.insert(*index, s).unwrap();
                 let delta = document.insert(*index, s).unwrap();
+                log::debug!("Insert delta: {}", delta.to_json());
+
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::Delete(delta_i, iv) => {
             TestOp::Delete(delta_i, iv) => {
                 let document = &mut self.documents[*delta_i];
                 let document = &mut self.documents[*delta_i];
                 let delta = document.replace(*iv, "").unwrap();
                 let delta = document.replace(*iv, "").unwrap();
+                log::trace!("Delete delta: {}", delta.to_json());
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::Replace(delta_i, iv, s) => {
             TestOp::Replace(delta_i, iv, s) => {
                 let document = &mut self.documents[*delta_i];
                 let document = &mut self.documents[*delta_i];
                 let delta = document.replace(*iv, s).unwrap();
                 let delta = document.replace(*iv, s).unwrap();
+                log::trace!("Replace delta: {}", delta.to_json());
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::InsertBold(delta_i, s, iv) => {
             TestOp::InsertBold(delta_i, s, iv) => {
@@ -126,6 +130,7 @@ impl TestBuilder {
                 let document = &mut self.documents[*delta_i];
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Bold(*enable);
                 let attribute = Attribute::Bold(*enable);
                 let delta = document.format(*iv, attribute).unwrap();
                 let delta = document.format(*iv, attribute).unwrap();
+                log::trace!("Bold delta: {}", delta.to_json());
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::Italic(delta_i, iv, enable) => {
             TestOp::Italic(delta_i, iv, enable) => {
@@ -135,24 +140,29 @@ impl TestBuilder {
                     false => Attribute::Italic(false),
                     false => Attribute::Italic(false),
                 };
                 };
                 let delta = document.format(*iv, attribute).unwrap();
                 let delta = document.format(*iv, attribute).unwrap();
+                log::trace!("Italic delta: {}", delta.to_json());
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::Header(delta_i, iv, level) => {
             TestOp::Header(delta_i, iv, level) => {
                 let document = &mut self.documents[*delta_i];
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Header(*level);
                 let attribute = Attribute::Header(*level);
                 let delta = document.format(*iv, attribute).unwrap();
                 let delta = document.format(*iv, attribute).unwrap();
+                log::trace!("Header delta: {}", delta.to_json());
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::Link(delta_i, iv, link) => {
             TestOp::Link(delta_i, iv, link) => {
                 let document = &mut self.documents[*delta_i];
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Link(link.to_owned());
                 let attribute = Attribute::Link(link.to_owned());
                 let delta = document.format(*iv, attribute).unwrap();
                 let delta = document.format(*iv, attribute).unwrap();
+                log::trace!("Link delta: {}", delta.to_json());
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::Bullet(delta_i, iv, enable) => {
             TestOp::Bullet(delta_i, iv, enable) => {
                 let document = &mut self.documents[*delta_i];
                 let document = &mut self.documents[*delta_i];
                 let attribute = Attribute::Bullet(*enable);
                 let attribute = Attribute::Bullet(*enable);
                 let delta = document.format(*iv, attribute).unwrap();
                 let delta = document.format(*iv, attribute).unwrap();
+                log::debug!("Bullet delta: {}", delta.to_json());
+
                 self.deltas.insert(*delta_i, Some(delta));
                 self.deltas.insert(*delta_i, Some(delta));
             },
             },
             TestOp::Transform(delta_a_i, delta_b_i) => {
             TestOp::Transform(delta_a_i, delta_b_i) => {

+ 15 - 2
rust-lib/flowy-document/tests/editor/serde_test.rs

@@ -82,8 +82,21 @@ fn delta_deserialize_null_test() {
     let json = r#"[
     let json = r#"[
         {"retain":7,"attributes":{"bold":null}}
         {"retain":7,"attributes":{"bold":null}}
      ]"#;
      ]"#;
-    let delta = Delta::from_json(json).unwrap();
-    println!("{}", delta);
+    let delta1 = Delta::from_json(json).unwrap();
+
+    let mut attribute = Attribute::Bold(true);
+    attribute.value = AttributeValue(None);
+    let delta2 = DeltaBuilder::new().retain_with_attributes(7, attribute.into()).build();
+
+    assert_eq!(delta2.to_json(), r#"[{"retain":7,"attributes":{"bold":""}}]"#);
+    assert_eq!(delta1, delta2);
+}
+
+#[test]
+fn delta_serde_null_test() {
+    let mut attribute = Attribute::Bold(true);
+    attribute.value = AttributeValue(None);
+    assert_eq!(attribute.to_json(), r#"{"bold":""}"#);
 }
 }
 
 
 #[test]
 #[test]

+ 13 - 1
rust-lib/flowy-ot/src/core/attributes/attribute.rs

@@ -3,8 +3,10 @@
 use crate::{block_attribute, core::Attributes, ignore_attribute, inline_attribute, list_attribute};
 use crate::{block_attribute, core::Attributes, ignore_attribute, inline_attribute, list_attribute};
 use lazy_static::lazy_static;
 use lazy_static::lazy_static;
 
 
+use serde_json::Error;
 use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
 use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
 use strum_macros::Display;
 use strum_macros::Display;
+
 #[derive(Debug, Clone)]
 #[derive(Debug, Clone)]
 pub struct Attribute {
 pub struct Attribute {
     pub key: AttributeKey,
     pub key: AttributeKey,
@@ -41,6 +43,16 @@ impl Attribute {
     list_attribute!(Ordered, "ordered");
     list_attribute!(Ordered, "ordered");
     list_attribute!(Checked, "checked");
     list_attribute!(Checked, "checked");
     list_attribute!(UnChecked, "unchecked");
     list_attribute!(UnChecked, "unchecked");
+
+    pub fn to_json(&self) -> String {
+        match serde_json::to_string(self) {
+            Ok(json) => json,
+            Err(e) => {
+                log::error!("Attribute serialize to str failed: {}", e);
+                "".to_owned()
+            },
+        }
+    }
 }
 }
 
 
 impl fmt::Display for Attribute {
 impl fmt::Display for Attribute {
@@ -100,7 +112,7 @@ pub enum AttributeKey {
 
 
 // pub trait AttributeValueData<'a>: Serialize + Deserialize<'a> {}
 // pub trait AttributeValueData<'a>: Serialize + Deserialize<'a> {}
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct AttributeValue(pub(crate) Option<String>);
+pub struct AttributeValue(pub Option<String>);
 
 
 impl std::convert::From<&usize> for AttributeValue {
 impl std::convert::From<&usize> for AttributeValue {
     fn from(val: &usize) -> Self { AttributeValue::from(*val) }
     fn from(val: &usize) -> Self { AttributeValue::from(*val) }

+ 56 - 32
rust-lib/flowy-ot/src/core/attributes/attributes_serde.rs

@@ -1,9 +1,10 @@
 #[rustfmt::skip]
 #[rustfmt::skip]
 use crate::core::AttributeValue;
 use crate::core::AttributeValue;
-use crate::core::{AttributeKey, Attributes};
+use crate::core::{Attribute, AttributeKey, Attributes};
 use serde::{
 use serde::{
     de,
     de,
     de::{MapAccess, Visitor},
     de::{MapAccess, Visitor},
+    ser,
     ser::SerializeMap,
     ser::SerializeMap,
     Deserialize,
     Deserialize,
     Deserializer,
     Deserializer,
@@ -12,6 +13,17 @@ use serde::{
 };
 };
 use std::fmt;
 use std::fmt;
 
 
+impl Serialize for Attribute {
+    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
+    where
+        S: Serializer,
+    {
+        let mut map = serializer.serialize_map(Some(1))?;
+        let _ = serial_attribute(&mut map, &self.key, &self.value)?;
+        map.end()
+    }
+}
+
 impl Serialize for Attributes {
 impl Serialize for Attributes {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
     where
@@ -23,42 +35,53 @@ impl Serialize for Attributes {
 
 
         let mut map = serializer.serialize_map(Some(self.inner.len()))?;
         let mut map = serializer.serialize_map(Some(self.inner.len()))?;
         for (k, v) in &self.inner {
         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::List => {
-                        map.serialize_entry(k, v)?;
-                    },
-                }
-            }
+            let _ = serial_attribute(&mut map, k, v)?;
         }
         }
         map.end()
         map.end()
     }
     }
 }
 }
 
 
+fn serial_attribute<S, E>(map_serializer: &mut S, key: &AttributeKey, value: &AttributeValue) -> Result<(), E>
+where
+    S: SerializeMap,
+    E: From<<S as SerializeMap>::Error>,
+{
+    if let Some(v) = &value.0 {
+        match key {
+            AttributeKey::Bold
+            | AttributeKey::Italic
+            | AttributeKey::Underline
+            | AttributeKey::StrikeThrough
+            | AttributeKey::CodeBlock
+            | AttributeKey::QuoteBlock => match &v.parse::<bool>() {
+                Ok(value) => map_serializer.serialize_entry(&key, value)?,
+                Err(e) => log::error!("Serial {:?} failed. {:?}", &key, e),
+            },
+
+            AttributeKey::Font
+            | AttributeKey::Size
+            | AttributeKey::Header
+            | AttributeKey::Indent
+            | AttributeKey::Width
+            | AttributeKey::Height => match &v.parse::<i32>() {
+                Ok(value) => map_serializer.serialize_entry(&key, value)?,
+                Err(e) => log::error!("Serial {:?} failed. {:?}", &key, e),
+            },
+
+            AttributeKey::Link
+            | AttributeKey::Color
+            | AttributeKey::Background
+            | AttributeKey::Align
+            | AttributeKey::List => {
+                map_serializer.serialize_entry(&key, v)?;
+            },
+        }
+    } else {
+        map_serializer.serialize_entry(&key, "")?;
+    }
+    Ok(())
+}
+
 impl<'de> Deserialize<'de> for Attributes {
 impl<'de> Deserialize<'de> for Attributes {
     fn deserialize<D>(deserializer: D) -> Result<Attributes, D::Error>
     fn deserialize<D>(deserializer: D) -> Result<Attributes, D::Error>
     where
     where
@@ -190,6 +213,7 @@ impl<'de> Deserialize<'de> for AttributeValue {
             where
             where
                 E: de::Error,
                 E: de::Error,
             {
             {
+                // the value that contains null will be processed here.
                 Ok(AttributeValue(None))
                 Ok(AttributeValue(None))
             }
             }