serialize and deserialize InMemoryStore

master
Ondřej Hruška 3 years ago
parent 64ad16365f
commit 875ba10429
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 34
      src/main.rs
  2. 2
      yopa/Cargo.toml
  3. 34
      yopa/src/data.rs
  4. 5
      yopa/src/id.rs
  5. 1
      yopa/src/insert.rs
  6. 92
      yopa/src/lib.rs
  7. 30
      yopa/src/model.rs
  8. 54
      yopa/src/serde_map_as_list.rs
  9. 23
      yopa/src/tests.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(())
}

@ -22,5 +22,5 @@ itertools = "0.10.0"
lazy_static = "1.4.0"
[features]
default = []
default = ["uuid-ids"]
uuid-ids = ["uuid"]

@ -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
}
}

@ -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;
}

@ -56,3 +56,4 @@ impl InsertObj {
}
}
}

@ -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<ID, model::ObjectModel>,
#[serde(with = "serde_map_as_list")]
rel_models: HashMap<ID, model::RelationModel>,
#[serde(with = "serde_map_as_list")]
prop_models: HashMap<ID, model::PropertyModel>,
#[serde(with = "serde_map_as_list")]
objects: HashMap<ID, data::Object>,
#[serde(with = "serde_map_as_list")]
relations: HashMap<ID, data::Relation>,
#[serde(with = "serde_map_as_list")]
properties: HashMap<ID, data::Value>,
}
#[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<ID, StorageError> {
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<ID, StorageError> {
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<ID, StorageError> {
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<ObjectModel, StorageError> {
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<model::RelationModel, StorageError> {
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<model::PropertyModel, StorageError> {
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<InsertValue>, parent_model_id: ID| -> Result<Vec<data::Value>, StorageError> {
let find_values_to_insert = |values: Vec<InsertValue>, parent_id : ID, parent_model_id: ID| -> Result<Vec<data::Value>, 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 {

@ -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<ID>,
pub parent: Option<ID>,
}
/// 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
}
}

@ -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<S, X>(x: &HashMap<ID, X>, s: S) -> Result<S::Ok, S::Error>
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<HashMap<ID, X>, D::Error>
where
D: Deserializer<'de>,
X: Deserialize<'de> + HaveId
{
deserializer.deserialize_seq(SeqToMap(Default::default()))
}
pub struct SeqToMap<X>(PhantomData<X>);
impl<'de, X : Deserialize<'de> + HaveId> Visitor<'de> for SeqToMap<X> {
type Value = HashMap<ID, X>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("sequence of items with id")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut map = HashMap::<ID, X>::new();
while let Some(next) = seq.next_element::<X>()? {
map.insert(next.get_id(), next);
}
Ok(map)
}
}

@ -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());
}
Loading…
Cancel
Save