mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
[rust]: fix delta serial bugs & add some tests
This commit is contained in:
parent
63d2ca27d3
commit
ce2ccf7d0a
11 changed files with 152 additions and 49 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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();
|
let delta1 = Delta::from_json(json).unwrap();
|
||||||
println!("{}", delta);
|
|
||||||
|
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]
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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 {
|
let _ = serial_attribute(&mut map, k, v)?;
|
||||||
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)?;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue