Skip to content

Commit

Permalink
chore: enhanced serde to cell (#344)
Browse files Browse the repository at this point in the history
* fix: select option color backward compatibility

* fix: single select cell serialization returns json string instead of array of string

* feat: add multi select impl

* feat: cell reader writer test and improv

* feat: checkbox

* feat: datetime

* feat: url and clean up

* fix: test
  • Loading branch information
speed2exe authored Dec 8, 2024
1 parent 35181c7 commit b6c74cf
Show file tree
Hide file tree
Showing 6 changed files with 421 additions and 29 deletions.
78 changes: 63 additions & 15 deletions collab-database/src/fields/type_option/checkbox_type_option.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use crate::entity::FieldType;
use crate::fields::{
TypeOptionCellReader, TypeOptionCellWriter, TypeOptionData, TypeOptionDataBuilder,
Expand All @@ -19,11 +21,10 @@ impl CheckboxTypeOption {

impl TypeOptionCellReader for CheckboxTypeOption {
fn json_cell(&self, cell: &Cell) -> Value {
let value = match cell.get_as::<String>(CELL_DATA) {
None => "".to_string(),
Some(s) => Self::convert_raw_cell_data(self, &s),
};
Value::String(value)
match cell.get_as::<Arc<str>>(CELL_DATA) {
None => false.into(),
Some(s) => bool_from_str(&s).into(),
}
}

fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
Expand All @@ -43,14 +44,13 @@ impl TypeOptionCellReader for CheckboxTypeOption {
impl TypeOptionCellWriter for CheckboxTypeOption {
fn convert_json_to_cell(&self, value: Value) -> Cell {
let mut cell = new_cell_builder(FieldType::Checkbox);
if let Some(data) = match value {
Value::String(s) => Some(s),
Value::Bool(b) => Some(b.to_string()),
Value::Number(n) => Some(n.to_string()),
_ => None,
} {
cell.insert(CELL_DATA.into(), bool_from_str(&data).to_string().into());
}
let checked = match value {
Value::String(s) => bool_from_str(&s),
Value::Bool(b) => b,
Value::Number(n) => n.as_i64().unwrap_or(0) > 0,
_ => false,
};
cell.insert(CELL_DATA.into(), checked.to_string().into());
cell
}
}
Expand Down Expand Up @@ -88,12 +88,12 @@ mod tests {

// Convert cell to JSON
let value = option.json_cell(&cell);
assert_eq!(value, Value::String("true".to_string()));
assert_eq!(value, Value::Bool(true));

// Test with empty data
let empty_cell = new_cell_builder(FieldType::Checkbox);
let empty_value = option.json_cell(&empty_cell);
assert_eq!(empty_value, Value::String("".to_string()));
assert_eq!(empty_value, Value::Bool(false));
}

#[test]
Expand Down Expand Up @@ -156,4 +156,52 @@ mod tests {
assert!(!bool_from_str("invalid"));
assert!(!bool_from_str(""));
}

#[test]
fn checkbox_cell_to_serde() {
let checkbox_type_option = CheckboxTypeOption::new();
let cell_writer: Box<dyn TypeOptionCellReader> = Box::new(checkbox_type_option);
{
let mut cell: Cell = new_cell_builder(FieldType::Checkbox);
cell.insert(CELL_DATA.into(), "Yes".into());
let serde_val = cell_writer.json_cell(&cell);
assert_eq!(serde_val, Value::Bool(true));
}
}

#[test]
fn number_serde_to_cell() {
let checkbox_type_option = CheckboxTypeOption;
let cell_writer: Box<dyn TypeOptionCellWriter> = Box::new(checkbox_type_option);
{
// empty string
let cell: Cell = cell_writer.convert_json_to_cell(Value::String("".to_string()));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "false");
}
{
// "yes" in any case
let cell: Cell = cell_writer.convert_json_to_cell(Value::String("yEs".to_string()));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "true");
}
{
// bool
let cell: Cell = cell_writer.convert_json_to_cell(Value::Bool(true));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "true");
}
{
// js number
let cell: Cell = cell_writer.convert_json_to_cell(Value::Number(1.into()));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "true");
}
{
// js null
let cell: Cell = cell_writer.convert_json_to_cell(Value::Null);
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "false");
}
}
}
100 changes: 95 additions & 5 deletions collab-database/src/fields/type_option/date_type_option.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::entity::FieldType;

use crate::error::DatabaseError;
use chrono::Timelike;
use chrono::{Datelike, Local, TimeZone};

