diff --git a/shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs b/shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs index f7d48b93fd..e253ad56f5 100644 --- a/shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs +++ b/shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs @@ -1,9 +1,76 @@ use crate::revision::{TrashRevision, WorkspaceRevision}; -use serde::{Deserialize, Serialize}; +use serde::de::{MapAccess, Visitor}; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::fmt; use std::sync::Arc; -#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] +#[derive(Debug, Default, Serialize, Clone, Eq, PartialEq)] pub struct FolderRevision { pub workspaces: Vec>, pub trash: Vec>, } + +impl<'de> Deserialize<'de> for FolderRevision { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct FolderVisitor<'a>(&'a mut Option); + impl<'de, 'a> Visitor<'de> for FolderVisitor<'a> { + type Value = (); + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expect struct FolderRevision") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let f = |map: &mut A, + workspaces: &mut Option>, + trash: &mut Option>| match map.next_key::() + { + Ok(Some(key)) => { + if key == "workspaces" && workspaces.is_none() { + *workspaces = Some(map.next_value::>().ok()?); + } + if key == "trash" && trash.is_none() { + *trash = Some(map.next_value::>().ok()?); + } + Some(()) + } + Ok(None) => None, + Err(_e) => None, + }; + + let mut workspaces: Option> = None; + let mut trash: Option> = None; + while f(&mut map, &mut workspaces, &mut trash).is_some() { + if workspaces.is_some() && trash.is_some() { + break; + } + } + + *self.0 = Some(FolderRevision { + workspaces: workspaces.unwrap_or_default().into_iter().map(Arc::new).collect(), + trash: trash.unwrap_or_default().into_iter().map(Arc::new).collect(), + }); + Ok(()) + } + } + + let mut folder_rev: Option = None; + const FIELDS: &[&str] = &["workspaces", "trash"]; + let _ = serde::Deserializer::deserialize_struct( + deserializer, + "FolderRevision", + FIELDS, + FolderVisitor(&mut folder_rev), + ); + + match folder_rev { + None => Err(de::Error::missing_field("workspaces or trash")), + Some(folder_rev) => Ok(folder_rev), + } + } +} diff --git a/shared-lib/flowy-sync/src/client_folder/folder_pad.rs b/shared-lib/flowy-sync/src/client_folder/folder_pad.rs index 69ed94a69b..93bed69641 100644 --- a/shared-lib/flowy-sync/src/client_folder/folder_pad.rs +++ b/shared-lib/flowy-sync/src/client_folder/folder_pad.rs @@ -12,6 +12,7 @@ use flowy_folder_data_model::revision::{AppRevision, FolderRevision, TrashRevisi use lib_infra::util::move_vec_element; use lib_ot::core::*; +use serde::Deserialize; use std::sync::Arc; #[derive(Debug, Clone, Eq, PartialEq)] @@ -44,7 +45,9 @@ impl FolderPad { pub fn from_delta(delta: FolderDelta) -> CollaborateResult { // TODO: Reconvert from history if delta.to_str() failed. let content = delta.content()?; - let folder_rev: FolderRevision = serde_json::from_str(&content).map_err(|e| { + let mut deserializer = serde_json::Deserializer::from_reader(content.as_bytes()); + + let folder_rev = FolderRevision::deserialize(&mut deserializer).map_err(|e| { tracing::error!("Deserialize folder from {} failed", content); return CollaborateError::internal().context(format!("Deserialize delta to folder failed: {}", e)); })?; @@ -455,6 +458,7 @@ mod tests { #![allow(clippy::all)] use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta}; use chrono::Utc; + use serde::Deserialize; use flowy_folder_data_model::revision::{ AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision,