From 875ba10429a858acb185279a4ea0b22e2455b950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 6 Feb 2021 00:00:59 +0100 Subject: [PATCH] serialize and deserialize InMemoryStore --- src/main.rs | 34 +++++++------ yopa/Cargo.toml | 2 +- yopa/src/data.rs | 34 ++++++++++--- yopa/src/id.rs | 5 ++ yopa/src/insert.rs | 1 + yopa/src/lib.rs | 92 ++++++++++++++++++++--------------- yopa/src/model.rs | 30 ++++++++++-- yopa/src/serde_map_as_list.rs | 54 ++++++++++++++++++++ yopa/src/tests.rs | 23 +++++++++ 9 files changed, 212 insertions(+), 63 deletions(-) create mode 100644 yopa/src/serde_map_as_list.rs create mode 100644 yopa/src/tests.rs diff --git a/src/main.rs b/src/main.rs index eafcca8..191a240 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use log::LevelFilter; -use yopa::model; +use yopa::{model, InMemoryStorage}; use yopa::model::DataType; use yopa::insert::{InsertObj, InsertValue, InsertRel}; use yopa::data::TypedValue; @@ -10,13 +10,11 @@ use yopa::data::TypedValue; fn main() { simple_logging::log_to_stderr(LevelFilter::Debug); - if let Err(e) = _main() { - error!("{:?}", e); - } + main_test_recipes().unwrap(); } #[allow(non_snake_case)] -fn _main() -> anyhow::Result<()> { +fn main_test_recipes() -> anyhow::Result<()> { simple_logging::log_to_stderr(LevelFilter::Debug); let mut store = yopa::InMemoryStorage::new(); @@ -24,12 +22,12 @@ fn _main() -> anyhow::Result<()> { let Recipe = store.define_object(model::ObjectModel { id: Default::default(), name: "recipe".to_string(), - parent_tpl_id: None + parent: None })?; let RecipeTitle = store.define_property(model::PropertyModel { id: Default::default(), - parent_tpl_id: Recipe, + object: Recipe, name: "title".to_string(), optional: false, multiple: false, @@ -39,7 +37,7 @@ fn _main() -> anyhow::Result<()> { let _PrepHours = store.define_property(model::PropertyModel { id: Default::default(), - parent_tpl_id: Recipe, + object: Recipe, name: "prep_hours".to_string(), optional: true, multiple: false, @@ -50,12 +48,12 @@ fn _main() -> anyhow::Result<()> { let Book = store.define_object(model::ObjectModel { id: Default::default(), name: "book".to_string(), - parent_tpl_id: None + parent: None })?; let BookName = store.define_property(model::PropertyModel { id: Default::default(), - parent_tpl_id: Book, + object: Book, name: "name".to_string(), optional: false, multiple: false, @@ -65,16 +63,16 @@ fn _main() -> anyhow::Result<()> { let BookToRecipe = store.define_relation(model::RelationModel { id: Default::default(), - object_tpl_id: Recipe, + object: Recipe, name: "book reference".to_string(), optional: true, multiple: true, - related_tpl_id: Book + related: Book })?; let BookToRecipePage = store.define_property(model::PropertyModel { id: Default::default(), - parent_tpl_id: BookToRecipe, + object: BookToRecipe, name: "page".to_string(), optional: false, multiple: false, @@ -102,7 +100,7 @@ fn _main() -> anyhow::Result<()> { model_id: BookToRecipe, related_id: MyBook1, values: vec![ - //InsertValue::new(BookToRecipePage, TypedValue::Integer(123)) + InsertValue::new(BookToRecipePage, TypedValue::Integer(123)) ] } ], @@ -110,5 +108,13 @@ fn _main() -> anyhow::Result<()> { debug!("{:#?}", store); + let as_s = serde_json::to_string_pretty(&store).unwrap(); + + println!("{}", as_s); + + let back : InMemoryStorage = serde_json::from_str(&as_s)?; + + debug!("After unpack: {:#?}", store); + Ok(()) } diff --git a/yopa/Cargo.toml b/yopa/Cargo.toml index b83f136..ca36030 100644 --- a/yopa/Cargo.toml +++ b/yopa/Cargo.toml @@ -22,5 +22,5 @@ itertools = "0.10.0" lazy_static = "1.4.0" [features] -default = [] +default = ["uuid-ids"] uuid-ids = ["uuid"] diff --git a/yopa/src/data.rs b/yopa/src/data.rs index 972ccb9..44155f8 100644 --- a/yopa/src/data.rs +++ b/yopa/src/data.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::ID; use crate::model::DataType; +use crate::id::HaveId; /// Value of a particular type #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -158,33 +159,54 @@ mod tests { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Object { /// PK + #[serde(default)] pub id: ID, /// Object template ID - pub model_id: ID, + pub model: ID, } /// Relation between two objects #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Relation { /// PK + #[serde(default)] pub id: ID, /// Source object ID - pub object_id: ID, + pub object: ID, /// Relation template ID - pub model_id: ID, + pub model: ID, /// Related object ID - pub related_id: ID, + pub related: ID, } /// Value attached to an object #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Value { /// PK + #[serde(default)] pub id: ID, /// Owning object ID - pub object_id: ID, + pub object: ID, /// Property template ID - pub model_id: ID, + pub model: ID, /// Property value pub value: TypedValue, } + +impl HaveId for Object { + fn get_id(&self) -> ID { + self.id + } +} + +impl HaveId for Relation { + fn get_id(&self) -> ID { + self.id + } +} + +impl HaveId for Value { + fn get_id(&self) -> ID { + self.id + } +} diff --git a/yopa/src/id.rs b/yopa/src/id.rs index 742cd4d..1aebe2d 100644 --- a/yopa/src/id.rs +++ b/yopa/src/id.rs @@ -46,3 +46,8 @@ mod impl_u64 { pub use impl_uuid::{ID, next_id, zero_id}; #[cfg(not(feature = "uuid-ids"))] pub use impl_u64::{ID, next_id, zero_id}; + +/// Something that has ID +pub trait HaveId { + fn get_id(&self) -> ID; +} diff --git a/yopa/src/insert.rs b/yopa/src/insert.rs index a30ef03..0053559 100644 --- a/yopa/src/insert.rs +++ b/yopa/src/insert.rs @@ -56,3 +56,4 @@ impl InsertObj { } } } + diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs index b0c69e5..c9587df 100644 --- a/yopa/src/lib.rs +++ b/yopa/src/lib.rs @@ -1,3 +1,5 @@ +#[macro_use] extern crate serde_json; + use std::borrow::Cow; use std::collections::HashMap; @@ -11,6 +13,7 @@ use id::next_id; use insert::InsertObj; use insert::InsertValue; use model::ObjectModel; +use serde::ser::SerializeSeq; pub mod model; pub mod data; @@ -18,18 +21,29 @@ pub mod insert; pub mod id; mod cool; +#[cfg(test)] +mod tests; +mod serde_map_as_list; + /// Stupid storage with no persistence #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct InMemoryStorage { + #[serde(with = "serde_map_as_list")] obj_models: HashMap, + #[serde(with = "serde_map_as_list")] rel_models: HashMap, + #[serde(with = "serde_map_as_list")] prop_models: HashMap, + #[serde(with = "serde_map_as_list")] objects: HashMap, + #[serde(with = "serde_map_as_list")] relations: HashMap, + #[serde(with = "serde_map_as_list")] properties: HashMap, } + #[derive(Debug, Error)] pub enum StorageError { #[error("Referenced {0} does not exist")] @@ -46,7 +60,7 @@ impl InMemoryStorage { /// Define a data object pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result { - if let Some(pid) = tpl.parent_tpl_id { + if let Some(pid) = tpl.parent { if !self.obj_models.contains_key(&pid) { return Err(StorageError::NotExist(format!("parent object model {}", pid).into())); } @@ -64,16 +78,16 @@ impl InMemoryStorage { /// Define a relation between two data objects pub fn define_relation(&mut self, mut rel: model::RelationModel) -> Result { - if !self.obj_models.contains_key(&rel.object_tpl_id) { - return Err(StorageError::NotExist(format!("source object model {}", rel.object_tpl_id).into())); + if !self.obj_models.contains_key(&rel.object) { + return Err(StorageError::NotExist(format!("source object model {}", rel.object).into())); } - if !self.obj_models.contains_key(&rel.related_tpl_id) { - return Err(StorageError::NotExist(format!("related object model {}", rel.related_tpl_id).into())); + if !self.obj_models.contains_key(&rel.related) { + return Err(StorageError::NotExist(format!("related object model {}", rel.related).into())); } - if self.rel_models.iter().find(|(_, t)| t.name == rel.name && t.object_tpl_id == rel.object_tpl_id).is_some() { + if self.rel_models.iter().find(|(_, t)| t.name == rel.name && t.object == rel.object).is_some() { return Err(StorageError::ConstraintViolation( - format!("relation with the name \"{}\" and on model {} already exists", rel.name, rel.object_tpl_id).into())); + format!("relation with the name \"{}\" and on model {} already exists", rel.name, rel.object).into())); } let id = next_id(); @@ -84,16 +98,16 @@ impl InMemoryStorage { /// Define a property attached to an object or a relation pub fn define_property(&mut self, mut prop: model::PropertyModel) -> Result { - if !self.obj_models.contains_key(&prop.parent_tpl_id) { + if !self.obj_models.contains_key(&prop.object) { // Maybe it's attached to a relation? - if !self.rel_models.contains_key(&prop.parent_tpl_id) { - return Err(StorageError::NotExist(format!("object or relation model {}", prop.parent_tpl_id).into())); + if !self.rel_models.contains_key(&prop.object) { + return Err(StorageError::NotExist(format!("object or relation model {}", prop.object).into())); } } - if self.prop_models.iter().find(|(_, t)| t.parent_tpl_id == prop.parent_tpl_id && t.name == prop.name).is_some() { + if self.prop_models.iter().find(|(_, t)| t.object == prop.object && t.name == prop.name).is_some() { return Err(StorageError::ConstraintViolation( - format!("property with the name \"{}\" already exists on model {}", prop.name, prop.parent_tpl_id).into())); + format!("property with the name \"{}\" already exists on model {}", prop.name, prop.object).into())); } // Ensure the default type is compatible @@ -114,21 +128,21 @@ impl InMemoryStorage { pub fn undefine_object(&mut self, id: ID) -> Result { return if let Some(t) = self.obj_models.remove(&id) { // Remove relation templates - let removed_relation_ids = map_drain_filter(&mut self.rel_models, |_k, v| v.object_tpl_id == id || v.related_tpl_id == id) + let removed_relation_ids = map_drain_filter(&mut self.rel_models, |_k, v| v.object == id || v.related == id) .keys(); // Remove related property templates - let removed_prop_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.parent_tpl_id == id || removed_relation_ids.contains(&v.parent_tpl_id)) + let removed_prop_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.object == id || removed_relation_ids.contains(&v.object)) .keys(); // Remove objects - let _ = map_drain_filter(&mut self.objects, |_k, v| v.model_id == id); + let _ = map_drain_filter(&mut self.objects, |_k, v| v.model == id); // Remove property values - let _ = map_drain_filter(&mut self.properties, |_k, v| removed_prop_ids.contains(&v.model_id)); + let _ = map_drain_filter(&mut self.properties, |_k, v| removed_prop_ids.contains(&v.model)); // Remove relations - let _ = map_drain_filter(&mut self.relations, |_k, v| removed_relation_ids.contains(&v.model_id)); + let _ = map_drain_filter(&mut self.relations, |_k, v| removed_relation_ids.contains(&v.model)); // Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with. @@ -142,12 +156,12 @@ impl InMemoryStorage { pub fn undefine_relation(&mut self, id: ID) -> Result { return if let Some(t) = self.rel_models.remove(&id) { // Remove relations - let _ = map_drain_filter(&mut self.relations, |_k, v| v.model_id == id).keys(); + let _ = map_drain_filter(&mut self.relations, |_k, v| v.model == id).keys(); // Remove related property templates - let removed_prop_tpl_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.parent_tpl_id == id).keys(); + let removed_prop_tpl_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.object == id).keys(); - let _ = map_drain_filter(&mut self.properties, |_k, v| removed_prop_tpl_ids.contains(&v.model_id)); + let _ = map_drain_filter(&mut self.properties, |_k, v| removed_prop_tpl_ids.contains(&v.model)); // Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with. @@ -161,7 +175,7 @@ impl InMemoryStorage { pub fn undefine_property(&mut self, id: ID) -> Result { return if let Some(t) = self.prop_models.remove(&id) { // Remove relations - let _ = map_drain_filter(&mut self.properties, |_k, v| v.model_id == id); + let _ = map_drain_filter(&mut self.properties, |_k, v| v.model == id); Ok(t) } else { Err(StorageError::NotExist(format!("property model {}", id).into())) @@ -194,14 +208,14 @@ impl InMemoryStorage { let object_id = next_id(); let object = data::Object { id: object_id, - model_id: obj_model_id, + model: obj_model_id, }; - let find_values_to_insert = |values: Vec, parent_model_id: ID| -> Result, StorageError> { + let find_values_to_insert = |values: Vec, parent_id : ID, parent_model_id: ID| -> Result, StorageError> { let mut values_by_id = values.into_iter().into_group_map_by(|iv| iv.model_id); let mut values_to_insert = vec![]; - for (id, prop) in self.prop_models.iter().filter(|(_id, p)| p.parent_tpl_id == parent_model_id) { + for (id, prop) in self.prop_models.iter().filter(|(_id, p)| p.object == parent_model_id) { if let Some(values) = values_by_id.remove(id) { if values.len() > 1 && !prop.multiple { return Err(StorageError::ConstraintViolation(format!("{} of {} cannot have multiple values", prop, self.describe_id(parent_model_id)).into())); @@ -209,8 +223,8 @@ impl InMemoryStorage { for val_instance in values { values_to_insert.push(data::Value { id: next_id(), - object_id, - model_id: prop.id, + object: parent_id, + model: prop.id, value: val_instance.value.cast_to(prop.data_type) .map_err(|v| StorageError::ConstraintViolation(format!("{} cannot accept value {:?}", prop, v).into()))?, }); @@ -220,8 +234,8 @@ impl InMemoryStorage { if let Some(def) = &prop.default { values_to_insert.push(data::Value { id: next_id(), - object_id, - model_id: prop.id, + object: parent_id, + model: prop.id, value: def.clone(), }); } else { @@ -234,13 +248,13 @@ impl InMemoryStorage { Ok(values_to_insert) }; - let mut values_to_insert = find_values_to_insert(insobj.values, obj_model_id)?; + let mut values_to_insert = find_values_to_insert(insobj.values, object_id, obj_model_id)?; // And now ..... relations! let mut relations_by_id = insobj.relations.into_iter().into_group_map_by(|ir| ir.model_id); let mut relations_to_insert = vec![]; - for (relation_model_id, relation_model) in self.rel_models.iter().filter(|(_id, r)| r.object_tpl_id == obj_model_id) { + for (relation_model_id, relation_model) in self.rel_models.iter().filter(|(_id, r)| r.object == obj_model_id) { if let Some(instances) = relations_by_id.remove(relation_model_id) { if instances.len() > 1 && !relation_model.multiple { return Err(StorageError::ConstraintViolation(format!("{} of {} cannot be set multiply", relation_model, obj_model).into())); @@ -248,23 +262,25 @@ impl InMemoryStorage { for rel_instance in instances { if let Some(related) = self.objects.get(&rel_instance.related_id) { - if related.model_id != relation_model.related_tpl_id { + if related.model != relation_model.related { return Err(StorageError::ConstraintViolation( format!("{} of {} requires object of type {}, got {}", relation_model, obj_model, - self.describe_id(relation_model.related_tpl_id), - self.describe_id(related.model_id)).into())); + self.describe_id(relation_model.related), + self.describe_id(related.model)).into())); } } + let relation_id = next_id(); + // Relations can have properties - values_to_insert.extend(find_values_to_insert(rel_instance.values, *relation_model_id)?); + values_to_insert.extend(find_values_to_insert(rel_instance.values, relation_id, *relation_model_id)?); relations_to_insert.push(data::Relation { - id: next_id(), - object_id, - model_id: rel_instance.model_id, - related_id: rel_instance.related_id, + id: relation_id, + object: object_id, + model: rel_instance.model_id, + related: rel_instance.related_id, }); } } else { diff --git a/yopa/src/model.rs b/yopa/src/model.rs index 1997230..a38e9ed 100644 --- a/yopa/src/model.rs +++ b/yopa/src/model.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use super::data::TypedValue; use super::ID; +use crate::id::HaveId; /// Get a description of a struct pub trait Describe { @@ -18,20 +19,22 @@ pub trait Describe { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ObjectModel { /// PK + #[serde(default)] pub id: ID, /// Template name, unique within the database pub name: String, /// Parent object template ID - pub parent_tpl_id: Option, + pub parent: Option, } /// Relation between templates #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RelationModel { /// PK + #[serde(default)] pub id: ID, /// Object template ID - pub object_tpl_id: ID, + pub object: ID, /// Relation name, unique within the parent object pub name: String, /// Relation is optional @@ -39,16 +42,17 @@ pub struct RelationModel { /// Relation can be multiple pub multiple: bool, /// Related object template ID - pub related_tpl_id: ID, + pub related: ID, } /// Property definition #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PropertyModel { /// PK + #[serde(default)] pub id: ID, /// Object or Reference template ID - pub parent_tpl_id: ID, + pub object: ID, /// Property name, unique within the parent object or reference pub name: String, /// Property is optional @@ -91,3 +95,21 @@ impl Display for PropertyModel { write!(f, "property \"{}\" ({})", self.name, self.id) } } + +impl HaveId for ObjectModel { + fn get_id(&self) -> ID { + self.id + } +} + +impl HaveId for RelationModel { + fn get_id(&self) -> ID { + self.id + } +} + +impl HaveId for PropertyModel { + fn get_id(&self) -> ID { + self.id + } +} diff --git a/yopa/src/serde_map_as_list.rs b/yopa/src/serde_map_as_list.rs new file mode 100644 index 0000000..8c55d3b --- /dev/null +++ b/yopa/src/serde_map_as_list.rs @@ -0,0 +1,54 @@ +//! Serialize a HashMap of ID-keyed structs that also contain the ID inside +//! to a plain list, and also back when deserializing. + +use std::collections::HashMap; +use crate::ID; +use serde::{Serialize, Deserializer, Deserialize, de}; +use serde::ser::SerializeSeq; +use serde::de::{Visitor, SeqAccess, Unexpected}; +use std::fmt; +use crate::id::HaveId; +use std::marker::PhantomData; + +pub fn serialize(x: &HashMap, s: S) -> Result + where + S: serde::Serializer, + X: Serialize + HaveId +{ + let mut seq = s.serialize_seq(Some(x.len()))?; + for p in x.values() { + seq.serialize_element(p)?; + } + seq.end() +} + +pub fn deserialize<'de, D, X>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + X: Deserialize<'de> + HaveId +{ + deserializer.deserialize_seq(SeqToMap(Default::default())) +} + +pub struct SeqToMap(PhantomData); + +impl<'de, X : Deserialize<'de> + HaveId> Visitor<'de> for SeqToMap { + type Value = HashMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("sequence of items with id") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut map = HashMap::::new(); + + while let Some(next) = seq.next_element::()? { + map.insert(next.get_id(), next); + } + + Ok(map) + } +} diff --git a/yopa/src/tests.rs b/yopa/src/tests.rs new file mode 100644 index 0000000..7b2c193 --- /dev/null +++ b/yopa/src/tests.rs @@ -0,0 +1,23 @@ +use crate::model::ObjectModel; + +#[test] +fn test1() { + let model = crate::model::ObjectModel { + id: Default::default(), + name: "Name".to_string(), + parent: None + }; + + println!("{}", serde_json::to_string_pretty(&model).unwrap()); + + let s = r#" + { + "name": "Name", + "parent_tpl_id": null + } + "#; + + let x : ObjectModel = serde_json::from_str(s).unwrap(); + + println!("{}", serde_json::to_string_pretty(&x).unwrap()); +}