Jelajahi Sumber

chore: add more test and fix warnings

appflowy 3 tahun lalu
induk
melakukan
b3ed254caf
24 mengubah file dengan 318 tambahan dan 132 penghapusan
  1. 2 0
      frontend/rust-lib/flowy-error/src/errors.rs
  2. 1 1
      frontend/rust-lib/flowy-folder/tests/workspace/script.rs
  3. 1 5
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  4. 3 3
      frontend/rust-lib/flowy-grid/src/manager.rs
  5. 2 2
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  6. 4 7
      frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs
  7. 15 6
      frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs
  8. 39 10
      frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs
  9. 6 1
      frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs
  10. 18 9
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  11. 23 8
      frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs
  12. 157 11
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  13. 11 7
      frontend/rust-lib/flowy-grid/tests/grid/script.rs
  14. 1 1
      frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs
  15. 1 1
      shared-lib/flowy-collaboration/src/client_grid/block_pad.rs
  16. 3 11
      shared-lib/flowy-collaboration/src/client_grid/grid_builder.rs
  17. 7 11
      shared-lib/flowy-collaboration/src/client_grid/grid_meta_pad.rs
  18. 2 2
      shared-lib/flowy-collaboration/src/client_grid/mod.rs
  19. 4 0
      shared-lib/flowy-error-code/src/code.rs
  20. 10 3
      shared-lib/flowy-error-code/src/protobuf/model/code.rs
  21. 2 0
      shared-lib/flowy-error-code/src/protobuf/proto/code.proto
  22. 1 1
      shared-lib/flowy-folder-data-model/src/entities/view.rs
  23. 5 1
      shared-lib/flowy-grid-data-model/src/entities/meta.rs
  24. 0 31
      shared-lib/lib-infra/src/future.rs

+ 2 - 0
frontend/rust-lib/flowy-error/src/errors.rs

