Compare commits

...

2 Commits

  1. 3
      yopa-web/build.rs
  2. 322
      yopa-web/src/main.rs
  3. 25
      yopa-web/src/routes.rs
  4. 61
      yopa-web/src/routes/models.rs
  5. 19
      yopa-web/src/routes/models/object.rs
  6. 76
      yopa-web/src/routes/models/property.rs
  7. 45
      yopa-web/src/routes/models/relation.rs
  8. 226
      yopa-web/src/routes/objects.rs
  9. 41
      yopa-web/src/tera_ext.rs
  10. 29
      yopa-web/src/utils.rs
  11. 5
      yopa/src/cool.rs
  12. 297
      yopa/src/data.rs
  13. 6
      yopa/src/id.rs
  14. 10
      yopa/src/insert.rs
  15. 528
      yopa/src/lib.rs
  16. 2
      yopa/src/model.rs
  17. 26
      yopa/src/serde_map_as_list.rs
  18. 2
      yopa/src/tests.rs
  19. 16
      yopa/src/update.rs

@ -10,5 +10,6 @@ fn main() {
resource_dir("./resources/static")
.with_generated_filename(out_path.join("static_files.rs"))
.with_generated_fn("included_static_files")
.build().unwrap();
.build()
.unwrap();
}

@ -3,35 +3,27 @@ extern crate actix_web;
#[macro_use]
extern crate log;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use actix_session::CookieSession;
use actix_web::{App, get, HttpRequest, HttpResponse, HttpServer, post, Responder, web};
use actix_web::http::StatusCode;
use actix_web::web::{scope, service};
use actix_web::{web, App, HttpResponse, HttpServer};
use actix_web_static_files;
use actix_web_static_files::ResourceFiles as StaticFiles;
use include_dir::Dir;
use log::LevelFilter;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use rand::Rng;
use tera::Tera;
use yopa::insert::{InsertObj, InsertRel, InsertValue};
use yopa::{Storage, TypedValue};
use crate::tera_ext::TeraExt;
use yopa::insert::{InsertObj, InsertValue, InsertRel};
mod utils;
mod tera_ext;
mod routes;
mod session_ext;
mod tera_ext;
mod utils;
// Embed static files
include!(concat!(env!("OUT_DIR"), "/static_files.rs"));
@ -45,47 +37,52 @@ pub(crate) static TERA: Lazy<Tera> = Lazy::new(|| {
// Special filter for the TypedValue map
use serde_json::Value;
tera.register_filter("print_typed_value", |v: &Value, _: &HashMap<String, Value>| -> tera::Result<Value> {
if v.is_null() {
return Ok(v.clone());
}
if let Value::Object(map) = v {
if let Some((_, v)) = map.iter().next() {
tera.register_filter(
"print_typed_value",
|v: &Value, _: &HashMap<String, Value>| -> tera::Result<Value> {
if v.is_null() {
return Ok(v.clone());
}
}
Err(tera::Error::msg("Expected nonenmpty object"))
});
if let Value::Object(map) = v {
if let Some((_, v)) = map.iter().next() {
return Ok(v.clone());
}
}
Err(tera::Error::msg("Expected nonenmpty object"))
},
);
// opt(checked=foo.is_checked)
tera.register_function("opt", |args: &HashMap<String, Value>| -> tera::Result<Value> {
if args.len() != 1 {
return Err("Expected 1 argument".into());
}
match args.iter().nth(0) {
Some((name, &Value::Bool(true))) => {
Ok(Value::String(name.clone()))
},
Some((_, &Value::Bool(false))) => {
Ok(Value::Null)
},
_ => Err("Expected bool argument".into()),
}
});
tera.register_function(
"opt",
|args: &HashMap<String, Value>| -> tera::Result<Value> {
if args.len() != 1 {
return Err("Expected 1 argument".into());
}
match args.iter().nth(0) {
Some((name, &Value::Bool(true))) => Ok(Value::String(name.clone())),
Some((_, &Value::Bool(false))) => Ok(Value::Null),
_ => Err("Expected bool argument".into()),
}
},
);
// selected(val=foo.color,opt="Red")
tera.register_function("selected", |args: &HashMap<String, Value>| -> tera::Result<Value> {
match (args.get("val"), args.get("opt")) {
(Some(v), Some(w)) => {
if v == w {
Ok(Value::String("selected".into()))
} else {
Ok(Value::Null)
tera.register_function(
"selected",
|args: &HashMap<String, Value>| -> tera::Result<Value> {
match (args.get("val"), args.get("opt")) {
(Some(v), Some(w)) => {
if v == w {
Ok(Value::String("selected".into()))
} else {
Ok(Value::Null)
}
}
},
_ => Err("Expected val and opt args".into()),
}
});
_ => Err("Expected val and opt args".into()),
}
},
);
// TODO need to inject HttpRequest::url_for() into tera context, but it then can't be accessed by the functions.
// tera.register_function("url_for", |args: HashMap<String, Value>| -> tera::Result<Value> {
@ -117,19 +114,14 @@ async fn main() -> std::io::Result<()> {
debug!("Session key: {:?}", session_key);
HttpServer::new(move || {
let static_files = StaticFiles::new("/static", included_static_files())
.do_not_resolve_defaults();
let static_files =
StaticFiles::new("/static", included_static_files()).do_not_resolve_defaults();
App::new()
/* Middlewares */
.wrap(
CookieSession::signed(&session_key)
.secure(false)
)
.wrap(CookieSession::signed(&session_key).secure(false))
/* Bind shared objects */
.app_data(yopa_store.clone())
/* Routes */
.service(routes::index)
.service(routes::takeout)
@ -163,13 +155,15 @@ async fn main() -> std::io::Result<()> {
.service(routes::objects::delete)
//
.service(static_files)
.default_service(web::to(|| HttpResponse::NotFound().body("File or endpoint not found")))
.default_service(web::to(|| {
HttpResponse::NotFound().body("File or endpoint not found")
}))
})
.bind("127.0.0.1:8080")?
.run().await
.bind("127.0.0.1:8080")?
.run()
.await
}
fn init_yopa() -> YopaStoreWrapper {
let mut store = Storage::new();
@ -177,107 +171,123 @@ fn init_yopa() -> YopaStoreWrapper {
use yopa::model;
use yopa::DataType;
let id_recipe = store.define_object(model::ObjectModel {
id: Default::default(),
name: "Recipe".to_string(),
}).unwrap();
let id_book = store.define_object(model::ObjectModel {
id: Default::default(),
name: "Book".to_string(),
}).unwrap();
let id_ing = store.define_object(model::ObjectModel {
id: Default::default(),
name: "Ingredient".to_string(),
}).unwrap();
let val_descr = store.define_property(model::PropertyModel {
id: Default::default(),
object: id_recipe,
name: "description".to_string(),
optional: true,
multiple: true,
data_type: DataType::String,
default: TypedValue::String("".into()),
}).unwrap();
store.define_property(model::PropertyModel {
id: Default::default(),
object: id_book,
name: "author".to_string(),
optional: true,
multiple: true,
data_type: DataType::String,
default: TypedValue::String("Pepa Novák".into()),
}).unwrap();
let rel_book_id = store.define_relation(model::RelationModel {
id: Default::default(),
object: id_recipe,
name: "book reference".to_string(),
reciprocal_name: "recipes".to_string(),
optional: true,
multiple: true,
related: id_book,
}).unwrap();
let page = store.define_property(model::PropertyModel {
id: Default::default(),
object: rel_book_id,
name: "page".to_string(),
optional: true,
multiple: false,
data_type: DataType::Integer,
default: TypedValue::Integer(0),
}).unwrap();
store.define_relation(model::RelationModel {
id: Default::default(),
object: id_recipe,
name: "related recipe".to_string(),
reciprocal_name: "related recipe".to_string(),
optional: true,
multiple: true,
related: id_recipe,
}).unwrap();
let book1 = store.insert_object(InsertObj {
model: id_book,
name: "Book 1".to_string(),
values: vec![],
relations: vec![]
}).unwrap();
store.insert_object(InsertObj {
model: id_book,
name: "Book 2".to_string(),
values: vec![],
relations: vec![]
}).unwrap();
store.insert_object(InsertObj {
model: id_recipe,
name: "Recipe1".to_string(),
values: vec![
InsertValue {
let id_recipe = store
.define_object(model::ObjectModel {
id: Default::default(),
name: "Recipe".to_string(),
})
.unwrap();
let id_book = store
.define_object(model::ObjectModel {
id: Default::default(),
name: "Book".to_string(),
})
.unwrap();
let _id_ing = store
.define_object(model::ObjectModel {
id: Default::default(),
name: "Ingredient".to_string(),
})
.unwrap();
let val_descr = store
.define_property(model::PropertyModel {
id: Default::default(),
object: id_recipe,
name: "description".to_string(),
optional: true,
multiple: true,
data_type: DataType::String,
default: TypedValue::String("".into()),
})
.unwrap();
store
.define_property(model::PropertyModel {
id: Default::default(),
object: id_book,
name: "author".to_string(),
optional: true,
multiple: true,
data_type: DataType::String,
default: TypedValue::String("Pepa Novák".into()),
})
.unwrap();
let rel_book_id = store
.define_relation(model::RelationModel {
id: Default::default(),
object: id_recipe,
name: "book reference".to_string(),
reciprocal_name: "recipes".to_string(),
optional: true,
multiple: true,
related: id_book,
})
.unwrap();
let page = store
.define_property(model::PropertyModel {
id: Default::default(),
object: rel_book_id,
name: "page".to_string(),
optional: true,
multiple: false,
data_type: DataType::Integer,
default: TypedValue::Integer(0),
})
.unwrap();
store
.define_relation(model::RelationModel {
id: Default::default(),
object: id_recipe,
name: "related recipe".to_string(),
reciprocal_name: "related recipe".to_string(),
optional: true,
multiple: true,
related: id_recipe,
})
.unwrap();
let book1 = store
.insert_object(InsertObj {
model: id_book,
name: "Book 1".to_string(),
values: vec![],
relations: vec![],
})
.unwrap();
store
.insert_object(InsertObj {
model: id_book,
name: "Book 2".to_string(),
values: vec![],
relations: vec![],
})
.unwrap();
store
.insert_object(InsertObj {
model: id_recipe,
name: "Recipe1".to_string(),
values: vec![InsertValue {
model: val_descr,
value: TypedValue::String("Bla bla bla".into())
}
],
relations: vec![
InsertRel {
value: TypedValue::String("Bla bla bla".into()),
}],
relations: vec![InsertRel {
model: rel_book_id,
related: book1,
values: vec![
InsertValue {
model: page,
value: TypedValue::Integer(15)
}
]
}
]
}).unwrap();
values: vec![InsertValue {
model: page,
value: TypedValue::Integer(15),
}],
}],
})
.unwrap();
web::Data::new(tokio::sync::RwLock::new(store))
}

@ -1,28 +1,17 @@
use std::fmt::{Debug, Display};
use std::ops::{DerefMut, Deref};
use std::str::FromStr;
use std::ops::Deref;
use actix_session::Session;
use actix_web::{HttpRequest, HttpResponse, Responder, web};
use actix_web::http::header::IntoHeaderValue;
use serde::{Deserialize, Serialize};
use yopa::{DataType, ID, Storage, StorageError, TypedValue};
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt;
use crate::routes::models::relation::RelationModelDisplay;
use crate::routes::models::object::ObjectModelDisplay;
use actix_web::{HttpResponse, Responder};
pub(crate) mod models;
pub(crate) mod objects;
#[get("/")]
pub(crate) async fn index(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<HttpResponse> {
objects::list_inner(session, store)
.await
pub(crate) async fn index(
session: Session,
store: crate::YopaStoreWrapper,
) -> actix_web::Result<HttpResponse> {
objects::list_inner(session, store).await
}
#[get("/takeout")]

@ -1,17 +1,20 @@
use actix_session::Session;
use actix_web::Responder;
use crate::routes::models::relation::RelationModelDisplay;
use crate::routes::models::object::ObjectModelDisplay;
use crate::routes::models::relation::RelationModelDisplay;
use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt;
use crate::TERA;
use actix_session::Session;
use actix_web::Responder;
pub(crate) mod object;
pub(crate) mod relation;
pub(crate) mod property;
pub(crate) mod relation;
#[get("/models")]
pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> {
pub(crate) async fn list(
session: Session,
store: crate::YopaStoreWrapper,
) -> actix_web::Result<impl Responder> {
let rg = store.read().await;
let models_iter = rg.get_object_models();
@ -24,31 +27,37 @@ pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> ac
let mut models = vec![];
for om in models_iter {
let mut relations = model_relations.remove(&om.id).unwrap_or_default();
let mut relations = relations.into_iter().map(|rm| {
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
rprops.sort_by_key(|m| &m.name);
let relations = model_relations.remove(&om.id).unwrap_or_default();
let mut relations = relations
.into_iter()
.map(|rm| {
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
rprops.sort_by_key(|m| &m.name);
RelationModelDisplay {
model: rm,
related_name: rg.get_model_name(rm.related),
properties: rprops,
}
}).collect::<Vec<_>>();
RelationModelDisplay {
model: rm,
related_name: rg.get_model_name(rm.related),
properties: rprops,
}
})
.collect::<Vec<_>>();
relations.sort_by_key(|d| &d.model.name);
// Relations coming INTO this model
let mut reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default();
let mut reciprocal_relations = reciprocal_relations.into_iter().map(|rm| {
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
rprops.sort_by_key(|m| &m.name);
let reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default();
let mut reciprocal_relations = reciprocal_relations
.into_iter()
.map(|rm| {
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
rprops.sort_by_key(|m| &m.name);
RelationModelDisplay {
model: rm,
related_name: rg.get_model_name(rm.object),
properties: rprops,
}
}).collect::<Vec<_>>();
RelationModelDisplay {
model: rm,
related_name: rg.get_model_name(rm.object),
properties: rprops,
}
})
.collect::<Vec<_>>();
reciprocal_relations.sort_by_key(|d| &d.model.reciprocal_name);
let mut properties = model_props.remove(&om.id).unwrap_or_default();

@ -1,15 +1,15 @@
use actix_session::Session;
use actix_web::{Responder, web};
use actix_web::{web, Responder};
use serde::{Deserialize, Serialize};
use yopa::ID;
use yopa::model::{ObjectModel, PropertyModel};
use yopa::ID;
use crate::routes::models::relation::RelationModelDisplay;
use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt;
use crate::utils::redirect;
use crate::TERA;
#[derive(Serialize, Debug)]
pub(crate) struct ObjectModelDisplay<'a> {
@ -60,7 +60,7 @@ pub(crate) async fn create(
Err(e) => {
warn!("Error creating model: {}", e);
session.flash_error(e.to_string());
session.set("old", form);
session.set("old", form).unwrap();
redirect("/model/object/create")
}
}
@ -76,7 +76,8 @@ pub(crate) async fn update_form(
session.render_flash(&mut context);
let rg = store.read().await;
let model = rg.get_object_model(*model_id)
let model = rg
.get_object_model(*model_id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
// Re-fill old values
@ -114,13 +115,12 @@ pub(crate) async fn update(
Err(e) => {
warn!("Error updating model: {}", e);
session.flash_error(e.to_string());
session.set("old", form);
session.set("old", form).unwrap();
redirect(format!("/model/object/update/{}", id))
}
}
}
#[get("/model/object/delete/{id}")]
pub(crate) async fn delete(
id: web::Path<String>,
@ -128,7 +128,10 @@ pub(crate) async fn delete(
session: Session,
) -> actix_web::Result<impl Responder> {
let mut wg = store.write().await;
match wg.undefine_object(id.parse().map_err(|e| actix_web::error::ErrorBadRequest(e))?) {
match wg.undefine_object(
id.parse()
.map_err(|e| actix_web::error::ErrorBadRequest(e))?,
) {
Ok(om) => {
debug!("Object model deleted, redirecting to root");
session.flash_success(format!("Object model \"{}\" deleted.", om.name));

@ -1,15 +1,15 @@
use actix_session::Session;
use actix_web::{Responder, web};
use serde::{Serialize, Deserialize};
use actix_web::{web, Responder};
use serde::{Deserialize, Serialize};
use yopa::{DataType, ID, TypedValue};
use yopa::model::{PropertyModel, RelationModel};
use yopa::model::PropertyModel;
use yopa::{DataType, TypedValue, ID};
use crate::routes::models::relation::ObjectOrRelationModelDisplay;
use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt;
use crate::utils::{ParseOrBadReq, redirect};
use crate::routes::models::relation::ObjectOrRelationModelDisplay;
use crate::utils::{redirect, ParseOrBadReq};
use crate::TERA;
#[get("/model/property/create/{object_id}")]
pub(crate) async fn create_form(
@ -26,14 +26,17 @@ pub(crate) async fn create_form(
if let Ok(Some(form)) = session.take::<PropertyModelCreateForm>("old") {
context.insert("old", &form);
} else {
context.insert("old", &PropertyModelCreateForm {
object: Default::default(),
name: "".to_string(),
optional: true,
multiple: false,
data_type: DataType::String,
default: "".to_string()
});
context.insert(
"old",
&PropertyModelCreateForm {
object: Default::default(),
name: "".to_string(),
optional: true,
multiple: false,
data_type: DataType::String,
default: "".to_string(),
},
);
}
debug!("ID = {}", object_id);
@ -60,7 +63,7 @@ pub(crate) async fn create_form(
TERA.build_response("models/property_create", &context)
}
#[derive(Serialize,Deserialize)]
#[derive(Serialize, Deserialize)]
pub(crate) struct PropertyModelCreateForm {
pub object: ID,
pub name: String,
@ -74,20 +77,19 @@ pub(crate) struct PropertyModelCreateForm {
pub default: String,
}
fn parse_default(data_type : DataType, default : String) -> Result<TypedValue, String> {
fn parse_default(data_type: DataType, default: String) -> Result<TypedValue, String> {
Ok(match data_type {
DataType::String => {
TypedValue::String(default.into())
}
DataType::String => TypedValue::String(default.into()),
DataType::Integer => {
if default.is_empty() {
TypedValue::Integer(0)
} else {
// TODO better error reporting
TypedValue::Integer(default.parse()
.map_err(|_| {
format!("Error parsing \"{}\" as integer", default)
})?)
TypedValue::Integer(
default
.parse()
.map_err(|_| format!("Error parsing \"{}\" as integer", default))?,
)
}
}
DataType::Decimal => {
@ -95,10 +97,11 @@ fn parse_default(data_type : DataType, default : String) -> Result<TypedValue, S
TypedValue::Decimal(0.0)
} else {
// TODO better error reporting
TypedValue::Decimal(default.parse()
.map_err(|_| {
format!("Error parsing \"{}\" as decimal", default)
})?)
TypedValue::Decimal(
default
.parse()
.map_err(|_| format!("Error parsing \"{}\" as decimal", default))?,
)
}
}
DataType::Boolean => {
@ -106,9 +109,8 @@ fn parse_default(data_type : DataType, default : String) -> Result<TypedValue, S
TypedValue::Boolean(false)
} else {
TypedValue::String(default.clone().into())
.cast_to(DataType::Boolean).map_err(|_| {
format!("Error parsing \"{}\" as boolean", default)
})?
.cast_to(DataType::Boolean)
.map_err(|_| format!("Error parsing \"{}\" as boolean", default))?
}
}
})
@ -131,7 +133,7 @@ pub(crate) async fn create(
Err(msg) => {
warn!("{}", msg);
session.flash_error(msg);
session.set("old", &form);
session.set("old", &form).unwrap();
return redirect(format!("/model/property/create/{}", form.object));
}
};
@ -153,7 +155,7 @@ pub(crate) async fn create(
Err(e) => {
warn!("Error creating property model: {}", e);
session.flash_error(e.to_string());
session.set("old", &form);
session.set("old", &form).unwrap();
redirect(format!("/model/property/create/{}", form.object))
}
}
@ -193,7 +195,6 @@ pub(crate) struct PropertyModelEditForm {
pub default: String,
}
#[get("/model/property/update/{model_id}")]
pub(crate) async fn update_form(
model_id: web::Path<ID>,
@ -204,7 +205,8 @@ pub(crate) async fn update_form(
session.render_flash(&mut context);
let rg = store.read().await;
let model = rg.get_property_model(*model_id)
let model = rg
.get_property_model(*model_id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
// Re-fill old values
@ -239,7 +241,7 @@ pub(crate) async fn update(
Err(msg) => {
warn!("{}", msg);
session.flash_error(msg);
session.set("old", form);
session.set("old", form).unwrap();
return redirect(format!("/model/property/update/{}", id));
}
};
@ -261,7 +263,7 @@ pub(crate) async fn update(
Err(e) => {
warn!("Error updating model: {}", e);
session.flash_error(e.to_string());
session.set("old", form);
session.set("old", form).unwrap();
redirect(format!("/model/relation/update/{}", id))
}
}

@ -1,14 +1,14 @@
use actix_session::Session;
use actix_web::{Responder, web};
use serde::{Serialize, Deserialize};
use actix_web::{web, Responder};
use serde::{Deserialize, Serialize};
use yopa::ID;
use yopa::model::{PropertyModel, RelationModel};
use yopa::ID;
use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt;
use crate::utils::{ParseOrBadReq, redirect};
use crate::utils::{redirect, ParseOrBadReq};
use crate::TERA;
#[derive(Serialize, Debug)]
pub(crate) struct RelationModelDisplay<'a> {
@ -33,17 +33,21 @@ pub(crate) async fn create_form(
if let Ok(Some(form)) = session.take::<RelationModelCreateForm>("old") {
context.insert("old", &form);
} else {
context.insert("old", &RelationModelCreateForm {
object: Default::default(),
name: "".to_string(),
reciprocal_name: "".to_string(),
optional: false,
multiple: false,
related: Default::default()
});
context.insert(
"old",
&RelationModelCreateForm {
object: Default::default(),
name: "".to_string(),
reciprocal_name: "".to_string(),
optional: false,
multiple: false,
related: Default::default(),
},
);
}
let object = rg.get_object_model(object_id.parse_or_bad_request()?)
let object = rg
.get_object_model(object_id.parse_or_bad_request()?)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such source object"))?;
let mut models: Vec<_> = rg.get_object_models().collect();
@ -56,7 +60,7 @@ pub(crate) async fn create_form(
TERA.build_response("models/relation_create", &context)
}
#[derive(Serialize,Deserialize)]
#[derive(Serialize, Deserialize)]
pub(crate) struct RelationModelCreateForm {
pub object: ID,
pub name: String,
@ -93,7 +97,7 @@ pub(crate) async fn create(
Err(e) => {
warn!("Error creating relation model: {}", e);
session.flash_error(e.to_string());
session.set("old", &form);
session.set("old", &form).unwrap();
redirect(format!("/model/relation/create/{}", form.object))
}
}
@ -126,7 +130,7 @@ pub(crate) async fn delete(
}
}
#[derive(Serialize,Deserialize)]
#[derive(Serialize, Deserialize)]
pub(crate) struct RelationModelEditForm {
pub name: String,
pub reciprocal_name: String,
@ -146,7 +150,8 @@ pub(crate) async fn update_form(
session.render_flash(&mut context);
let rg = store.read().await;
let model = rg.get_relation_model(*model_id)
let model = rg
.get_relation_model(*model_id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
// Re-fill old values
@ -181,7 +186,7 @@ pub(crate) async fn update(
reciprocal_name: form.reciprocal_name.to_string(),
optional: form.optional,
multiple: form.multiple,
related: Default::default() // dummy
related: Default::default(), // dummy
}) {
Ok(_id) => {
debug!("Relation updated, redirecting to root");
@ -191,7 +196,7 @@ pub(crate) async fn update(
Err(e) => {
warn!("Error updating model: {}", e);
session.flash_error(e.to_string());
session.set("old", form);
session.set("old", form).unwrap();
redirect(format!("/model/relation/update/{}", id))
}
}

@ -1,21 +1,20 @@
use actix_session::Session;
use actix_web::{Responder, web, HttpResponse};
use crate::session_ext::SessionExt;
use crate::routes::models::object::ObjectModelForm;
use crate::TERA;
use actix_session::Session;
use actix_web::{web, HttpResponse, Responder};
use crate::tera_ext::TeraExt;
use yopa::{ID, model, Storage, data, TypedValue};
use yopa::data::Object;
use serde::{Serialize,Deserialize};
use yopa::insert::{InsertObj, InsertValue};
use crate::utils::redirect;
use actix_web::web::Json;
use serde_json::Value;
use itertools::Itertools;
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
use crate::TERA;
use serde::Serialize;
use yopa::data::Object;
use yopa::insert::InsertObj;
use yopa::{data, model, Storage, ID};
use heck::TitleCase;
use std::collections::HashMap;
use itertools::Itertools;
use json_dotpath::DotPaths;
use std::collections::HashMap;
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
use yopa::update::UpdateObj;
// we only need references here, Context serializes everything to Value.
@ -28,7 +27,7 @@ pub struct Schema<'a> {
pub prop_models: Vec<&'a model::PropertyModel>,
}
#[derive(Serialize,Debug,Clone)]
#[derive(Serialize, Debug, Clone)]
pub struct ObjectCreateData<'a> {
pub model_id: ID,
pub schema: Schema<'a>,
@ -39,14 +38,15 @@ pub struct ObjectCreateData<'a> {
pub(crate) async fn create_form(
model_id: web::Path<ID>,
store: crate::YopaStoreWrapper,
session: Session
session: Session,
) -> actix_web::Result<impl Responder> {
let mut context = tera::Context::new();
session.render_flash(&mut context);
let rg = store.read().await;
let model = rg.get_object_model(*model_id)
let model = rg
.get_object_model(*model_id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
context.insert("model", model);
@ -58,19 +58,20 @@ pub(crate) async fn create_form(
TERA.build_response("objects/object_create", &context)
}
fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result<ObjectCreateData> {
let model = rg.get_object_model(model_id)
fn prepare_object_create_data(rg: &Storage, model_id: ID) -> actix_web::Result<ObjectCreateData> {
let model = rg
.get_object_model(model_id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
let relations : Vec<_> = rg.get_relation_models_for_object_model(model.id).collect();
let relations: Vec<_> = rg.get_relation_models_for_object_model(model.id).collect();
let mut prop_object_ids : Vec<ID> = relations.iter().map(|r| r.id).collect();
let mut prop_object_ids: Vec<ID> = relations.iter().map(|r| r.id).collect();
prop_object_ids.push(model.id);
prop_object_ids.sort();
prop_object_ids.dedup();
let mut related_ids : Vec<_> = relations.iter().map(|r| r.related).collect();
let mut related_ids: Vec<_> = relations.iter().map(|r| r.related).collect();
related_ids.sort();
related_ids.dedup();
@ -80,9 +81,11 @@ fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result
schema: Schema {
obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here
rel_models: relations,
prop_models: rg.get_property_models_for_parents(prop_object_ids).collect()
prop_models: rg
.get_property_models_for_parents(prop_object_ids)
.collect(),
},
objects: rg.get_objects_of_type(related_ids).collect()
objects: rg.get_objects_of_type(related_ids).collect(),
})
}
@ -120,30 +123,35 @@ pub(crate) async fn create(
#[derive(Debug, Serialize, Clone)]
struct ModelWithObjects<'a> {
model : &'a ObjectModel,
objects: Vec<&'a Object>
model: &'a ObjectModel,
objects: Vec<&'a Object>,
}
#[get("/objects")]
pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> {
pub(crate) async fn list(
session: Session,
store: crate::YopaStoreWrapper,
) -> actix_web::Result<impl Responder> {
list_inner(session, store).await
}
pub(crate) async fn list_inner(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<HttpResponse> {
pub(crate) async fn list_inner(
session: Session,
store: crate::YopaStoreWrapper,
) -> actix_web::Result<HttpResponse> {
let rg = store.read().await;
let mut objects_by_model = rg.get_grouped_objects();
let mut models : Vec<_> = rg.get_object_models()
let models: Vec<_> = rg
.get_object_models()
.sorted_by_key(|m| &m.name)
.map(|model| {
let mut objects = objects_by_model.remove(&model.id).unwrap_or_default();
objects.sort_by_key(|o| &o.name);
ModelWithObjects {
model,
objects
}
}).collect();
ModelWithObjects { model, objects }
})
.collect();
let mut ctx = tera::Context::new();
ctx.insert("models", &models);
@ -152,31 +160,30 @@ pub(crate) async fn list_inner(session: Session, store: crate::YopaStoreWrapper)
TERA.build_response("objects/index", &ctx)
}
#[derive(Debug,Serialize)]
#[derive(Debug, Serialize)]
struct PropertyView<'a> {
model : &'a PropertyModel,
values: Vec<&'a yopa::data::Value>
model: &'a PropertyModel,
values: Vec<&'a yopa::data::Value>,
}
#[derive(Debug,Serialize)]
#[derive(Debug, Serialize)]
struct RelationView<'a> {
model : &'a RelationModel,
model: &'a RelationModel,
related_name: &'a str,
instances: Vec<RelationInstanceView<'a>>
instances: Vec<RelationInstanceView<'a>>,
}
#[derive(Debug,Serialize)]
#[derive(Debug, Serialize)]
struct RelationInstanceView<'a> {
related: &'a Object,
properties: Vec<PropertyView<'a>>
properties: Vec<PropertyView<'a>>,
}
#[get("/object/detail/{id}")]
pub(crate) async fn detail(
id: web::Path<ID>,
store: crate::YopaStoreWrapper,
session: Session
session: Session,
) -> actix_web::Result<impl Responder> {
let object_id = *id;
@ -185,18 +192,22 @@ pub(crate) async fn detail(
let rg = store.read().await;
let object = rg.get_object(object_id)
let object = rg
.get_object(object_id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
let model = rg.get_object_model(object.model)
let model = rg
.get_object_model(object.model)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
context.insert("object", object);
context.insert("model", model);
context.insert("kind", &rg.get_model_name(object.model));
let mut relations = rg.get_relations_for_object(object_id).collect_vec();
let reci_relations = rg.get_reciprocal_relations_for_object(object_id).collect_vec();
let relations = rg.get_relations_for_object(object_id).collect_vec();
let reci_relations = rg
.get_reciprocal_relations_for_object(object_id)
.collect_vec();
// values by parent ID
let mut ids_to_get_values_for = relations.iter().map(|r| r.id).collect_vec();
@ -207,15 +218,17 @@ pub(crate) async fn detail(
// object's own properties
{
let mut object_values_by_model = grouped_values
.remove(&object_id).unwrap_or_default().into_iter()
let object_values_by_model = grouped_values
.remove(&object_id)
.unwrap_or_default()
.into_iter()
.into_group_map_by(|value| value.model);
let mut view_object_properties = vec![];
for (prop_model_id, values) in object_values_by_model {
view_object_properties.push(PropertyView {
model: rg.get_property_model(prop_model_id).unwrap(),
values
values,
})
}
@ -226,7 +239,8 @@ pub(crate) async fn detail(
// go through relations
{
let grouped_relations = relations.iter()
let grouped_relations = relations
.iter()
.into_group_map_by(|relation| relation.model);
let mut relation_views = vec![];
@ -236,18 +250,20 @@ pub(crate) async fn detail(
for rel in relations {
let related_obj = match rg.get_object(rel.related) {
None => continue,
Some(obj) => obj
Some(obj) => obj,
};
let mut rel_values_by_model = grouped_values
.remove(&rel.id).unwrap_or_default().into_iter()
let rel_values_by_model = grouped_values
.remove(&rel.id)
.unwrap_or_default()
.into_iter()
.into_group_map_by(|value| value.model);
let mut view_rel_properties = vec![];
for (prop_model_id, values) in rel_values_by_model {
view_rel_properties.push(PropertyView {
model: rg.get_property_model(prop_model_id).unwrap(),
values
values,
})
}
@ -255,7 +271,7 @@ pub(crate) async fn detail(
instances.push(RelationInstanceView {
related: related_obj,
properties: view_rel_properties
properties: view_rel_properties,
})
}
@ -264,7 +280,7 @@ pub(crate) async fn detail(
relation_views.push(RelationView {
model: rg.get_relation_model(model_id).unwrap(),
related_name: rg.get_model_name(model_id),
instances
instances,
})
}
@ -275,7 +291,8 @@ pub(crate) async fn detail(
// near-copypasta for reciprocal
{
let grouped_relations = reci_relations.iter()
let grouped_relations = reci_relations
.iter()
.into_group_map_by(|relation| relation.model);
let mut relation_views = vec![];
@ -285,18 +302,20 @@ pub(crate) async fn detail(
for rel in relations {
let related_obj = match rg.get_object(rel.object) {
None => continue,
Some(obj) => obj
Some(obj) => obj,
};
let mut rel_values_by_model = grouped_values
.remove(&rel.id).unwrap_or_default().into_iter()
let rel_values_by_model = grouped_values
.remove(&rel.id)
.unwrap_or_default()
.into_iter()
.into_group_map_by(|value| value.model);
let mut view_rel_properties = vec![];
for (prop_model_id, values) in rel_values_by_model {
view_rel_properties.push(PropertyView {
model: rg.get_property_model(prop_model_id).unwrap(),
values
values,
})
}
@ -304,7 +323,7 @@ pub(crate) async fn detail(
instances.push(RelationInstanceView {
related: related_obj,
properties: view_rel_properties
properties: view_rel_properties,
})
}
@ -313,7 +332,7 @@ pub(crate) async fn detail(
relation_views.push(RelationView {
model: rg.get_relation_model(model_id).unwrap(),
related_name: rg.get_model_name(model_id),
instances
instances,
})
}
@ -325,16 +344,19 @@ pub(crate) async fn detail(
TERA.build_response("objects/object_detail", &context)
}
#[derive(Serialize,Debug,Clone)]
#[derive(Serialize, Debug, Clone)]
struct EnrichedObject<'a> {
id: ID,
model: ID,
name: String,
values: HashMap<String /* ID but as string so serde will stop exploding */, Vec<&'a data::Value>>,
values: HashMap<
String, /* ID but as string so serde will stop exploding */
Vec<&'a data::Value>,
>,
relations: HashMap<String /* ID */, Vec<EnrichedRelation<'a>>>,
}
#[derive(Serialize,Debug,Clone)]
#[derive(Serialize, Debug, Clone)]
struct EnrichedRelation<'a> {
id: ID,
object: ID,
@ -347,17 +369,19 @@ struct EnrichedRelation<'a> {
pub(crate) async fn update_form(
id: web::Path<ID>,
store: crate::YopaStoreWrapper,
session: Session
session: Session,
) -> actix_web::Result<impl Responder> {
let mut context = tera::Context::new();
session.render_flash(&mut context);
let rg = store.read().await;
let object = rg.get_object(*id)
let object = rg
.get_object(*id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
let model = rg.get_object_model(object.model)
let model = rg
.get_object_model(object.model)
.ok_or_else(|| actix_web::error::ErrorNotFound("Object has no model"))?;
// maybe its useful,idk
@ -370,47 +394,71 @@ pub(crate) async fn update_form(
let mut relation_map = HashMap::new();
// Some properties may have no values, so we first check what IDs to expect
let prop_ids = create_data.schema.prop_models.iter()
.filter(|p| p.object == model.id).map(|p| p.id).collect_vec();
let prop_ids = create_data
.schema
.prop_models
.iter()
.filter(|p| p.object == model.id)
.map(|p| p.id)
.collect_vec();
let mut values_grouped = rg.get_values_for_object(*id)
.into_group_map_by(|v| v.model);
let mut values_grouped = rg.get_values_for_object(*id).into_group_map_by(|v| v.model);
prop_ids.into_iter().for_each(|id| {
value_map.insert(id.to_string(), values_grouped.remove(&id).unwrap_or_default());
value_map.insert(
id.to_string(),
values_grouped.remove(&id).unwrap_or_default(),
);
});
{
// Some properties may have no values, so we first check what IDs to expect
let relation_model_ids = create_data.schema.rel_models.iter()
let relation_model_ids = create_data
.schema
.rel_models
.iter()
.filter(|p| p.object == model.id)
.map(|p| p.id).collect_vec();
.map(|p| p.id)
.collect_vec();
let relations = rg.get_relations_for_object(*id).collect_vec();
let relation_ids = relations.iter().map(|r| r.id).collect_vec();
let mut relations_grouped_by_model = relations.iter()
let mut relations_grouped_by_model = relations
.iter()
.into_group_map_by(|relation| relation.model);
let mut property_models_grouped_by_parent = rg.get_grouped_prop_models_for_parents(relation_model_ids.clone());
let mut property_models_grouped_by_parent =
rg.get_grouped_prop_models_for_parents(relation_model_ids.clone());
let mut relation_values_grouped_by_instance = rg.get_grouped_values_for_objects(relation_ids);
let mut relation_values_grouped_by_instance =
rg.get_grouped_values_for_objects(relation_ids);
for rel_model_id in relation_model_ids {
let relations = relations_grouped_by_model.remove(&rel_model_id).unwrap_or_default();
let relations = relations_grouped_by_model
.remove(&rel_model_id)
.unwrap_or_default();
let mut instances = vec![];
let prop_models_for_relation = property_models_grouped_by_parent.remove(&rel_model_id).unwrap_or_default();
let prop_models_for_relation = property_models_grouped_by_parent
.remove(&rel_model_id)
.unwrap_or_default();
for rel in relations {
let mut relation_values_map = HashMap::new();
// values keyed by model
let mut rel_values = relation_values_grouped_by_instance.remove(&rel.id).unwrap_or_default().into_iter()
let mut rel_values = relation_values_grouped_by_instance
.remove(&rel.id)
.unwrap_or_default()
.into_iter()
.into_group_map_by(|relation| relation.model);
prop_models_for_relation.iter().for_each(|prop_model| {
relation_values_map.insert(prop_model.id.to_string(), rel_values.remove(&prop_model.id).unwrap_or_default());
relation_values_map.insert(
prop_model.id.to_string(),
rel_values.remove(&prop_model.id).unwrap_or_default(),
);
});
instances.push(EnrichedRelation {
@ -418,7 +466,7 @@ pub(crate) async fn update_form(
object: rel.object,
model: rel.model,
related: rel.related,
values: relation_values_map
values: relation_values_map,
});
}
@ -433,7 +481,7 @@ pub(crate) async fn update_form(
model: object.model,
name: object.name.clone(),
values: value_map,
relations: relation_map
relations: relation_map,
};
form.dot_remove("model_id").unwrap();
@ -443,10 +491,9 @@ pub(crate) async fn update_form(
TERA.build_response("objects/object_update", &context)
}
#[post("/object/update/{id}")]
pub(crate) async fn update(
id: web::Path<ID>,
_id: web::Path<ID>,
form: web::Json<UpdateObj>,
store: crate::YopaStoreWrapper,
session: Session,
@ -457,7 +504,8 @@ pub(crate) async fn update(
let form = form.into_inner();
let name = form.name.clone();
let object = wg.get_object(form.id)
let object = wg
.get_object(form.id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
let model_name = wg.get_model_name(object.model).to_owned().to_title_case();

@ -3,9 +3,20 @@ use actix_web::HttpResponse;
use include_dir::Dir;
use tera::Tera;
fn tera_includedir_walk_folder_inner(collected: &mut Vec<(String, String)>, tera: &mut Tera, dir: &Dir) {
fn tera_includedir_walk_folder_inner(
collected: &mut Vec<(String, String)>,
tera: &mut Tera,
dir: &Dir,
) {
for f in dir.files() {
let halves: Vec<_> = f.path().file_name().unwrap().to_str().unwrap().split('.').collect();
let halves: Vec<_> = f
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.split('.')
.collect();
if halves.last().unwrap() != &"tera" {
debug!("Bad file: {:?}", f);
@ -30,11 +41,20 @@ fn tera_includedir_walk_folder_inner(collected: &mut Vec<(String, String)>, tera
pub(crate) trait TeraExt {
fn add_include_dir_templates(&mut self, dir: &Dir) -> tera::Result<()>;
fn build_response(&self, template: &str, context: &tera::Context) -> actix_web::Result<HttpResponse> {
fn build_response(
&self,
template: &str,
context: &tera::Context,
) -> actix_web::Result<HttpResponse> {
self.build_err_response(StatusCode::OK, template, context)
}
fn build_err_response(&self, code: StatusCode, template: &str, context: &tera::Context) -> actix_web::Result<HttpResponse>;
fn build_err_response(
&self,
code: StatusCode,
template: &str,
context: &tera::Context,
) -> actix_web::Result<HttpResponse>;
}
impl TeraExt for Tera {
@ -45,10 +65,15 @@ impl TeraExt for Tera {
self.add_raw_templates(templates)
}
fn build_err_response(&self, code: StatusCode, template: &str, context: &tera::Context) -> actix_web::Result<HttpResponse> {
let html = self.render(template, context).map_err(|e| {
actix_web::error::ErrorInternalServerError(e)
})?;
fn build_err_response(
&self,
code: StatusCode,
template: &str,
context: &tera::Context,
) -> actix_web::Result<HttpResponse> {
let html = self
.render(template, context)
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
Ok(HttpResponse::build(code).body(html))
}

@ -1,7 +1,7 @@
use actix_web::http::header::IntoHeaderValue;
use actix_web::HttpResponse;
use std::fmt::{Debug, Display};
use std::str::FromStr;
use std::fmt::{Display, Debug};
pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result<HttpResponse> {
Ok(HttpResponse::SeeOther()
@ -11,29 +11,30 @@ pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result<HttpResponse> {
pub trait ParseOrBadReq {
fn parse_or_bad_request<T, E>(&self) -> actix_web::Result<T>
where T: FromStr<Err=E>,
E: Display + Debug + 'static;
where
T: FromStr<Err = E>,
E: Display + Debug + 'static;
}
impl ParseOrBadReq for &str {
fn parse_or_bad_request<T, E>(&self) -> actix_web::Result<T>
where T: FromStr<Err=E>,
E: Display + Debug + 'static
where
T: FromStr<Err = E>,
E: Display + Debug + 'static,
{
self.parse::<T>()
.map_err(|e| {
error!("Parse error for \"{}\"", self);
actix_web::error::ErrorBadRequest(e)
})
self.parse::<T>().map_err(|e| {
error!("Parse error for \"{}\"", self);
actix_web::error::ErrorBadRequest(e)
})
}
}
impl ParseOrBadReq for String {
fn parse_or_bad_request<T, E>(&self) -> actix_web::Result<T>
where T: FromStr<Err=E>,
E: Display + Debug + 'static
where
T: FromStr<Err = E>,
E: Display + Debug + 'static,
{
self.as_str()
.parse_or_bad_request()
self.as_str().parse_or_bad_request()
}
}

@ -4,7 +4,10 @@ use std::collections::HashMap;
use std::hash::Hash;
/// drain_filter() implemented for HashMap. It returns the removed items as a Vec
pub fn map_drain_filter<K: Eq + Hash, V>(map: &mut HashMap<K, V>, filter: impl Fn(&K, &V) -> bool) -> Vec<(K, V)> {
pub fn map_drain_filter<K: Eq + Hash, V>(
map: &mut HashMap<K, V>,
filter: impl Fn(&K, &V) -> bool,
) -> Vec<(K, V)> {
let mut removed = vec![];
let mut retain = vec![];
for (k, v) in map.drain() {

@ -4,11 +4,11 @@ use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use crate::ID;
use crate::model::DataType;
use crate::id::HaveId;
use std::fmt::{Display, Formatter};
use crate::model::DataType;
use crate::ID;
use std::fmt;
use std::fmt::{Display, Formatter};
/// Value of a particular type
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -40,43 +40,41 @@ impl TypedValue {
match (self, ty) {
// to string
(s @ TypedValue::String(_), DataType::String) => Ok(s),
(TypedValue::Integer(i), DataType::String) => Ok(TypedValue::String(i.to_string().into())),
(TypedValue::Decimal(f), DataType::String) => Ok(TypedValue::String(f.to_string().into())),
(TypedValue::Boolean(b), DataType::String) => Ok(TypedValue::String(Cow::Borrowed(if b { "1" } else { "0" }))),
// to int
(TypedValue::String(s), DataType::Integer) => {
match s.parse::<i64>() {
Ok(i) => Ok(TypedValue::Integer(i)),
Err(_) => Err(TypedValue::String(s))
}
(TypedValue::Integer(i), DataType::String) => {
Ok(TypedValue::String(i.to_string().into()))
}
(TypedValue::Decimal(f), DataType::String) => {
Ok(TypedValue::String(f.to_string().into()))
}
(TypedValue::Boolean(b), DataType::String) => {
Ok(TypedValue::String(Cow::Borrowed(if b { "1" } else { "0" })))
}
// to int
(TypedValue::String(s), DataType::Integer) => match s.parse::<i64>() {
Ok(i) => Ok(TypedValue::Integer(i)),
Err(_) => Err(TypedValue::String(s)),
},
(s @ TypedValue::Integer(_), DataType::Integer) => Ok(s),
(TypedValue::Decimal(f), DataType::Integer) => Ok(TypedValue::Integer(f.round() as i64)),
(TypedValue::Boolean(b), DataType::Integer) => Ok(TypedValue::Integer(if b { 1 } else { 0 })),
// to float
(TypedValue::String(s), DataType::Decimal) => {
match s.parse::<f64>() {
Ok(i) => Ok(TypedValue::Decimal(i)),
Err(_) => Err(TypedValue::String(s))
}
(TypedValue::Decimal(f), DataType::Integer) => {
Ok(TypedValue::Integer(f.round() as i64))
}
(TypedValue::Boolean(b), DataType::Integer) => {
Ok(TypedValue::Integer(if b { 1 } else { 0 }))
}
// to float
(TypedValue::String(s), DataType::Decimal) => match s.parse::<f64>() {
Ok(i) => Ok(TypedValue::Decimal(i)),
Err(_) => Err(TypedValue::String(s)),
},
(TypedValue::Integer(i), DataType::Decimal) => Ok(TypedValue::Decimal(i as f64)),
(d @ TypedValue::Decimal(_), DataType::Decimal) => Ok(d),
(e @ TypedValue::Boolean(_), DataType::Decimal) => Err(e),
// to bool
(TypedValue::String(s), DataType::Boolean) => {
match &(&s).to_ascii_lowercase()[..] {
"y" | "yes" | "true" | "1" => {
Ok(TypedValue::Boolean(true))
}
"n" | "no" | "false" | "0" => {
Ok(TypedValue::Boolean(false))
}
_ => {
Err(TypedValue::String(s))
}
}
}
(TypedValue::String(s), DataType::Boolean) => match &(&s).to_ascii_lowercase()[..] {
"y" | "yes" | "true" | "1" => Ok(TypedValue::Boolean(true)),
"n" | "no" | "false" | "0" => Ok(TypedValue::Boolean(false)),
_ => Err(TypedValue::String(s)),
},
(TypedValue::Integer(i), DataType::Boolean) => Ok(TypedValue::Boolean(i != 0)),
(e @ TypedValue::Decimal(_), DataType::Boolean) => Err(e),
(b @ TypedValue::Boolean(_), DataType::Boolean) => Ok(b),
@ -93,78 +91,213 @@ mod tests {
#[test]
fn test_cast_to_bool() {
// Cast to bool
assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::Boolean(true).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::Boolean(false).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::Integer(123).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::Integer(0).cast_to(DataType::Boolean));
assert_eq!(Err(TypedValue::Decimal(0.0)), TypedValue::Decimal(0.0).cast_to(DataType::Boolean));
assert_eq!(Err(TypedValue::Decimal(123.0)), TypedValue::Decimal(123.0).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("true".into()).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("1".into()).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("y".into()).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("yes".into()).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("false".into()).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("0".into()).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("n".into()).cast_to(DataType::Boolean));
assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("no".into()).cast_to(DataType::Boolean));
assert_eq!(Err(TypedValue::String("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::Boolean));
assert_eq!(
Ok(TypedValue::Boolean(true)),
TypedValue::Boolean(true).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(false)),
TypedValue::Boolean(false).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(true)),
TypedValue::Integer(123).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(false)),
TypedValue::Integer(0).cast_to(DataType::Boolean)
);
assert_eq!(
Err(TypedValue::Decimal(0.0)),
TypedValue::Decimal(0.0).cast_to(DataType::Boolean)
);
assert_eq!(
Err(TypedValue::Decimal(123.0)),
TypedValue::Decimal(123.0).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(true)),
TypedValue::String("true".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(true)),
TypedValue::String("1".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(true)),
TypedValue::String("y".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(true)),
TypedValue::String("yes".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(false)),
TypedValue::String("false".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(false)),
TypedValue::String("0".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(false)),
TypedValue::String("n".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Ok(TypedValue::Boolean(false)),
TypedValue::String("no".into()).cast_to(DataType::Boolean)
);
assert_eq!(
Err(TypedValue::String("blorg".into())),
TypedValue::String("blorg".into()).cast_to(DataType::Boolean)
);
}
#[test]
fn test_cast_to_int() {
// Cast to bool
assert_eq!(Ok(TypedValue::Integer(1)), TypedValue::Boolean(true).cast_to(DataType::Integer));
assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Boolean(false).cast_to(DataType::Integer));
assert_eq!(
Ok(TypedValue::Integer(1)),
TypedValue::Boolean(true).cast_to(DataType::Integer)
);
assert_eq!(
Ok(TypedValue::Integer(0)),
TypedValue::Boolean(false).cast_to(DataType::Integer)
);
assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::Integer(123).cast_to(DataType::Integer));
assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Integer(0).cast_to(DataType::Integer));
assert_eq!(
Ok(TypedValue::Integer(123)),
TypedValue::Integer(123).cast_to(DataType::Integer)
);
assert_eq!(
Ok(TypedValue::Integer(0)),
TypedValue::Integer(0).cast_to(DataType::Integer)
);
assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Decimal(0.0).cast_to(DataType::Integer));
assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::Decimal(123.0).cast_to(DataType::Integer));
assert_eq!(Ok(TypedValue::Integer(-124)), TypedValue::Decimal(-123.7).cast_to(DataType::Integer));
assert_eq!(
Ok(TypedValue::Integer(0)),
TypedValue::Decimal(0.0).cast_to(DataType::Integer)
);
assert_eq!(
Ok(TypedValue::Integer(123)),
TypedValue::Decimal(123.0).cast_to(DataType::Integer)
);
assert_eq!(
Ok(TypedValue::Integer(-124)),
TypedValue::Decimal(-123.7).cast_to(DataType::Integer)
);
assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::String("123".into()).cast_to(DataType::Integer));
assert_eq!(Ok(TypedValue::Integer(-123)), TypedValue::String("-123".into()).cast_to(DataType::Integer));
assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::String("0".into()).cast_to(DataType::Integer));
assert_eq!(Err(TypedValue::String("123.456".into())), TypedValue::String("123.456".into()).cast_to(DataType::Integer));
assert_eq!(Err(TypedValue::String("-123.456".into())), TypedValue::String("-123.456".into()).cast_to(DataType::Integer));
assert_eq!(
Ok(TypedValue::Integer(123)),
TypedValue::String("123".into()).cast_to(DataType::Integer)
);
assert_eq!(
Ok(TypedValue::Integer(-123)),
TypedValue::String("-123".into()).cast_to(DataType::Integer)
);
assert_eq!(
Ok(TypedValue::Integer(0)),
TypedValue::String("0".into()).cast_to(DataType::Integer)
);
assert_eq!(
Err(TypedValue::String("123.456".into())),
TypedValue::String("123.456".into()).cast_to(DataType::Integer)
);
assert_eq!(
Err(TypedValue::String("-123.456".into())),
TypedValue::String("-123.456".into()).cast_to(DataType::Integer)
);
}
#[test]
fn test_cast_to_decimal() {
// Cast to bool
assert_eq!(Err(TypedValue::Boolean(true)), TypedValue::Boolean(true).cast_to(DataType::Decimal));
assert_eq!(Err(TypedValue::Boolean(false)), TypedValue::Boolean(false).cast_to(DataType::Decimal));
assert_eq!(
Err(TypedValue::Boolean(true)),
TypedValue::Boolean(true).cast_to(DataType::Decimal)
);
assert_eq!(
Err(TypedValue::Boolean(false)),
TypedValue::Boolean(false).cast_to(DataType::Decimal)
);
assert_eq!(Ok(TypedValue::Decimal(123.0)), TypedValue::Integer(123).cast_to(DataType::Decimal));
assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::Integer(0).cast_to(DataType::Decimal));
assert_eq!(
Ok(TypedValue::Decimal(123.0)),
TypedValue::Integer(123).cast_to(DataType::Decimal)
);
assert_eq!(
Ok(TypedValue::Decimal(0.0)),
TypedValue::Integer(0).cast_to(DataType::Decimal)
);
assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::Decimal(0.0).cast_to(DataType::Decimal));
assert_eq!(Ok(TypedValue::Decimal(-123.7)), TypedValue::Decimal(-123.7).cast_to(DataType::Decimal));
assert_eq!(
Ok(TypedValue::Decimal(0.0)),
TypedValue::Decimal(0.0).cast_to(DataType::Decimal)
);
assert_eq!(
Ok(TypedValue::Decimal(-123.7)),
TypedValue::Decimal(-123.7).cast_to(DataType::Decimal)
);
assert_eq!(Ok(TypedValue::Decimal(123.0)), TypedValue::String("123".into()).cast_to(DataType::Decimal));
assert_eq!(Ok(TypedValue::Decimal(-123.0)), TypedValue::String("-123".into()).cast_to(DataType::Decimal));
assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::String("0".into()).cast_to(DataType::Decimal));
assert_eq!(Ok(TypedValue::Decimal(-123.456)), TypedValue::String("-123.456".into()).cast_to(DataType::Decimal));
assert_eq!(
Ok(TypedValue::Decimal(123.0)),
TypedValue::String("123".into()).cast_to(DataType::Decimal)
);
assert_eq!(
Ok(TypedValue::Decimal(-123.0)),
TypedValue::String("-123".into()).cast_to(DataType::Decimal)
);
assert_eq!(
Ok(TypedValue::Decimal(0.0)),
TypedValue::String("0".into()).cast_to(DataType::Decimal)
);
assert_eq!(
Ok(TypedValue::Decimal(-123.456)),
TypedValue::String("-123.456".into()).cast_to(DataType::Decimal)
);
}
#[test]
fn test_cast_to_string() {
// Cast to bool
assert_eq!(Ok(TypedValue::String("1".into())), TypedValue::Boolean(true).cast_to(DataType::String));
assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Boolean(false).cast_to(DataType::String));
assert_eq!(
Ok(TypedValue::String("1".into())),
TypedValue::Boolean(true).cast_to(DataType::String)
);
assert_eq!(
Ok(TypedValue::String("0".into())),
TypedValue::Boolean(false).cast_to(DataType::String)
);
assert_eq!(Ok(TypedValue::String("123".into())), TypedValue::Integer(123).cast_to(DataType::String));
assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Integer(0).cast_to(DataType::String));
assert_eq!(
Ok(TypedValue::String("123".into())),
TypedValue::Integer(123).cast_to(DataType::String)
);
assert_eq!(
Ok(TypedValue::String("0".into())),
TypedValue::Integer(0).cast_to(DataType::String)
);
assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Decimal(0.0).cast_to(DataType::String));
assert_eq!(Ok(TypedValue::String("123".into())), TypedValue::Decimal(123.0).cast_to(DataType::String));
assert_eq!(Ok(TypedValue::String("-123.5".into())), TypedValue::Decimal(-123.5).cast_to(DataType::String));
assert_eq!(
Ok(TypedValue::String("0".into())),
TypedValue::Decimal(0.0).cast_to(DataType::String)
);
assert_eq!(
Ok(TypedValue::String("123".into())),
TypedValue::Decimal(123.0).cast_to(DataType::String)
);
assert_eq!(
Ok(TypedValue::String("-123.5".into())),
TypedValue::Decimal(-123.5).cast_to(DataType::String)
);
assert_eq!(Ok(TypedValue::String("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::String));
assert_eq!(
Ok(TypedValue::String("blorg".into())),
TypedValue::String("blorg".into()).cast_to(DataType::String)
);
}
}
@ -177,7 +310,7 @@ pub struct Object {
/// Object template ID
pub model: ID,
/// Model name, mainly shown in lists
pub name : String,
pub name: String,
}
/// Relation between two objects

@ -42,10 +42,10 @@ mod impl_u64 {
}
}
#[cfg(feature = "uuid-ids")]
pub use impl_uuid::{ID, next_id, zero_id};
#[cfg(not(feature = "uuid-ids"))]
pub use impl_u64::{ID, next_id, zero_id};
pub use impl_u64::{next_id, zero_id, ID};
#[cfg(feature = "uuid-ids")]
pub use impl_uuid::{next_id, zero_id, ID};
/// Something that has ID
pub trait HaveId {

@ -43,13 +43,18 @@ impl InsertRel {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InsertObj {
pub model: ID,
pub name : String,
pub name: String,
pub values: Vec<InsertValue>,
pub relations: Vec<InsertRel>,
}
impl InsertObj {
pub fn new(model_id: ID, name : String, values: Vec<InsertValue>, relations: Vec<InsertRel>) -> Self {
pub fn new(
model_id: ID,
name: String,
values: Vec<InsertValue>,
relations: Vec<InsertRel>,
) -> Self {
Self {
model: model_id,
name,
@ -58,4 +63,3 @@ impl InsertObj {
}
}
}

@ -1,36 +1,38 @@
#[macro_use] extern crate serde_json;
#[macro_use] extern crate log;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate log;
use std::borrow::Cow;
use std::collections::{HashMap};
use std::collections::HashMap;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use cool::{KVVecToKeysOrValues, map_drain_filter};
pub use id::ID;
use crate::model::{PropertyModel, RelationModel};
use cool::{map_drain_filter, KVVecToKeysOrValues};
use id::next_id;
pub use id::ID;
use insert::InsertObj;
use insert::InsertValue;
use model::ObjectModel;
use crate::model::{PropertyModel, RelationModel};
pub use data::{TypedValue};
pub use model::{DataType};
use crate::data::{Object};
use crate::data::Object;
use crate::update::{UpdateObj, UpsertValue};
pub use data::TypedValue;
pub use model::DataType;
pub mod model;
mod cool;
pub mod data;
pub mod id;
pub mod insert;
pub mod model;
pub mod update;
pub mod id;
mod cool;
mod serde_map_as_list;
#[cfg(test)]
mod tests;
mod serde_map_as_list;
/// Stupid storage with no persistence
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
@ -50,7 +52,6 @@ pub struct Storage {
values: HashMap<ID, data::Value>,
}
#[derive(Debug, Error)]
pub enum StorageError {
#[error("Referenced {0} does not exist")]
@ -68,11 +69,20 @@ impl Storage {
/// Define a data object
pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> {
if tpl.name.is_empty() {
return Err(StorageError::ConstraintViolation("Name must not be empty".into()));
return Err(StorageError::ConstraintViolation(
"Name must not be empty".into(),
));
}
if self.obj_models.iter().find(|(_, t)| t.name == tpl.name).is_some() {
return Err(StorageError::ConstraintViolation(format!("Object model with the name \"{}\" already exists", tpl.name).into()));
if self
.obj_models
.iter()
.find(|(_, t)| t.name == tpl.name)
.is_some()
{
return Err(StorageError::ConstraintViolation(
format!("Object model with the name \"{}\" already exists", tpl.name).into(),
));
}
debug!("Define object model \"{}\"", tpl.name);
@ -85,31 +95,45 @@ impl Storage {
/// Define a relation between two data objects
pub fn define_relation(&mut self, mut rel: model::RelationModel) -> Result<ID, StorageError> {
if rel.name.is_empty() || rel.reciprocal_name.is_empty() {
return Err(StorageError::ConstraintViolation("Names must not be empty".into()));
return Err(StorageError::ConstraintViolation(
"Names must not be empty".into(),
));
}
if !self.obj_models.contains_key(&rel.object) {
return Err(StorageError::NotExist(format!("Source object model {}", rel.object).into()));
return Err(StorageError::NotExist(
format!("Source object model {}", rel.object).into(),
));
}
if !self.obj_models.contains_key(&rel.related) {
return Err(StorageError::NotExist(format!("Related object model {}", rel.related).into()));
return Err(StorageError::NotExist(
format!("Related object model {}", rel.related).into(),
));
}
if let Some((_, colliding)) = self.rel_models.iter().find(|(_, other)| {
(other.name == rel.name && other.object == rel.object) // Exact match
|| (other.name == rel.reciprocal_name && other.object == rel.related) // Our reciprocal name collides with related's own relation name
|| (other.reciprocal_name == rel.name && other.related == rel.object) // Our name name collides with a reciprocal name on the other relation
|| (other.reciprocal_name == rel.reciprocal_name && other.related == rel.related) // Reciprocal names collide for the same destination
|| (other.reciprocal_name == rel.reciprocal_name && other.related == rel.related)
// Reciprocal names collide for the same destination
}) {
return Err(StorageError::ConstraintViolation(
format!("Name collision (\"{}\" / \"{}\") with existing relation (\"{}\" / \"{}\")",
rel.name, rel.reciprocal_name,
colliding.name, colliding.reciprocal_name
).into()));
format!(
"Name collision (\"{}\" / \"{}\") with existing relation (\"{}\" / \"{}\")",
rel.name, rel.reciprocal_name, colliding.name, colliding.reciprocal_name
)
.into(),
));
}
debug!("Define relation model \"{}\" from {} to {}, reciprocal name \"{}\"",
rel.name, self.describe_model(rel.object), self.describe_model(rel.related), rel.reciprocal_name);
debug!(
"Define relation model \"{}\" from {} to {}, reciprocal name \"{}\"",
rel.name,
self.describe_model(rel.object),
self.describe_model(rel.related),
rel.reciprocal_name
);
let id = next_id();
rel.id = id;
@ -120,28 +144,51 @@ impl Storage {
/// Define a property attached to an object or a relation
pub fn define_property(&mut self, mut prop: model::PropertyModel) -> Result<ID, StorageError> {
if prop.name.is_empty() {
return Err(StorageError::ConstraintViolation("Name must not be empty".into()));
return Err(StorageError::ConstraintViolation(
"Name must not be empty".into(),
));
}
if !self.obj_models.contains_key(&prop.object) {
// Maybe it's attached to a relation?
if !self.rel_models.contains_key(&prop.object) {
return Err(StorageError::NotExist(format!("Object or relation model {}", prop.object).into()));
return Err(StorageError::NotExist(
format!("Object or relation model {}", prop.object).into(),
));
}
}
if self.prop_models.iter().find(|(_, t)| t.object == prop.object && 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, self.describe_model(prop.object)).into()));
format!(
"Property with the name \"{}\" already exists on model {}",
prop.name,
self.describe_model(prop.object)
)
.into(),
));
}
// Ensure the default type is compatible
prop.default = match prop.default.clone().cast_to(prop.data_type) {
Ok(v) => v,
Err(_) => return Err(StorageError::NotExist(format!("default value {:?} has invalid type", prop.default).into()))
Err(_) => {
return Err(StorageError::NotExist(
format!("default value {:?} has invalid type", prop.default).into(),
))
}
};
debug!("Define property model \"{}\" of {}", prop.name, self.describe_model(prop.object));
debug!(
"Define property model \"{}\" of {}",
prop.name,
self.describe_model(prop.object)
);
let id = next_id();
prop.id = id;
self.prop_models.insert(id, prop);
@ -153,13 +200,17 @@ impl Storage {
return if let Some(t) = self.obj_models.remove(&id) {
debug!("Undefine object model \"{}\"", t.name);
// Remove relation templates
let removed_relation_ids = map_drain_filter(&mut self.rel_models, |_k, v| v.object == id || v.related == id)
.keys();
let removed_relation_ids = map_drain_filter(&mut self.rel_models, |_k, v| {
v.object == id || v.related == id
})
.keys();
debug!("Undefined {} relation models", removed_relation_ids.len());
// Remove related property templates
let removed_prop_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.object == id || removed_relation_ids.contains(&v.object))
.keys();
let removed_prop_ids = map_drain_filter(&mut self.prop_models, |_k, v| {
v.object == id || removed_relation_ids.contains(&v.object)
})
.keys();
debug!("Undefined {} property models", removed_prop_ids.len());
// Remove objects
@ -167,18 +218,24 @@ impl Storage {
debug!("Deleted {} objects", removed_objects.len());
// Remove property values
let removed_values = map_drain_filter(&mut self.values, |_k, v| removed_prop_ids.contains(&v.model));
let removed_values = map_drain_filter(&mut self.values, |_k, v| {
removed_prop_ids.contains(&v.model)
});
debug!("Deleted {} object or relation values", removed_values.len());
// Remove relations
let removed_relations = map_drain_filter(&mut self.relations, |_k, v| removed_relation_ids.contains(&v.model));
let removed_relations = map_drain_filter(&mut self.relations, |_k, v| {
removed_relation_ids.contains(&v.model)
});
debug!("Deleted {} object relations", removed_relations.len());
// Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with.
Ok(t)
} else {
Err(StorageError::NotExist(format!("object model {}", id).into()))
Err(StorageError::NotExist(
format!("object model {}", id).into(),
))
};
}
@ -192,17 +249,25 @@ impl Storage {
debug!("Deleted {} object relations", removed.len());
// Remove related property templates
let removed_prop_tpl_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.object == id).keys();
debug!("Undefined {} relation property models", removed_prop_tpl_ids.len());
let removed_values = map_drain_filter(&mut self.values, |_k, v| removed_prop_tpl_ids.contains(&v.model));
let removed_prop_tpl_ids =
map_drain_filter(&mut self.prop_models, |_k, v| v.object == id).keys();
debug!(
"Undefined {} relation property models",
removed_prop_tpl_ids.len()
);
let removed_values = map_drain_filter(&mut self.values, |_k, v| {
removed_prop_tpl_ids.contains(&v.model)
});
debug!("Deleted {} relation values", removed_values.len());
// Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with.
Ok(t)
} else {
Err(StorageError::NotExist(format!("relation model {}", id).into()))
Err(StorageError::NotExist(
format!("relation model {}", id).into(),
))
};
}
@ -216,7 +281,9 @@ impl Storage {
debug!("Deleted {} values", removed_values.len());
Ok(t)
} else {
Err(StorageError::NotExist(format!("property model {}", id).into()))
Err(StorageError::NotExist(
format!("property model {}", id).into(),
))
};
}
@ -253,38 +320,70 @@ impl Storage {
let obj_model = match self.obj_models.get(&obj_model_id) {
Some(m) => m,
None => return Err(StorageError::NotExist(format!("object model {}", obj_model_id).into()))
None => {
return Err(StorageError::NotExist(
format!("object model {}", obj_model_id).into(),
))
}
};
// validate unique name
if self.objects.iter().find(|(_, o)| o.model == obj_model_id && o.name == insobj.name).is_some() {
if self
.objects
.iter()
.find(|(_, o)| o.model == obj_model_id && o.name == insobj.name)
.is_some()
{
return Err(StorageError::ConstraintViolation(
format!("{} named \"{}\" already exists", self.get_model_name(obj_model_id), insobj.name).into()));
format!(
"{} named \"{}\" already exists",
self.get_model_name(obj_model_id),
insobj.name
)
.into(),
));
}
let object_id = next_id();
let object = data::Object {
id: object_id,
model: obj_model_id,
name: insobj.name
name: insobj.name,
};
let find_values_to_insert = |values: Vec<InsertValue>, parent_id : ID, 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);
let mut values_to_insert = vec![];
for (id, prop) in self.prop_models.iter().filter(|(_id, p)| p.object == 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_model(parent_model_id)).into()));
return Err(StorageError::ConstraintViolation(
format!(
"{} of {} cannot have multiple values",
prop,
self.describe_model(parent_model_id)
)
.into(),
));
}
for val_instance in values {
values_to_insert.push(data::Value {
id: next_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()))?,
value: val_instance.value.cast_to(prop.data_type).map_err(|v| {
StorageError::ConstraintViolation(
format!("{} cannot accept value {:?}", prop, v).into(),
)
})?,
});
}
} else {
@ -305,30 +404,49 @@ impl Storage {
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);
let mut relations_by_id = insobj
.relations
.into_iter()
.into_group_map_by(|ir| ir.model);
let mut relations_to_insert = vec![];
for (relation_model_id, relation_model) in self.rel_models.iter().filter(|(_id, r)| r.object == 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()));
return Err(StorageError::ConstraintViolation(
format!("{} of {} cannot be set multiply", relation_model, obj_model)
.into(),
));
}
for rel_instance in instances {
if let Some(related) = self.objects.get(&rel_instance.related) {
if related.model != relation_model.related {
return Err(StorageError::ConstraintViolation(
format!("{} of {} requires object of type {}, got {}",
relation_model, obj_model,
self.describe_model(relation_model.related),
self.describe_model(related.model)).into()));
format!(
"{} of {} requires object of type {}, got {}",
relation_model,
obj_model,
self.describe_model(relation_model.related),
self.describe_model(related.model)
)
.into(),
));
}
}
let relation_id = next_id();
// Relations can have properties
values_to_insert.extend(find_values_to_insert(rel_instance.values, relation_id, *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: relation_id,
@ -339,7 +457,9 @@ impl Storage {
}
} else {
if !relation_model.optional {
return Err(StorageError::ConstraintViolation(format!("{} is required for {}", relation_model, obj_model).into()));
return Err(StorageError::ConstraintViolation(
format!("{} is required for {}", relation_model, obj_model).into(),
));
}
}
}
@ -360,92 +480,120 @@ impl Storage {
}
// Reading
pub fn get_object_models(&self) -> impl Iterator<Item=&ObjectModel> {
pub fn get_object_models(&self) -> impl Iterator<Item = &ObjectModel> {
self.obj_models.values()
}
pub fn get_object_model(&self, id : ID) -> Option<&ObjectModel> {
pub fn get_object_model(&self, id: ID) -> Option<&ObjectModel> {
self.obj_models.get(&id)
}
pub fn get_relation_model(&self, id : ID) -> Option<&RelationModel> {
pub fn get_relation_model(&self, id: ID) -> Option<&RelationModel> {
self.rel_models.get(&id)
}
pub fn get_property_model(&self, id : ID) -> Option<&PropertyModel> {
pub fn get_property_model(&self, id: ID) -> Option<&PropertyModel> {
self.prop_models.get(&id)
}
pub fn get_grouped_prop_models(&self) -> HashMap<ID, Vec<&PropertyModel>> {
self.prop_models.values()
self.prop_models
.values()
.into_group_map_by(|model| model.object)
}
pub fn get_grouped_prop_models_for_parents(&self, parents: Vec<ID>) -> HashMap<ID, Vec<&PropertyModel>> {
self.prop_models.values()
pub fn get_grouped_prop_models_for_parents(
&self,
parents: Vec<ID>,
) -> HashMap<ID, Vec<&PropertyModel>> {
self.prop_models
.values()
.filter(|p| parents.contains(&p.object))
.into_group_map_by(|model| model.object)
}
pub fn get_relations_for_object(&self, object_id: ID) -> impl Iterator<Item=&data::Relation> {
self.relations.values()
pub fn get_relations_for_object(&self, object_id: ID) -> impl Iterator<Item = &data::Relation> {
self.relations
.values()
.filter(move |rel| rel.object == object_id)
}
pub fn get_reciprocal_relations_for_object(&self, object_id: ID) -> impl Iterator<Item=&data::Relation> {
self.relations.values()
pub fn get_reciprocal_relations_for_object(
&self,
object_id: ID,
) -> impl Iterator<Item = &data::Relation> {
self.relations
.values()
.filter(move |rel| rel.related == object_id)
}
pub fn get_values_for_object(&self, object_id: ID) -> impl Iterator<Item=&data::Value> {
self.values.values()
pub fn get_values_for_object(&self, object_id: ID) -> impl Iterator<Item = &data::Value> {
self.values
.values()
.filter(move |prop| prop.object == object_id)
}
pub fn get_grouped_values_for_objects(&self, parents: Vec<ID>) -> HashMap<ID, Vec<&data::Value>> {
self.values.values()
pub fn get_grouped_values_for_objects(
&self,
parents: Vec<ID>,
) -> HashMap<ID, Vec<&data::Value>> {
self.values
.values()
.filter(move |prop| parents.contains(&prop.object))
.into_group_map_by(|model| model.object)
}
pub fn get_relation_models_for_object_model(&self, model_id: ID) -> impl Iterator<Item=&RelationModel> {
self.rel_models.values()
pub fn get_relation_models_for_object_model(
&self,
model_id: ID,
) -> impl Iterator<Item = &RelationModel> {
self.rel_models
.values()
.filter(move |model| model.object == model_id)
}
pub fn get_property_models_for_parents(&self, parents: Vec<ID>) -> impl Iterator<Item=&PropertyModel> {
self.prop_models.values()
pub fn get_property_models_for_parents(
&self,
parents: Vec<ID>,
) -> impl Iterator<Item = &PropertyModel> {
self.prop_models
.values()
.filter(move |model| parents.contains(&model.object))
}
pub fn get_objects_of_type(&self, model_ids : Vec<ID>) -> impl Iterator<Item=&data::Object> {
self.objects.values()
pub fn get_objects_of_type(&self, model_ids: Vec<ID>) -> impl Iterator<Item = &data::Object> {
self.objects
.values()
.filter(move |object| model_ids.contains(&object.model))
}
pub fn get_grouped_objects(&self) -> HashMap<ID, Vec<&Object>> {
self.objects.values()
self.objects
.values()
.into_group_map_by(|object| object.model)
}
pub fn get_grouped_relation_models(&self) -> HashMap<ID, Vec<&RelationModel>> {
self.rel_models.values()
self.rel_models
.values()
.into_group_map_by(|model| model.object)
}
pub fn get_grouped_reciprocal_relation_models(&self) -> HashMap<ID, Vec<&RelationModel>> {
self.rel_models.values()
self.rel_models
.values()
.into_group_map_by(|model| model.related)
}
pub fn get_object(&self, id : ID) -> Option<&Object> {
pub fn get_object(&self, id: ID) -> Option<&Object> {
self.objects.get(&id)
}
// Updates
pub fn update_object(&mut self, updobj: UpdateObj) -> Result<(), StorageError> {
let old_object = self.objects.get(&updobj.id)
.ok_or_else(|| StorageError::ConstraintViolation(format!("Object does not exist").into()))?;
let old_object = self.objects.get(&updobj.id).ok_or_else(|| {
StorageError::ConstraintViolation(format!("Object does not exist").into())
})?;
let updated_object_id = old_object.id;
let updated_object_model_id = old_object.model;
@ -453,48 +601,92 @@ impl Storage {
let obj_model = match self.obj_models.get(&updated_object_model_id) {
Some(m) => m,
None => return Err(StorageError::NotExist(format!("object model {}", updated_object_model_id).into()))
None => {
return Err(StorageError::NotExist(
format!("object model {}", updated_object_model_id).into(),
))
}
};
// validate unique name
if self.objects.iter().find(|(_, o)| o.model == updated_object_model_id && o.name == updobj.name && o.id != updated_object_id).is_some() {
if self
.objects
.iter()
.find(|(_, o)| {
o.model == updated_object_model_id
&& o.name == updobj.name
&& o.id != updated_object_id
})
.is_some()
{
return Err(StorageError::ConstraintViolation(
format!("{} named \"{}\" already exists", self.get_model_name(updated_object_model_id), updobj.name).into()));
format!(
"{} named \"{}\" already exists",
self.get_model_name(updated_object_model_id),
updobj.name
)
.into(),
));
}
// Update the object after everything else is checked
let find_values_to_change = |values: Vec<UpsertValue>, parent_id : ID, parent_model_id: ID| -> Result<(
// Insert (can overwrite existing, the ID will not change)
Vec<data::Value>,
// Delete
Vec<ID>
), StorageError> {
let find_values_to_change = |values: Vec<UpsertValue>,
parent_id: ID,
parent_model_id: ID|
-> Result<
(
// Insert (can overwrite existing, the ID will not change)
Vec<data::Value>,
// Delete
Vec<ID>,
),
StorageError,
> {
let mut values_by_model = values.into_iter().into_group_map_by(|iv| iv.model);
let mut values_to_insert = vec![];
let mut ids_to_delete = vec![];
let mut existing_values_by_id = self.values.values()
let mut existing_values_by_id = self
.values
.values()
.filter(|v| v.object == parent_id)
.into_group_map_by(|v| v.model);
for (prop_model_id, prop) in self.prop_models.iter().filter(|(_id, p)| p.object == parent_model_id) {
for (prop_model_id, prop) in self
.prop_models
.iter()
.filter(|(_id, p)| p.object == parent_model_id)
{
if let Some(values) = values_by_model.remove(prop_model_id) {
if values.len() > 1 && !prop.multiple {
return Err(StorageError::ConstraintViolation(format!("{} of {} cannot have multiple values", prop, self.describe_model(parent_model_id)).into()));
return Err(StorageError::ConstraintViolation(
format!(
"{} of {} cannot have multiple values",
prop,
self.describe_model(parent_model_id)
)
.into(),
));
}
let updated_ids = values.iter().filter_map(|v| v.id).collect_vec();
ids_to_delete.extend(existing_values_by_id.remove(&prop.id).unwrap_or_default().into_iter()
.filter(|v| !updated_ids.contains(&v.id)).map(|v| v.id));
ids_to_delete.extend(
existing_values_by_id
.remove(&prop.id)
.unwrap_or_default()
.into_iter()
.filter(|v| !updated_ids.contains(&v.id))
.map(|v| v.id),
);
for val_instance in values {
values_to_insert.push(data::Value {
id: val_instance.id.unwrap_or_else(|| next_id()),
object: parent_id,
model: prop.id,
value: val_instance.value
value: val_instance.value,
});
}
} else {
@ -511,42 +703,63 @@ impl Storage {
Ok((values_to_insert, ids_to_delete))
};
let (mut values_to_insert, mut value_ids_to_delete) = find_values_to_change(updobj.values, updated_object_id, updated_object_model_id)?;
let (mut values_to_insert, mut value_ids_to_delete) =
find_values_to_change(updobj.values, updated_object_id, updated_object_model_id)?;
// And now ..... relations!
let mut relations_by_model = updobj.relations.into_iter().into_group_map_by(|ir| ir.model);
let mut relations_by_model = updobj
.relations
.into_iter()
.into_group_map_by(|ir| ir.model);
let mut relations_to_insert = vec![];
let mut relations_to_delete = vec![];
let mut existing_relations_by_id = self.relations.values()
let mut existing_relations_by_id = self
.relations
.values()
.filter(|v| v.object == updated_object_id)
.into_group_map_by(|v| v.model);
let rel_models_by_id = self.rel_models.iter().filter(|(_id, r)| r.object == updated_object_model_id);
let rel_models_by_id = self
.rel_models
.iter()
.filter(|(_id, r)| r.object == updated_object_model_id);
for (relation_model_id, relation_model) in rel_models_by_id {
let mut updated_ids = vec![];
if let Some(instances) = relations_by_model.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()));
return Err(StorageError::ConstraintViolation(
format!("{} of {} cannot be set multiply", relation_model, obj_model)
.into(),
));
}
for rel_instance in instances {
if let Some(related) = self.objects.get(&rel_instance.related) {
if related.model != relation_model.related {
return Err(StorageError::ConstraintViolation(
format!("{} of {} requires object of type {}, got {}",
relation_model, obj_model,
self.describe_model(relation_model.related),
self.describe_model(related.model)).into()));
format!(
"{} of {} requires object of type {}, got {}",
relation_model,
obj_model,
self.describe_model(relation_model.related),
self.describe_model(related.model)
)
.into(),
));
}
}
let relation_id = rel_instance.id.unwrap_or_else(|| next_id());
// Relations can have properties
let (ins, del) = find_values_to_change(rel_instance.values, relation_id, *relation_model_id)?;
let (ins, del) = find_values_to_change(
rel_instance.values,
relation_id,
*relation_model_id,
)?;
values_to_insert.extend(ins);
value_ids_to_delete.extend(del);
@ -561,12 +774,20 @@ impl Storage {
}
} else {
if !relation_model.optional {
return Err(StorageError::ConstraintViolation(format!("{} is required for {}", relation_model, obj_model).into()));
return Err(StorageError::ConstraintViolation(
format!("{} is required for {}", relation_model, obj_model).into(),
));
}
}
relations_to_delete.extend(existing_relations_by_id.remove(&relation_model_id).unwrap_or_default().into_iter()
.filter(|rel| !updated_ids.contains(&rel.id)).map(|rel| rel.id));
relations_to_delete.extend(
existing_relations_by_id
.remove(&relation_model_id)
.unwrap_or_default()
.into_iter()
.filter(|rel| !updated_ids.contains(&rel.id))
.map(|rel| rel.id),
);
}
let obj_mut = self.objects.get_mut(&updated_object_id).unwrap();
@ -595,26 +816,38 @@ impl Storage {
Ok(())
}
pub fn update_object_model(&mut self, model : ObjectModel) -> Result<(), StorageError> {
pub fn update_object_model(&mut self, model: ObjectModel) -> Result<(), StorageError> {
if model.name.is_empty() {
return Err(StorageError::ConstraintViolation(format!("Model name must not be empty.").into()));
return Err(StorageError::ConstraintViolation(
format!("Model name must not be empty.").into(),
));
}
if !self.obj_models.contains_key(&model.id) {
return Err(StorageError::NotExist(format!("Object model ID {} does not exist.", model.id).into()));
return Err(StorageError::NotExist(
format!("Object model ID {} does not exist.", model.id).into(),
));
}
if let Some(conflict) = self.obj_models.values().find(|m| m.id != model.id && m.name == model.name) {
return Err(StorageError::ConstraintViolation(format!("Object {} already has the name {}", conflict.id, model.name).into()));
if let Some(conflict) = self
.obj_models
.values()
.find(|m| m.id != model.id && m.name == model.name)
{
return Err(StorageError::ConstraintViolation(
format!("Object {} already has the name {}", conflict.id, model.name).into(),
));
}
self.obj_models.insert(model.id, model);
Ok(())
}
pub fn update_relation_model(&mut self, mut rel : RelationModel) -> Result<(), StorageError> {
pub fn update_relation_model(&mut self, mut rel: RelationModel) -> Result<(), StorageError> {
if rel.name.is_empty() || rel.reciprocal_name.is_empty() {
return Err(StorageError::ConstraintViolation(format!("Relation names must not be empty.").into()));
return Err(StorageError::ConstraintViolation(
format!("Relation names must not be empty.").into(),
));
}
// Object and Related can't be changed, so we re-fill them from the existing model
@ -622,7 +855,9 @@ impl Storage {
rel.object = existing.object;
rel.related = existing.related;
} else {
return Err(StorageError::NotExist(format!("Relation model ID {} does not exist.", rel.id).into()));
return Err(StorageError::NotExist(
format!("Relation model ID {} does not exist.", rel.id).into(),
));
}
// Difficult checks ...
@ -647,25 +882,44 @@ impl Storage {
pub fn update_property_model(&mut self, mut prop: PropertyModel) -> Result<(), StorageError> {
if prop.name.is_empty() {
return Err(StorageError::ConstraintViolation(format!("Property name must not be empty.").into()));
return Err(StorageError::ConstraintViolation(
format!("Property name must not be empty.").into(),
));
}
// Object can't be changed, so we re-fill them from the existing model
if let Some(existing) = self.prop_models.get(&prop.id) {
prop.object = existing.object;
} else {
return Err(StorageError::NotExist(format!("Property model ID {} does not exist.", prop.id).into()));
return Err(StorageError::NotExist(
format!("Property model ID {} does not exist.", prop.id).into(),
));
}
if self.prop_models.iter().find(|(_, t)| t.object == prop.object && t.name == prop.name && t.id != prop.id).is_some() {
if self
.prop_models
.iter()
.find(|(_, t)| t.object == prop.object && t.name == prop.name && t.id != prop.id)
.is_some()
{
return Err(StorageError::ConstraintViolation(
format!("property with the name \"{}\" already exists on {}", prop.name, self.describe_model(prop.object)).into()));
format!(
"property with the name \"{}\" already exists on {}",
prop.name,
self.describe_model(prop.object)
)
.into(),
));
}
// Ensure the default type is compatible
prop.default = match prop.default.clone().cast_to(prop.data_type) {
Ok(v) => v,
Err(_) => return Err(StorageError::NotExist(format!("default value {:?} has invalid type", prop.default).into()))
Err(_) => {
return Err(StorageError::NotExist(
format!("default value {:?} has invalid type", prop.default).into(),
))
}
};
self.prop_models.insert(prop.id, prop);
@ -677,12 +931,16 @@ impl Storage {
return if let Some(t) = self.objects.remove(&id) {
debug!("Delete object \"{}\"", t.name);
// Remove relation templates
let removed_relation_ids = map_drain_filter(&mut self.relations, |_k, v| v.object == id || v.related == id)
.keys();
let removed_relation_ids = map_drain_filter(&mut self.relations, |_k, v| {
v.object == id || v.related == id
})
.keys();
debug!("Deleted {} object relations", removed_relation_ids.len());
// Remove values
let removed_values = map_drain_filter(&mut self.values, |_k, v| removed_relation_ids.contains(&v.object) || v.object == id);
let removed_values = map_drain_filter(&mut self.values, |_k, v| {
removed_relation_ids.contains(&v.object) || v.object == id
});
debug!("Deleted {} object values", removed_values.len());
Ok(t)

@ -1,7 +1,7 @@
//! Data model structs and enums
use std::fmt::{Display, Formatter};
use std::fmt;
use std::fmt::{Display, Formatter};
use serde::{Deserialize, Serialize};

@ -1,19 +1,19 @@
//! 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::HaveId;
use crate::ID;
use serde::{Serialize, Deserializer, Deserialize};
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::de::{Visitor, SeqAccess};
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
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
where
S: serde::Serializer,
X: Serialize + HaveId,
{
let mut seq = s.serialize_seq(Some(x.len()))?;
for p in x.values() {
@ -23,16 +23,16 @@ pub fn serialize<S, X>(x: &HashMap<ID, X>, s: S) -> Result<S::Ok, S::Error>
}
pub fn deserialize<'de, D, X>(deserializer: D) -> Result<HashMap<ID, X>, D::Error>
where
D: Deserializer<'de>,
X: Deserialize<'de> + HaveId
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> {
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 {
@ -40,8 +40,8 @@ impl<'de, X : Deserialize<'de> + HaveId> Visitor<'de> for SeqToMap<X> {
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
where
A: SeqAccess<'de>,
{
let mut map = HashMap::<ID, X>::new();

@ -16,7 +16,7 @@ fn test1() {
}
"#;
let x : ObjectModel = serde_json::from_str(s).unwrap();
let x: ObjectModel = serde_json::from_str(s).unwrap();
println!("{}", serde_json::to_string_pretty(&x).unwrap());
}

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::{ID, TypedValue};
use crate::{TypedValue, ID};
/// Update or insert a value
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -27,12 +27,12 @@ pub struct UpsertRelation {
}
/// Update an existing object
#[derive(Deserialize,Debug)]
#[derive(Deserialize, Debug)]
pub struct UpdateObj {
pub id: ID,
pub name: String,
pub values: Vec<UpsertValue>,
pub relations: Vec<UpsertRelation>
pub relations: Vec<UpsertRelation>,
}
#[cfg(test)]
@ -83,14 +83,6 @@ mod tests {
]
});
let obj : UpdateObj = serde_json::from_value(json).unwrap();
}
#[test]
#[cfg(not(feature = "uuid-ids"))]
fn deserialize2() {
let json = json!();
let obj : UpdateObj = serde_json::from_value(json).unwrap();
let _obj: UpdateObj = serde_json::from_value(json).unwrap();
}
}

Loading…
Cancel
Save