diff --git a/frontend/rust-lib/flowy-error/src/errors.rs b/frontend/rust-lib/flowy-error/src/errors.rs index 956df16fa5..cd890fd02e 100644 --- a/frontend/rust-lib/flowy-error/src/errors.rs +++ b/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 for FlowyError { diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs index 3cb65a414c..e476c36f2d 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ b/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) diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 5028a40407..28e25854d0 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/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::>() - .into(); + let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::>().into(); data_result(repeated_field) } diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index 28a246bd75..fde6d136f2 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -111,8 +111,7 @@ impl GridManager { ) -> Result, 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> { 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?; diff --git a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs index fb72f8c34d..f0d0b8e475 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs +++ b/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::>(); Ok(row_orders) } diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs b/frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs index 1b057b5314..30af0fb160 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/description/date_description.rs +++ b/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 { - let timestamp = match data.parse::() { - Ok(timestamp) => timestamp, - Err(e) => { - tracing::error!("Parse {} to i64 failed: {}", data, e); - chrono::Utc::now().timestamp() - } + if let Err(e) = data.parse::() { + tracing::error!("Parse {} to i64 failed: {}", data, e); + return Err(FlowyError::internal().context(e)); }; - Ok(format!("{}", timestamp)) + Ok(data.to_owned()) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs b/frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs index a4a3c08d90..c3468786df 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/description/number_description.rs +++ b/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 { - 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; diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs b/frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs index 2f772c3c5d..0115e390dd 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/description/selection_description.rs +++ b/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 { - 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 { - 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::>(); +fn single_select_option_id_from_data(data: String) -> FlowyResult { + 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 { + let select_option_ids = select_option_ids(data)?; + Ok(select_option_ids.join(SELECTION_IDS_SEPARATOR)) +} + +fn select_option_ids(mut data: String) -> FlowyResult> { + data.retain(|c| !c.is_whitespace()); + let select_option_ids = data.split(SELECTION_IDS_SEPARATOR).collect::>(); + 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::>()) } #[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)] diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs b/frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs index 1f71244519..539b82402c 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/description/text_description.rs +++ b/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 { - 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) + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 86c7a8fc0e..9436b62e6f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/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>, rev_manager: Arc, block_meta_manager: Arc, - kv_persistence: Arc, } impl ClientGridEditor { @@ -35,7 +34,6 @@ impl ClientGridEditor { grid_id: &str, user: Arc, mut rev_manager: RevisionManager, - kv_persistence: Arc, ) -> FlowyResult> { 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) -> FlowyResult<()> { let block_id = self.last_block_id().await?; let mut rows_by_block_id: HashMap> = 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 } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs index 6bc231293a..676411e8c7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs +++ b/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::>(); + 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)] diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index 30469d406f..acb8e27c39 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/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::>() - .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![AssertRowCount(3), CreateRow { context }, AssertGridMetaPad]; + 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(); + 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; } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index c49bf82a03..2c784153be 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/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); diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs index 89346eaf35..6ed3df3207 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs +++ b/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::{ diff --git a/shared-lib/flowy-collaboration/src/client_grid/block_pad.rs b/shared-lib/flowy-collaboration/src/client_grid/block_pad.rs index fafa2d45ce..840e4c28af 100644 --- a/shared-lib/flowy-collaboration/src/client_grid/block_pad.rs +++ b/shared-lib/flowy-collaboration/src/client_grid/block_pad.rs @@ -52,7 +52,7 @@ impl GridBlockMetaPad { pub fn get_rows(&self, row_ids: Option>) -> CollaborateResult>> { match row_ids { - None => Ok(self.rows.iter().map(|row| row.clone()).collect::>()), + None => Ok(self.rows.to_vec()), Some(row_ids) => { let row_map = self .rows diff --git a/shared-lib/flowy-collaboration/src/client_grid/grid_builder.rs b/shared-lib/flowy-collaboration/src/client_grid/grid_builder.rs index 42faa4abda..a6be909ef6 100644 --- a/shared-lib/flowy-collaboration/src/client_grid/grid_builder.rs +++ b/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], }; diff --git a/shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs b/shared-lib/flowy-collaboration/src/client_grid/grid_meta_pad.rs similarity index 95% rename from shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs rename to shared-lib/flowy-collaboration/src/client_grid/grid_meta_pad.rs index bcae5144a8..e836034fae 100644 --- a/shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs +++ b/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 { - 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) -> CollaborateResult> { @@ -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); diff --git a/shared-lib/flowy-collaboration/src/client_grid/mod.rs b/shared-lib/flowy-collaboration/src/client_grid/mod.rs index 5fbaa170b4..9dcb155557 100644 --- a/shared-lib/flowy-collaboration/src/client_grid/mod.rs +++ b/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::*; diff --git a/shared-lib/flowy-error-code/src/code.rs b/shared-lib/flowy-error-code/src/code.rs index c47d4fd8d4..166202bd5b 100644 --- a/shared-lib/flowy-error-code/src/code.rs +++ b/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 { diff --git a/shared-lib/flowy-error-code/src/protobuf/model/code.rs b/shared-lib/flowy-error-code/src/protobuf/model/code.rs index f70c1eb49d..769c059eb2 100644 --- a/shared-lib/flowy-error-code/src/protobuf/model/code.rs +++ b/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; diff --git a/shared-lib/flowy-error-code/src/protobuf/proto/code.proto b/shared-lib/flowy-error-code/src/protobuf/proto/code.proto index ed12d58848..4bf692b8ff 100644 --- a/shared-lib/flowy-error-code/src/protobuf/proto/code.proto +++ b/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; } diff --git a/shared-lib/flowy-folder-data-model/src/entities/view.rs b/shared-lib/flowy-folder-data-model/src/entities/view.rs index ac346188ce..3b98e3f64c 100644 --- a/shared-lib/flowy-folder-data-model/src/entities/view.rs +++ b/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}; diff --git a/shared-lib/flowy-grid-data-model/src/entities/meta.rs b/shared-lib/flowy-grid-data-model/src/entities/meta.rs index f8c762345d..d70d17e0e9 100644 --- a/shared-lib/flowy-grid-data-model/src/entities/meta.rs +++ b/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 { diff --git a/shared-lib/lib-infra/src/future.rs b/shared-lib/lib-infra/src/future.rs index 9a0b6550dd..9077dd18b7 100644 --- a/shared-lib/lib-infra/src/future.rs +++ b/shared-lib/lib-infra/src/future.rs @@ -65,34 +65,3 @@ where } pub type BoxResultFuture<'a, T, E> = BoxFuture<'a, Result>; - -#[pin_project] -pub struct FutureResultSend { - #[pin] - pub fut: Pin> + Send>>, -} - -impl FutureResultSend { - pub fn new(f: F) -> Self - where - F: Future> + Send + 'static, - { - Self { - fut: Box::pin(async { f.await }), - } - } -} - -impl Future for FutureResultSend -where - T: Send, - E: Debug, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - let result = ready!(this.fut.poll(cx)); - Poll::Ready(result) - } -}