@@ -64,6 +64,8 @@ impl FlowyError {
     static_flowy_error!(name_empty, ErrorCode::UserNameIsEmpty);
     static_flowy_error!(user_id, ErrorCode::UserIdInvalid);
     static_flowy_error!(user_not_exist, ErrorCode::UserNotExist);
+    static_flowy_error!(text_too_long, ErrorCode::TextTooLong);
+    static_flowy_error!(invalid_data, ErrorCode::InvalidData);
 }
 
 impl std::convert::From<ErrorCode> for FlowyError {

+ 1 - 1
frontend/rust-lib/flowy-folder/tests/workspace/script.rs

@@ -351,7 +351,7 @@ pub async fn create_view(sdk: &FlowySDKTest, app_id: &str, name: &str, desc: &st
         thumbnail: None,
         data_type,
         plugin_type: 0,
-        data: "".to_string(),
+        data: vec![],
     };
     let view = FolderEventBuilder::new(sdk.clone())
         .event(CreateView)

+ 1 - 5
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -36,11 +36,7 @@ pub(crate) async fn get_fields_handler(
     let payload: QueryFieldPayload = data.into_inner();
     let editor = manager.get_grid_editor(&payload.grid_id)?;
     let field_metas = editor.get_field_metas(Some(payload.field_orders)).await?;
-    let repeated_field: RepeatedField = field_metas
-        .into_iter()
-        .map(|field_meta| Field::from(field_meta))
-        .collect::<Vec<_>>()
-        .into();
+    let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
     data_result(repeated_field)
 }
 

+ 3 - 3
frontend/rust-lib/flowy-grid/src/manager.rs

@@ -111,8 +111,7 @@ impl GridManager {
     ) -> Result<Arc<ClientGridEditor>, FlowyError> {
         let user = self.grid_user.clone();
         let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?;
-        let kv_persistence = self.get_kv_persistence()?;
-        let grid_editor = ClientGridEditor::new(grid_id, user, rev_manager, kv_persistence).await?;
+        let grid_editor = ClientGridEditor::new(grid_id, user, rev_manager).await?;
         Ok(grid_editor)
     }
 
@@ -137,6 +136,7 @@ impl GridManager {
         Ok(rev_manager)
     }
 
+    #[allow(dead_code)]
     fn get_kv_persistence(&self) -> FlowyResult<Arc<GridKVPersistence>> {
         let read_guard = self.kv_persistence.read();
         if read_guard.is_some() {
@@ -198,7 +198,7 @@ pub async fn make_grid_view_data(
     let grid_block_meta_delta = make_block_meta_delta(&build_context.grid_block_meta);
     let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes();
     let repeated_revision: RepeatedRevision =
-        Revision::initial_revision(&user_id, &block_id, block_meta_delta_data).into();
+        Revision::initial_revision(user_id, &block_id, block_meta_delta_data).into();
     let _ = grid_manager
         .create_grid_block_meta(&block_id, repeated_revision)
         .await?;

+ 2 - 2
frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs

@@ -139,7 +139,7 @@ impl GridBlockMetaEditorManager {
         for grid_block in grid_blocks {
             let editor = self.get_editor(&grid_block.id).await?;
             let row_metas = editor.get_row_metas(None).await?;
-            let block_row_orders = row_metas.iter().map(|row_meta| RowOrder::from(row_meta));
+            let block_row_orders = row_metas.iter().map(RowOrder::from);
             row_orders.extend(block_row_orders);
         }
         Ok(row_orders)
@@ -257,7 +257,7 @@ impl ClientGridBlockMetaEditor {
             .await
             .get_rows(None)?
             .iter()
-            .map(|row_meta| RowOrder::from(row_meta))
+            .map(RowOrder::from)
             .collect::<Vec<RowOrder>>();
         Ok(row_orders)
     }

+ 4 - 7
frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs

@@ -53,14 +53,11 @@ impl CellDataSerde for DateDescription {
     }
 
     fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
-        let timestamp = match data.parse::<i64>() {
-            Ok(timestamp) => timestamp,
-            Err(e) => {
-                tracing::error!("Parse {} to i64 failed: {}", data, e);
-                chrono::Utc::now().timestamp()
-            }
+        if let Err(e) = data.parse::<i64>() {
+            tracing::error!("Parse {} to i64 failed: {}", data, e);
+            return Err(FlowyError::internal().context(e));
         };
-        Ok(format!("{}", timestamp))
+        Ok(data.to_owned())
     }
 }
 

+ 15 - 6
frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs

@@ -93,7 +93,7 @@ impl NumberDescription {
     }
 
     fn decimal_from_str(&self, s: &str) -> Decimal {
-        let mut decimal = Decimal::from_str(s).unwrap_or(Decimal::zero());
+        let mut decimal = Decimal::from_str(s).unwrap_or_else(|_| Decimal::zero());
         match decimal.set_scale(self.scale) {
             Ok(_) => {}
             Err(e) => {
@@ -130,7 +130,12 @@ impl CellDataSerde for NumberDescription {
     }
 
     fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
-        Ok(self.strip_symbol(data))
+        let data = self.strip_symbol(data);
+
+        if !data.chars().all(char::is_numeric) {
+            return Err(FlowyError::invalid_data().context("Should only contain numbers"));
+        }
+        Ok(data)
     }
 }
 
@@ -188,8 +193,10 @@ mod tests {
 
     #[test]
     fn number_description_scale_test() {
-        let mut description = NumberDescription::default();
-        description.scale = 1;
+        let mut description = NumberDescription {
+            scale: 1,
+            ..Default::default()
+        };
 
         for format in NumberFormat::iter() {
             description.format = format;
@@ -224,8 +231,10 @@ mod tests {
 
     #[test]
     fn number_description_sign_test() {
-        let mut description = NumberDescription::default();
-        description.sign_positive = false;
+        let mut description = NumberDescription {
+            sign_positive: false,
+            ..Default::default()
+        };
 
         for format in NumberFormat::iter() {
             description.format = format;

+ 39 - 10
frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs

@@ -2,9 +2,13 @@ use crate::impl_from_and_to_type_option;
 use crate::services::row::CellDataSerde;
 use crate::services::util::*;
 use flowy_derive::ProtoBuf;
-use flowy_error::FlowyError;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
 use serde::{Deserialize, Serialize};
+use uuid::Uuid;
+
+pub const SELECTION_IDS_SEPARATOR: &str = ",";
 
 // Single select
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
@@ -23,7 +27,7 @@ impl CellDataSerde for SingleSelectDescription {
     }
 
     fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
-        Ok(select_option_id_from_data(data.to_owned(), true))
+        single_select_option_id_from_data(data.to_owned())
     }
 }
 
@@ -43,20 +47,45 @@ impl CellDataSerde for MultiSelectDescription {
     }
 
     fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
-        Ok(select_option_id_from_data(data.to_owned(), false))
+        multi_select_option_id_from_data(data.to_owned())
     }
 }
 
-fn select_option_id_from_data(data: String, is_single_select: bool) -> String {
-    if !is_single_select {
-        return data;
-    }
-    let select_option_ids = data.split(',').collect::<Vec<&str>>();
+fn single_select_option_id_from_data(data: String) -> FlowyResult<String> {
+    let select_option_ids = select_option_ids(data)?;
     if select_option_ids.is_empty() {
-        return "".to_owned();
+        return Ok("".to_owned());
     }
 
-    select_option_ids.split_first().unwrap().0.to_string()
+    Ok(select_option_ids.split_first().unwrap().0.to_string())
+}
+
+fn multi_select_option_id_from_data(data: String) -> FlowyResult<String> {
+    let select_option_ids = select_option_ids(data)?;
+    Ok(select_option_ids.join(SELECTION_IDS_SEPARATOR))
+}
+
+fn select_option_ids(mut data: String) -> FlowyResult<Vec<String>> {
+    data.retain(|c| !c.is_whitespace());
+    let select_option_ids = data.split(SELECTION_IDS_SEPARATOR).collect::<Vec<&str>>();
+    if select_option_ids
+        .par_iter()
+        .find_first(|option_id| match Uuid::parse_str(option_id) {
+            Ok(_) => false,
+            Err(e) => {
+                tracing::error!("{}", e);
+                true
+            }
+        })
+        .is_some()
+    {
+        let msg = format!(
+            "Invalid selection id string: {}. It should consist of the uuid string and separated by comma",
+            data
+        );
+        return Err(FlowyError::internal().context(msg));
+    }
+    Ok(select_option_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>())
 }
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]

+ 6 - 1
frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs

@@ -19,6 +19,11 @@ impl CellDataSerde for RichTextDescription {
     }
 
     fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
-        Ok(data.to_owned())
+        let data = data.to_owned();
+        if data.len() > 10000 {
+            Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
+        } else {
+            Ok(data)
+        }
     }
 }

+ 18 - 9
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -1,19 +1,19 @@
 use crate::manager::GridUser;
 use crate::services::block_meta_editor::GridBlockMetaEditorManager;
-use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
 use bytes::Bytes;
 use flowy_collaboration::client_grid::{GridChange, GridMetaPad};
 use flowy_collaboration::entities::revision::Revision;
 use flowy_collaboration::util::make_delta_from_revisions;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
-    CellMetaChangeset, Field, FieldChangeset, FieldMeta, Grid, GridBlock, GridBlockChangeset, RepeatedFieldOrder,
+    CellMetaChangeset, FieldChangeset, FieldMeta, Grid, GridBlock, GridBlockChangeset, RepeatedFieldOrder,
     RepeatedRowOrder, Row, RowMeta, RowMetaChangeset,
 };
 use std::collections::HashMap;
 
 use crate::services::row::{
-    make_row_by_row_id, make_rows, row_meta_from_context, CreateRowContext, CreateRowContextBuilder,
+    make_row_by_row_id, make_rows, row_meta_from_context, serialize_cell_data, CreateRowContext,
+    CreateRowContextBuilder,
 };
 use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
 use lib_infra::future::FutureResult;
@@ -27,7 +27,6 @@ pub struct ClientGridEditor {
     grid_meta_pad: Arc<RwLock<GridMetaPad>>,
     rev_manager: Arc<RevisionManager>,
     block_meta_manager: Arc<GridBlockMetaEditorManager>,
-    kv_persistence: Arc<GridKVPersistence>,
 }
 
 impl ClientGridEditor {
@@ -35,7 +34,6 @@ impl ClientGridEditor {
         grid_id: &str,
         user: Arc<dyn GridUser>,
         mut rev_manager: RevisionManager,
-        kv_persistence: Arc<GridKVPersistence>,
     ) -> FlowyResult<Arc<Self>> {
         let token = user.token()?;
         let cloud = Arc::new(GridRevisionCloudService { token });
@@ -52,7 +50,6 @@ impl ClientGridEditor {
             grid_meta_pad,
             rev_manager,
             block_meta_manager,
-            kv_persistence,
         }))
     }
 
@@ -88,7 +85,8 @@ impl ClientGridEditor {
     pub async fn create_row(&self) -> FlowyResult<()> {
         let field_metas = self.grid_meta_pad.read().await.get_field_metas(None)?;
         let block_id = self.last_block_id().await?;
-        let row = row_meta_from_context(&block_id, CreateRowContextBuilder::new(&field_metas).build());
+        let create_row_ctx = CreateRowContextBuilder::new(&field_metas).build();
+        let row = row_meta_from_context(&block_id, create_row_ctx);
         let row_count = self.block_meta_manager.create_row(row).await?;
         let changeset = GridBlockChangeset::from_row_count(&block_id, row_count);
         let _ = self.update_block(changeset).await?;
@@ -98,12 +96,11 @@ impl ClientGridEditor {
     pub async fn insert_rows(&self, contexts: Vec<CreateRowContext>) -> FlowyResult<()> {
         let block_id = self.last_block_id().await?;
         let mut rows_by_block_id: HashMap<String, Vec<RowMeta>> = HashMap::new();
-
         for ctx in contexts {
             let row_meta = row_meta_from_context(&block_id, ctx);
             rows_by_block_id
                 .entry(block_id.clone())
-                .or_insert(Vec::new())
+                .or_insert_with(Vec::new)
                 .push(row_meta);
         }
         let changesets = self.block_meta_manager.insert_row(rows_by_block_id).await?;
@@ -118,6 +115,18 @@ impl ClientGridEditor {
     }
 
     pub async fn update_cell(&self, changeset: CellMetaChangeset) -> FlowyResult<()> {
+        if let Some(cell_data) = changeset.data.as_ref() {
+            match self.grid_meta_pad.read().await.get_field(&changeset.field_id) {
+                None => {
+                    return Err(FlowyError::internal()
+                        .context(format!("Can not find the field with id: {}", &changeset.field_id)));
+                }
+                Some(field_meta) => {
+                    let _ = serialize_cell_data(cell_data, field_meta)?;
+                }
+            }
+        }
+
         let row_changeset: RowMetaChangeset = changeset.into();
         self.update_row(row_changeset).await
     }

+ 23 - 8
frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs

@@ -1,28 +1,43 @@
+use crate::services::row::serialize_cell_data;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{CellMeta, FieldMeta, RowMeta, DEFAULT_ROW_HEIGHT};
 use std::collections::HashMap;
 
 pub struct CreateRowContextBuilder<'a> {
-    #[allow(dead_code)]
-    fields: &'a [FieldMeta],
+    field_meta_map: HashMap<&'a String, &'a FieldMeta>,
     ctx: CreateRowContext,
 }
 
 impl<'a> CreateRowContextBuilder<'a> {
     pub fn new(fields: &'a [FieldMeta]) -> Self {
+        let field_meta_map = fields
+            .iter()
+            .map(|field| (&field.id, field))
+            .collect::<HashMap<&String, &FieldMeta>>();
+
         let ctx = CreateRowContext {
             row_id: uuid::Uuid::new_v4().to_string(),
             cell_by_field_id: Default::default(),
             height: DEFAULT_ROW_HEIGHT,
             visibility: true,
         };
-        Self { fields, ctx }
+
+        Self { field_meta_map, ctx }
     }
 
-    #[allow(dead_code)]
-    pub fn add_cell(mut self, field_id: &str, data: String) -> Self {
-        let cell = CellMeta::new(field_id, data);
-        self.ctx.cell_by_field_id.insert(field_id.to_owned(), cell);
-        self
+    pub fn add_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
+        match self.field_meta_map.get(&field_id.to_owned()) {
+            None => {
+                let msg = format!("Invalid field_id: {}", field_id);
+                Err(FlowyError::internal().context(msg))
+            }
+            Some(field_meta) => {
+                let data = serialize_cell_data(&data, field_meta)?;
+                let cell = CellMeta::new(field_id, data);
+                self.ctx.cell_by_field_id.insert(field_id.to_owned(), cell);
+                Ok(())
+            }
+        }
     }
 
     #[allow(dead_code)]

+ 157 - 11
frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs

@@ -1,8 +1,11 @@
 use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
+use chrono::NaiveDateTime;
 use flowy_grid::services::cell::*;
 use flowy_grid::services::row::{deserialize_cell_data, serialize_cell_data, CellDataSerde, CreateRowContextBuilder};
-use flowy_grid_data_model::entities::{FieldChangeset, FieldType, GridBlock, GridBlockChangeset, RowMetaChangeset};
+use flowy_grid_data_model::entities::{
+    CellMetaChangeset, FieldChangeset, FieldType, GridBlock, GridBlockChangeset, RowMetaChangeset,
+};
 
 #[tokio::test]
 async fn grid_create_field() {
@@ -237,29 +240,28 @@ async fn grid_delete_row() {
 }
 
 #[tokio::test]
-async fn grid_update_cell() {
+async fn grid_row_add_cells_test() {
     let mut test = GridEditorTest::new().await;
     let mut builder = CreateRowContextBuilder::new(&test.field_metas);
     for field in &test.field_metas {
         match field.field_type {
             FieldType::RichText => {
                 let data = serialize_cell_data("hello world", field).unwrap();
-                builder = builder.add_cell(&field.id, data);
+                builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::Number => {
                 let data = serialize_cell_data("¥18,443", field).unwrap();
-                builder = builder.add_cell(&field.id, data);
+                builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::DateTime => {
                 let data = serialize_cell_data("1647251762", field).unwrap();
-                builder = builder.add_cell(&field.id, data);
+                builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::SingleSelect => {
                 let description = SingleSelectDescription::from(field);
                 let options = description.options.first().unwrap();
-
                 let data = description.serialize_cell_data(&options.id).unwrap();
-                builder = builder.add_cell(&field.id, data);
+                builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::MultiSelect => {
                 let description = MultiSelectDescription::from(field);
@@ -268,17 +270,161 @@ async fn grid_update_cell() {
                     .iter()
                     .map(|option| option.id.clone())
                     .collect::<Vec<_>>()
-                    .join(",");
+                    .join(SELECTION_IDS_SEPARATOR);
                 let data = description.serialize_cell_data(&options).unwrap();
-                builder = builder.add_cell(&field.id, data);
+                builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::Checkbox => {
                 let data = serialize_cell_data("false", field).unwrap();
-                builder = builder.add_cell(&field.id, data);
+                builder.add_cell(&field.id, data).unwrap();
+            }
+        }
+    }
+    let context = builder.build();
+    let scripts = vec![CreateRow { context }, AssertGridMetaPad];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_row_add_selection_cell_test() {
+    let mut test = GridEditorTest::new().await;
+    let mut builder = CreateRowContextBuilder::new(&test.field_metas);
+    let uuid = uuid::Uuid::new_v4().to_string();
+    let mut single_select_field_id = "".to_string();
+    let mut multi_select_field_id = "".to_string();
+    for field in &test.field_metas {
+        match field.field_type {
+            FieldType::SingleSelect => {
+                single_select_field_id = field.id.clone();
+                // The element must be parsed as uuid
+                assert!(builder.add_cell(&field.id, "data".to_owned()).is_err());
+                // // The data should not be empty
+                assert!(builder.add_cell(&field.id, "".to_owned()).is_err());
+                // The element must be parsed as uuid
+                assert!(builder.add_cell(&field.id, "1,2,3".to_owned()).is_err(),);
+                // The separator must be comma
+                assert!(builder.add_cell(&field.id, format!("{}. {}", &uuid, &uuid),).is_err());
+                //
+
+                assert!(builder.add_cell(&field.id, uuid.clone()).is_ok());
+                assert!(builder.add_cell(&field.id, format!("{},   {}", &uuid, &uuid)).is_ok());
+            }
+            FieldType::MultiSelect => {
+                multi_select_field_id = field.id.clone();
+                assert!(builder.add_cell(&field.id, format!("{},   {}", &uuid, &uuid)).is_ok());
             }
+            _ => {}
         }
     }
     let context = builder.build();
-    let scripts = vec![AssertRowCount(3), CreateRow { context }, AssertGridMetaPad];
+    assert_eq!(
+        &context
+            .cell_by_field_id
+            .get(&single_select_field_id)
+            .as_ref()
+            .unwrap()
+            .data,
+        &uuid
+    );
+    assert_eq!(
+        context
+            .cell_by_field_id
+            .get(&multi_select_field_id)
+            .as_ref()
+            .unwrap()
+            .data,
+        format!("{},{}", &uuid, &uuid)
+    );
+
+    let scripts = vec![CreateRow { context }];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_row_add_date_cell_test() {
+    let mut test = GridEditorTest::new().await;
+    let mut builder = CreateRowContextBuilder::new(&test.field_metas);
+    let mut date_field = None;
+    let timestamp = 1647390674;
+    for field in &test.field_metas {
+        if field.field_type == FieldType::DateTime {
+            date_field = Some(field.clone());
+            NaiveDateTime::from_timestamp(123, 0);
+            // The data should not be empty
+            assert!(builder.add_cell(&field.id, "".to_owned()).is_err());
+
+            assert!(builder.add_cell(&field.id, "123".to_owned()).is_ok());
+            assert!(builder.add_cell(&field.id, format!("{}", timestamp)).is_ok());
+        }
+    }
+    let context = builder.build();
+    let date_field = date_field.unwrap();
+    let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
+    assert_eq!(
+        deserialize_cell_data(cell_data.data.clone(), &date_field).unwrap(),
+        "2022/03/16 08:31",
+    );
+    let scripts = vec![CreateRow { context }];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_cell_update() {
+    let mut test = GridEditorTest::new().await;
+    let field_metas = &test.field_metas;
+    let row_metas = &test.row_metas;
+    assert_eq!(row_metas.len(), 3);
+
+    let mut scripts = vec![];
+    for (index, row_meta) in row_metas.iter().enumerate() {
+        for field_meta in field_metas {
+            if index == 0 {
+                let data = match field_meta.field_type {
+                    FieldType::RichText => "".to_string(),
+                    FieldType::Number => "123".to_string(),
+                    FieldType::DateTime => "123".to_string(),
+                    FieldType::SingleSelect => {
+                        let description = SingleSelectDescription::from(field_meta);
+                        description.options.first().unwrap().id.clone()
+                    }
+                    FieldType::MultiSelect => {
+                        let description = MultiSelectDescription::from(field_meta);
+                        description.options.first().unwrap().id.clone()
+                    }
+                    FieldType::Checkbox => "1".to_string(),
+                };
+
+                scripts.push(UpdateCell {
+                    changeset: CellMetaChangeset {
+                        row_id: row_meta.id.clone(),
+                        field_id: field_meta.id.clone(),
+                        data: Some(data),
+                    },
+                    is_err: false,
+                });
+            }
+
+            if index == 1 {
+                let (data, is_err) = match field_meta.field_type {
+                    FieldType::RichText => ("1".to_string().repeat(10001), true),
+                    FieldType::Number => ("abc".to_string(), true),
+                    FieldType::DateTime => ("abc".to_string(), true),
+                    FieldType::SingleSelect => ("abc".to_string(), true),
+                    FieldType::MultiSelect => ("abc".to_string(), true),
+                    FieldType::Checkbox => ("2".to_string(), false),
+                };
+
+                scripts.push(UpdateCell {
+                    changeset: CellMetaChangeset {
+                        row_id: row_meta.id.clone(),
+                        field_id: field_meta.id.clone(),
+                        data: Some(data),
+                    },
+                    is_err,
+                });
+            }
+        }
+    }
+
     test.run_scripts(scripts).await;
 }

+ 11 - 7
frontend/rust-lib/flowy-grid/tests/grid/script.rs

@@ -1,8 +1,6 @@
 use bytes::Bytes;
 use flowy_collaboration::client_grid::GridBuilder;
-use flowy_collaboration::entities::revision::{RepeatedRevision, Revision};
-use flowy_error::FlowyResult;
-use flowy_grid::manager::{make_grid_view_data, GridManager};
+
 use flowy_grid::services::cell::*;
 use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
@@ -16,7 +14,7 @@ use flowy_test::helper::ViewTest;
 use flowy_test::FlowySDKTest;
 use std::sync::Arc;
 use std::time::Duration;
-use strum::{EnumCount, IntoEnumIterator};
+use strum::EnumCount;
 use tokio::time::sleep;
 
 pub enum EditorScript {
@@ -65,6 +63,7 @@ pub enum EditorScript {
     },
     UpdateCell {
         changeset: CellMetaChangeset,
+        is_err: bool,
     },
     AssertRowCount(usize),
     // AssertRowEqual{ row_index: usize, row: RowMeta},
@@ -199,9 +198,14 @@ impl GridEditorTest {
                     assert_eq!(row.height, height);
                 }
             }
-            EditorScript::UpdateCell { changeset } => {
-                self.editor.update_cell(changeset).await.unwrap();
-                self.row_metas = self.editor.get_row_metas(None).await.unwrap();
+            EditorScript::UpdateCell { changeset, is_err } => {
+                let result = self.editor.update_cell(changeset).await;
+                if is_err {
+                    assert!(result.is_err())
+                } else {
+                    let _ = result.unwrap();
+                    self.row_metas = self.editor.get_row_metas(None).await.unwrap();
+                }
             }
             EditorScript::AssertRowCount(count) => {
                 assert_eq!(self.editor.get_rows(None).await.unwrap().len(), count);

+ 1 - 1
frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs

@@ -4,7 +4,7 @@ use flowy_collaboration::client_document::default::initial_quill_delta_string;
 use flowy_collaboration::entities::revision::{RepeatedRevision, Revision};
 use flowy_collaboration::entities::ws_data::ClientRevisionWSData;
 use flowy_database::ConnectionPool;
-use flowy_folder::errors::FlowyResult;
+
 use flowy_folder::manager::{ViewDataProcessor, ViewDataProcessorMap};
 use flowy_folder::prelude::ViewDataType;
 use flowy_folder::{

+ 1 - 1
shared-lib/flowy-collaboration/src/client_grid/block_pad.rs

@@ -52,7 +52,7 @@ impl GridBlockMetaPad {
 
     pub fn get_rows(&self, row_ids: Option<Vec<String>>) -> CollaborateResult<Vec<Arc<RowMeta>>> {
         match row_ids {
-            None => Ok(self.rows.iter().map(|row| row.clone()).collect::<Vec<_>>()),
+            None => Ok(self.rows.to_vec()),
             Some(row_ids) => {
                 let row_map = self
                     .rows

+ 3 - 11
shared-lib/flowy-collaboration/src/client_grid/grid_builder.rs

@@ -1,19 +1,11 @@
-use crate::client_grid::{make_block_meta_delta, make_grid_delta, GridBlockMetaDelta, GridMetaDelta};
 use crate::errors::{CollaborateError, CollaborateResult};
-use flowy_grid_data_model::entities::{BuildGridContext, FieldMeta, GridBlock, GridBlockMeta, GridMeta, RowMeta};
+use flowy_grid_data_model::entities::{BuildGridContext, FieldMeta, RowMeta};
 
+#[derive(Default)]
 pub struct GridBuilder {
     build_context: BuildGridContext,
 }
 
-impl std::default::Default for GridBuilder {
-    fn default() -> Self {
-        Self {
-            build_context: Default::default(),
-        }
-    }
-}
-
 impl GridBuilder {
     pub fn add_field(mut self, field: FieldMeta) -> Self {
         self.build_context.field_metas.push(field);
@@ -63,7 +55,7 @@ mod tests {
             .build();
 
         let grid_meta = GridMeta {
-            grid_id: grid_id.clone(),
+            grid_id,
             fields: build_context.field_metas,
             blocks: vec![build_context.grid_block],
         };

+ 7 - 11
shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs → shared-lib/flowy-collaboration/src/client_grid/grid_meta_pad.rs

@@ -58,19 +58,15 @@ impl GridMetaPad {
     }
 
     pub fn contain_field(&self, field_id: &str) -> bool {
-        self.grid_meta
-            .fields
-            .iter()
-            .find(|field| &field.id == field_id)
-            .is_some()
+        self.grid_meta.fields.iter().any(|field| field.id == field_id)
+    }
+
+    pub fn get_field(&self, field_id: &str) -> Option<&FieldMeta> {
+        self.grid_meta.fields.iter().find(|field| field.id == field_id)
     }
 
     pub fn get_field_orders(&self) -> Vec<FieldOrder> {
-        self.grid_meta
-            .fields
-            .iter()
-            .map(|field_meta| FieldOrder::from(field_meta))
-            .collect()
+        self.grid_meta.fields.iter().map(FieldOrder::from).collect()
     }
 
     pub fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> CollaborateResult<Vec<FieldMeta>> {
@@ -154,7 +150,7 @@ impl GridMetaPad {
                         if last_block.start_row_index > block.start_row_index
                             && last_block.len() > block.start_row_index
                         {
-                            let msg = format!("GridBlock's start_row_index should be greater than the last_block's start_row_index and its len");
+                            let msg = "GridBlock's start_row_index should be greater than the last_block's start_row_index and its len".to_string();
                             return Err(CollaborateError::internal().context(msg))
                         }
                         grid.blocks.push(block);

+ 2 - 2
shared-lib/flowy-collaboration/src/client_grid/mod.rs

@@ -1,7 +1,7 @@
 mod block_pad;
 mod grid_builder;
-mod grid_pad;
+mod grid_meta_pad;
 
 pub use block_pad::*;
 pub use grid_builder::*;
-pub use grid_pad::*;
+pub use grid_meta_pad::*;

+ 4 - 0
shared-lib/flowy-error-code/src/code.rs

@@ -83,6 +83,10 @@ pub enum ErrorCode {
     UserIdInvalid = 311,
     #[display(fmt = "User not exist")]
     UserNotExist = 312,
+    #[display(fmt = "Text is too long")]
+    TextTooLong = 400,
+    #[display(fmt = "Invalid data")]
+    InvalidData = 401,
 }
 
 impl ErrorCode {

+ 10 - 3
shared-lib/flowy-error-code/src/protobuf/model/code.rs

@@ -55,6 +55,8 @@ pub enum ErrorCode {
     UserNameIsEmpty = 310,
     UserIdInvalid = 311,
     UserNotExist = 312,
+    TextTooLong = 400,
+    InvalidData = 401,
 }
 
 impl ::protobuf::ProtobufEnum for ErrorCode {
@@ -94,6 +96,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             310 => ::std::option::Option::Some(ErrorCode::UserNameIsEmpty),
             311 => ::std::option::Option::Some(ErrorCode::UserIdInvalid),
             312 => ::std::option::Option::Some(ErrorCode::UserNotExist),
+            400 => ::std::option::Option::Some(ErrorCode::TextTooLong),
+            401 => ::std::option::Option::Some(ErrorCode::InvalidData),
             _ => ::std::option::Option::None
         }
     }
@@ -130,6 +134,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
             ErrorCode::UserNameIsEmpty,
             ErrorCode::UserIdInvalid,
             ErrorCode::UserNotExist,
+            ErrorCode::TextTooLong,
+            ErrorCode::InvalidData,
         ];
         values
     }
@@ -158,7 +164,7 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\ncode.proto*\xc4\x05\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
+    \n\ncode.proto*\xe8\x05\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
     \n\x10UserUnauthorized\x10\x02\x12\x12\n\x0eRecordNotFound\x10\x03\x12\
     \x18\n\x14WorkspaceNameInvalid\x10d\x12\x16\n\x12WorkspaceIdInvalid\x10e\
     \x12\x18\n\x14AppColorStyleInvalid\x10f\x12\x18\n\x14WorkspaceDescTooLon\
@@ -174,8 +180,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     rmatInvalid\x10\xb2\x02\x12\x15\n\x10PasswordNotMatch\x10\xb3\x02\x12\
     \x14\n\x0fUserNameTooLong\x10\xb4\x02\x12'\n\"UserNameContainForbiddenCh\
     aracters\x10\xb5\x02\x12\x14\n\x0fUserNameIsEmpty\x10\xb6\x02\x12\x12\n\
-    \rUserIdInvalid\x10\xb7\x02\x12\x11\n\x0cUserNotExist\x10\xb8\x02b\x06pr\
-    oto3\
+    \rUserIdInvalid\x10\xb7\x02\x12\x11\n\x0cUserNotExist\x10\xb8\x02\x12\
+    \x10\n\x0bTextTooLong\x10\x90\x03\x12\x10\n\x0bInvalidData\x10\x91\x03b\
+    \x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 0
shared-lib/flowy-error-code/src/protobuf/proto/code.proto

@@ -31,4 +31,6 @@ enum ErrorCode {
     UserNameIsEmpty = 310;
     UserIdInvalid = 311;
     UserNotExist = 312;
+    TextTooLong = 400;
+    InvalidData = 401;
 }

+ 1 - 1
shared-lib/flowy-folder-data-model/src/entities/view.rs

@@ -4,7 +4,7 @@ use crate::{
     impl_def_and_def_mut,
     parser::{
         app::AppIdentify,
-        view::{ViewDesc, ViewExtensionData, ViewIdentify, ViewName, ViewThumbnail},
+        view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail},
     },
 };
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};

+ 5 - 1
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -1,7 +1,7 @@
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
-use strum::{EnumCount, IntoEnumIterator};
+
 use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString};
 
 pub const DEFAULT_ROW_HEIGHT: i32 = 36;
@@ -35,6 +35,10 @@ impl GridBlock {
     pub fn len(&self) -> i32 {
         self.start_row_index + self.row_count
     }
+
+    pub fn is_empty(&self) -> bool {
+        self.row_count == 0
+    }
 }
 
 impl GridBlock {

+ 0 - 31
shared-lib/lib-infra/src/future.rs

@@ -65,34 +65,3 @@ where
 }
 
 pub type BoxResultFuture<'a, T, E> = BoxFuture<'a, Result<T, E>>;
-
-#[pin_project]
-pub struct FutureResultSend<T, E> {
-    #[pin]
-    pub fut: Pin<Box<dyn Future<Output = Result<T, E>> + Send>>,
-}
-
-impl<T, E> FutureResultSend<T, E> {
-    pub fn new<F>(f: F) -> Self
-    where
-        F: Future<Output = Result<T, E>> + Send + 'static,
-    {
-        Self {
-            fut: Box::pin(async { f.await }),
-        }
-    }
-}
-
-impl<T, E> Future for FutureResultSend<T, E>
-where
-    T: Send,
-    E: Debug,
-{
-    type Output = Result<T, E>;
-
-    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
-        let this = self.as_mut().project();
-        let result = ready!(this.fut.poll(cx));
-        Poll::Ready(result)
-    }
-}