use crate::fields::{
TypeOptionCellReader, TypeOptionCellWriter, TypeOptionData, TypeOptionDataBuilder,
};
use crate::rows::{new_cell_builder, Cell};
use crate::template::entity::CELL_DATA;
use chrono::{FixedOffset, Local, MappedLocalTime, NaiveDateTime, NaiveTime, Offset, TimeZone};
use chrono::{FixedOffset, MappedLocalTime, NaiveDateTime, NaiveTime, Offset};
use chrono_tz::Tz;
use collab::util::AnyMapExt;
use serde::de::Visitor;
Expand Down Expand Up @@ -118,8 +121,39 @@ impl TypeOptionCellReader for DateTypeOption {

impl TypeOptionCellWriter for DateTypeOption {
fn convert_json_to_cell(&self, json_value: Value) -> Cell {
let cell_data = serde_json::from_value::<DateCellData>(json_value).unwrap();
Cell::from(&cell_data)
let date_cell_data: DateCellData = match json_value {
Value::Number(number) => DateCellData::from_timestamp(number.as_i64().unwrap_or_default()),
Value::String(s) => {
// try rfc3339 format
if let Ok(date) = chrono::DateTime::parse_from_rfc3339(&s) {
DateCellData::from_timestamp(date.timestamp())
} else {
// try naive time
if let Ok(Some(date)) = self.naive_time_from_time_string(true, Some(&s)) {
let seconds_since_midnight = date.num_seconds_from_midnight();
let start_of_day_ts = {
let now = Local::now();
let start_of_day = Local
.with_ymd_and_hms(now.year(), now.month(), now.day(), 0, 0, 0)
.unwrap();
start_of_day.timestamp()
};
DateCellData::from_timestamp(start_of_day_ts + seconds_since_midnight as i64)
} else {
// try to parse as json
if let Ok(date_cell_data_obj) = serde_json::from_str::<Value>(&s) {
serde_json::from_value::<DateCellData>(date_cell_data_obj).unwrap_or_default()
} else {
DateCellData::from_timestamp(0)
}
}
}
},
date_cell_data_obj => {
serde_json::from_value::<DateCellData>(date_cell_data_obj).unwrap_or_default()
},
};
Cell::from(&date_cell_data)
}
}

Expand Down Expand Up @@ -168,11 +202,11 @@ impl DateTypeOption {
pub fn naive_time_from_time_string(
&self,
include_time: bool,
time_str: Option<String>,
time_str: Option<&str>,
) -> Result<Option<NaiveTime>, DatabaseError> {
match (include_time, time_str) {
(true, Some(time_str)) => {
let result = NaiveTime::parse_from_str(&time_str, self.time_format.format_str());
let result = NaiveTime::parse_from_str(time_str, self.time_format.format_str());
match result {
Ok(time) => Ok(Some(time)),
Err(_e) => {
Expand Down Expand Up @@ -663,4 +697,60 @@ mod tests {
let result = date_type_option.convert_raw_cell_data(invalid_raw_data);
assert_eq!(result, "");
}

#[test]
fn date_cell_to_serde() {
let date_type_option = DateTypeOption::default_utc();
let cell_writer: Box<dyn TypeOptionCellReader> = Box::new(date_type_option);
{
let mut cell: Cell = new_cell_builder(FieldType::DateTime);
cell.insert(CELL_DATA.into(), "1672531200".into());
let serde_val = cell_writer.json_cell(&cell);
assert_eq!(
serde_val,
serde_json::to_value(DateCellData {
timestamp: Some(1672531200),
end_timestamp: None,
include_time: false,
is_range: false,
reminder_id: "".to_string(),
})
.unwrap()
);
}
}

#[test]
fn date_serde_to_cell() {
let date_type_option = DateTypeOption::default_utc();
let cell_writer: Box<dyn TypeOptionCellWriter> = Box::new(date_type_option);
{
// rf3339
let cell: Cell =
cell_writer.convert_json_to_cell(Value::String("2019-10-12T07:20:50.52Z".to_string()));
let data: String = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "1570864850");
}
{
// naive time
let cell: Cell = cell_writer.convert_json_to_cell(Value::String("12:51".to_string()));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
let last_2 = &data[data.len() - 2..];
assert_eq!(last_2, "60"); // because of the seconds
}
{
// enconded json
let str = serde_json::to_string(&DateCellData::from_timestamp(1570864850)).unwrap();
let cell: Cell = cell_writer.convert_json_to_cell(Value::String(str));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "1570864850");
}
{
// json
let js_val = serde_json::to_value(DateCellData::from_timestamp(1570864850)).unwrap();
let cell: Cell = cell_writer.convert_json_to_cell(js_val);
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "1570864850");
}
}
}
36 changes: 35 additions & 1 deletion collab-database/src/fields/type_option/number_type_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl From<NumberTypeOption> for TypeOptionData {
impl TypeOptionCellReader for NumberTypeOption {
fn json_cell(&self, cell: &Cell) -> Value {
// Returns the formated number string.
Value::String(self.stringify_cell(cell))
self.stringify_cell(cell).into()
}

fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
Expand Down Expand Up @@ -763,6 +763,10 @@ impl NumberFormat {

#[cfg(test)]
mod tests {
use collab::util::AnyMapExt;

use crate::template::entity::CELL_DATA;

use super::*;
/// Testing when the input is not a number.
#[test]
Expand Down Expand Up @@ -831,4 +835,34 @@ mod tests {
let output = type_option.convert_raw_cell_data(input_str);
assert_eq!(output, expected_str.to_owned());
}

#[test]
fn number_cell_to_serde() {
let number_type_option = NumberTypeOption::default();
let cell_writer: Box<dyn TypeOptionCellReader> = Box::new(number_type_option);
{
let mut cell: Cell = new_cell_builder(FieldType::Number);
cell.insert(CELL_DATA.into(), "42".into());
let serde_val = cell_writer.json_cell(&cell);
assert_eq!(serde_val, Value::String("42".into()));
}
}

#[test]
fn number_serde_to_cell() {
let number_type_option = NumberTypeOption::default();
let cell_writer: Box<dyn TypeOptionCellWriter> = Box::new(number_type_option);
{
// js string
let cell: Cell = cell_writer.convert_json_to_cell(Value::String("42.195".to_string()));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "42.195");
}
{
// js number
let cell: Cell = cell_writer.convert_json_to_cell(Value::Number(10.into()));
let data = cell.get_as::<String>(CELL_DATA).unwrap();
assert_eq!(data, "10");
}
}
}
Loading

0 comments on commit b6c74cf

Please sign in to comment.