fix warning and reformat all

master
Ondřej Hruška 4 years ago
parent 6229f7969c
commit 2a93800765
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  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. 8
      yopa/src/update.rs

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

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

@ -1,28 +1,17 @@
use std::fmt::{Debug, Display}; use std::ops::Deref;
use std::ops::{DerefMut, Deref};
use std::str::FromStr;
use actix_session::Session; use actix_session::Session;
use actix_web::{HttpRequest, HttpResponse, Responder, web}; use actix_web::{HttpResponse, Responder};
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;
pub(crate) mod models; pub(crate) mod models;
pub(crate) mod objects; pub(crate) mod objects;
#[get("/")] #[get("/")]
pub(crate) async fn index(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<HttpResponse> { pub(crate) async fn index(
objects::list_inner(session, store) session: Session,
.await store: crate::YopaStoreWrapper,
) -> actix_web::Result<HttpResponse> {
objects::list_inner(session, store).await
} }
#[get("/takeout")] #[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::object::ObjectModelDisplay;
use crate::routes::models::relation::RelationModelDisplay;
use crate::session_ext::SessionExt; use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt; use crate::tera_ext::TeraExt;
use crate::TERA;
use actix_session::Session;
use actix_web::Responder;
pub(crate) mod object; pub(crate) mod object;
pub(crate) mod relation;
pub(crate) mod property; pub(crate) mod property;
pub(crate) mod relation;
#[get("/models")] #[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 rg = store.read().await;
let models_iter = rg.get_object_models(); 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![]; let mut models = vec![];
for om in models_iter { for om in models_iter {
let mut relations = model_relations.remove(&om.id).unwrap_or_default(); let relations = model_relations.remove(&om.id).unwrap_or_default();
let mut relations = relations.into_iter().map(|rm| { let mut relations = relations
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default(); .into_iter()
rprops.sort_by_key(|m| &m.name); .map(|rm| {
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
rprops.sort_by_key(|m| &m.name);
RelationModelDisplay { RelationModelDisplay {
model: rm, model: rm,
related_name: rg.get_model_name(rm.related), related_name: rg.get_model_name(rm.related),
properties: rprops, properties: rprops,
} }
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
relations.sort_by_key(|d| &d.model.name); relations.sort_by_key(|d| &d.model.name);
// Relations coming INTO this model // Relations coming INTO this model
let mut reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default(); let reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default();
let mut reciprocal_relations = reciprocal_relations.into_iter().map(|rm| { let mut reciprocal_relations = reciprocal_relations
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default(); .into_iter()
rprops.sort_by_key(|m| &m.name); .map(|rm| {
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
rprops.sort_by_key(|m| &m.name);
RelationModelDisplay { RelationModelDisplay {
model: rm, model: rm,
related_name: rg.get_model_name(rm.object), related_name: rg.get_model_name(rm.object),
properties: rprops, properties: rprops,
} }
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
reciprocal_relations.sort_by_key(|d| &d.model.reciprocal_name); reciprocal_relations.sort_by_key(|d| &d.model.reciprocal_name);
let mut properties = model_props.remove(&om.id).unwrap_or_default(); let mut properties = model_props.remove(&om.id).unwrap_or_default();

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

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

@ -1,14 +1,14 @@
use actix_session::Session; use actix_session::Session;
use actix_web::{Responder, web}; use actix_web::{web, Responder};
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use yopa::ID;
use yopa::model::{PropertyModel, RelationModel}; use yopa::model::{PropertyModel, RelationModel};
use yopa::ID;
use crate::session_ext::SessionExt; use crate::session_ext::SessionExt;
use crate::TERA;
use crate::tera_ext::TeraExt; use crate::tera_ext::TeraExt;
use crate::utils::{ParseOrBadReq, redirect}; use crate::utils::{redirect, ParseOrBadReq};
use crate::TERA;
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub(crate) struct RelationModelDisplay<'a> { pub(crate) struct RelationModelDisplay<'a> {
@ -33,17 +33,21 @@ pub(crate) async fn create_form(
if let Ok(Some(form)) = session.take::<RelationModelCreateForm>("old") { if let Ok(Some(form)) = session.take::<RelationModelCreateForm>("old") {
context.insert("old", &form); context.insert("old", &form);
} else { } else {
context.insert("old", &RelationModelCreateForm { context.insert(
object: Default::default(), "old",
name: "".to_string(), &RelationModelCreateForm {
reciprocal_name: "".to_string(), object: Default::default(),
optional: false, name: "".to_string(),
multiple: false, reciprocal_name: "".to_string(),
related: Default::default() 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"))?; .ok_or_else(|| actix_web::error::ErrorNotFound("No such source object"))?;
let mut models: Vec<_> = rg.get_object_models().collect(); 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) TERA.build_response("models/relation_create", &context)
} }
#[derive(Serialize,Deserialize)] #[derive(Serialize, Deserialize)]
pub(crate) struct RelationModelCreateForm { pub(crate) struct RelationModelCreateForm {
pub object: ID, pub object: ID,
pub name: String, pub name: String,
@ -93,7 +97,7 @@ pub(crate) async fn create(
Err(e) => { Err(e) => {
warn!("Error creating relation model: {}", e); warn!("Error creating relation model: {}", e);
session.flash_error(e.to_string()); session.flash_error(e.to_string());
session.set("old", &form); session.set("old", &form).unwrap();
redirect(format!("/model/relation/create/{}", form.object)) 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(crate) struct RelationModelEditForm {
pub name: String, pub name: String,
pub reciprocal_name: String, pub reciprocal_name: String,
@ -146,7 +150,8 @@ pub(crate) async fn update_form(
session.render_flash(&mut context); session.render_flash(&mut context);
let rg = store.read().await; 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"))?; .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
// Re-fill old values // Re-fill old values
@ -181,7 +186,7 @@ pub(crate) async fn update(
reciprocal_name: form.reciprocal_name.to_string(), reciprocal_name: form.reciprocal_name.to_string(),
optional: form.optional, optional: form.optional,
multiple: form.multiple, multiple: form.multiple,
related: Default::default() // dummy related: Default::default(), // dummy
}) { }) {
Ok(_id) => { Ok(_id) => {
debug!("Relation updated, redirecting to root"); debug!("Relation updated, redirecting to root");
@ -191,7 +196,7 @@ pub(crate) async fn update(
Err(e) => { Err(e) => {
warn!("Error updating model: {}", e); warn!("Error updating model: {}", e);
session.flash_error(e.to_string()); session.flash_error(e.to_string());
session.set("old", form); session.set("old", form).unwrap();
redirect(format!("/model/relation/update/{}", id)) 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::session_ext::SessionExt;
use crate::routes::models::object::ObjectModelForm; use actix_session::Session;
use crate::TERA; use actix_web::{web, HttpResponse, Responder};
use crate::tera_ext::TeraExt; 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 crate::utils::redirect;
use actix_web::web::Json; use crate::TERA;
use serde_json::Value; use serde::Serialize;
use itertools::Itertools; use yopa::data::Object;
use yopa::model::{ObjectModel, PropertyModel, RelationModel}; use yopa::insert::InsertObj;
use yopa::{data, model, Storage, ID};
use heck::TitleCase; use heck::TitleCase;
use std::collections::HashMap; use itertools::Itertools;
use json_dotpath::DotPaths; use json_dotpath::DotPaths;
use std::collections::HashMap;
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
use yopa::update::UpdateObj; use yopa::update::UpdateObj;
// we only need references here, Context serializes everything to Value. // 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>, pub prop_models: Vec<&'a model::PropertyModel>,
} }
#[derive(Serialize,Debug,Clone)] #[derive(Serialize, Debug, Clone)]
pub struct ObjectCreateData<'a> { pub struct ObjectCreateData<'a> {
pub model_id: ID, pub model_id: ID,
pub schema: Schema<'a>, pub schema: Schema<'a>,
@ -39,14 +38,15 @@ pub struct ObjectCreateData<'a> {
pub(crate) async fn create_form( pub(crate) async fn create_form(
model_id: web::Path<ID>, model_id: web::Path<ID>,
store: crate::YopaStoreWrapper, store: crate::YopaStoreWrapper,
session: Session session: Session,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let mut context = tera::Context::new(); let mut context = tera::Context::new();
session.render_flash(&mut context); session.render_flash(&mut context);
let rg = store.read().await; 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"))?; .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
context.insert("model", model); context.insert("model", model);
@ -58,19 +58,20 @@ pub(crate) async fn create_form(
TERA.build_response("objects/object_create", &context) TERA.build_response("objects/object_create", &context)
} }
fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result<ObjectCreateData> { fn prepare_object_create_data(rg: &Storage, model_id: ID) -> actix_web::Result<ObjectCreateData> {
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"))?; .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.push(model.id);
prop_object_ids.sort(); prop_object_ids.sort();
prop_object_ids.dedup(); 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.sort();
related_ids.dedup(); related_ids.dedup();
@ -80,9 +81,11 @@ fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result
schema: Schema { schema: Schema {
obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here
rel_models: relations, 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)] #[derive(Debug, Serialize, Clone)]
struct ModelWithObjects<'a> { struct ModelWithObjects<'a> {
model : &'a ObjectModel, model: &'a ObjectModel,
objects: Vec<&'a Object> objects: Vec<&'a Object>,
} }
#[get("/objects")] #[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 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 rg = store.read().await;
let mut objects_by_model = rg.get_grouped_objects(); 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) .sorted_by_key(|m| &m.name)
.map(|model| { .map(|model| {
let mut objects = objects_by_model.remove(&model.id).unwrap_or_default(); let mut objects = objects_by_model.remove(&model.id).unwrap_or_default();
objects.sort_by_key(|o| &o.name); objects.sort_by_key(|o| &o.name);
ModelWithObjects { ModelWithObjects { model, objects }
model, })
objects .collect();
}
}).collect();
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.insert("models", &models); 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) TERA.build_response("objects/index", &ctx)
} }
#[derive(Debug,Serialize)] #[derive(Debug, Serialize)]
struct PropertyView<'a> { struct PropertyView<'a> {
model : &'a PropertyModel, model: &'a PropertyModel,
values: Vec<&'a yopa::data::Value> values: Vec<&'a yopa::data::Value>,
} }
#[derive(Debug,Serialize)] #[derive(Debug, Serialize)]
struct RelationView<'a> { struct RelationView<'a> {
model : &'a RelationModel, model: &'a RelationModel,
related_name: &'a str, related_name: &'a str,
instances: Vec<RelationInstanceView<'a>> instances: Vec<RelationInstanceView<'a>>,
} }
#[derive(Debug,Serialize)] #[derive(Debug, Serialize)]
struct RelationInstanceView<'a> { struct RelationInstanceView<'a> {
related: &'a Object, related: &'a Object,
properties: Vec<PropertyView<'a>> properties: Vec<PropertyView<'a>>,
} }
#[get("/object/detail/{id}")] #[get("/object/detail/{id}")]
pub(crate) async fn detail( pub(crate) async fn detail(
id: web::Path<ID>, id: web::Path<ID>,
store: crate::YopaStoreWrapper, store: crate::YopaStoreWrapper,
session: Session session: Session,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let object_id = *id; let object_id = *id;
@ -185,18 +192,22 @@ pub(crate) async fn detail(
let rg = store.read().await; 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"))?; .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"))?; .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
context.insert("object", object); context.insert("object", object);
context.insert("model", model); context.insert("model", model);
context.insert("kind", &rg.get_model_name(object.model)); context.insert("kind", &rg.get_model_name(object.model));
let mut relations = rg.get_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(); let reci_relations = rg
.get_reciprocal_relations_for_object(object_id)
.collect_vec();
// values by parent ID // values by parent ID
let mut ids_to_get_values_for = relations.iter().map(|r| r.id).collect_vec(); 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 // object's own properties
{ {
let mut object_values_by_model = grouped_values let object_values_by_model = grouped_values
.remove(&object_id).unwrap_or_default().into_iter() .remove(&object_id)
.unwrap_or_default()
.into_iter()
.into_group_map_by(|value| value.model); .into_group_map_by(|value| value.model);
let mut view_object_properties = vec![]; let mut view_object_properties = vec![];
for (prop_model_id, values) in object_values_by_model { for (prop_model_id, values) in object_values_by_model {
view_object_properties.push(PropertyView { view_object_properties.push(PropertyView {
model: rg.get_property_model(prop_model_id).unwrap(), model: rg.get_property_model(prop_model_id).unwrap(),
values values,
}) })
} }
@ -226,7 +239,8 @@ pub(crate) async fn detail(
// go through relations // go through relations
{ {
let grouped_relations = relations.iter() let grouped_relations = relations
.iter()
.into_group_map_by(|relation| relation.model); .into_group_map_by(|relation| relation.model);
let mut relation_views = vec![]; let mut relation_views = vec![];
@ -236,18 +250,20 @@ pub(crate) async fn detail(
for rel in relations { for rel in relations {
let related_obj = match rg.get_object(rel.related) { let related_obj = match rg.get_object(rel.related) {
None => continue, None => continue,
Some(obj) => obj Some(obj) => obj,
}; };
let mut rel_values_by_model = grouped_values let rel_values_by_model = grouped_values
.remove(&rel.id).unwrap_or_default().into_iter() .remove(&rel.id)
.unwrap_or_default()
.into_iter()
.into_group_map_by(|value| value.model); .into_group_map_by(|value| value.model);
let mut view_rel_properties = vec![]; let mut view_rel_properties = vec![];
for (prop_model_id, values) in rel_values_by_model { for (prop_model_id, values) in rel_values_by_model {
view_rel_properties.push(PropertyView { view_rel_properties.push(PropertyView {
model: rg.get_property_model(prop_model_id).unwrap(), model: rg.get_property_model(prop_model_id).unwrap(),
values values,
}) })
} }
@ -255,7 +271,7 @@ pub(crate) async fn detail(
instances.push(RelationInstanceView { instances.push(RelationInstanceView {
related: related_obj, related: related_obj,
properties: view_rel_properties properties: view_rel_properties,
}) })
} }
@ -264,7 +280,7 @@ pub(crate) async fn detail(
relation_views.push(RelationView { relation_views.push(RelationView {
model: rg.get_relation_model(model_id).unwrap(), model: rg.get_relation_model(model_id).unwrap(),
related_name: rg.get_model_name(model_id), related_name: rg.get_model_name(model_id),
instances instances,
}) })
} }
@ -275,7 +291,8 @@ pub(crate) async fn detail(
// near-copypasta for reciprocal // near-copypasta for reciprocal
{ {
let grouped_relations = reci_relations.iter() let grouped_relations = reci_relations
.iter()
.into_group_map_by(|relation| relation.model); .into_group_map_by(|relation| relation.model);
let mut relation_views = vec![]; let mut relation_views = vec![];
@ -285,18 +302,20 @@ pub(crate) async fn detail(
for rel in relations { for rel in relations {
let related_obj = match rg.get_object(rel.object) { let related_obj = match rg.get_object(rel.object) {
None => continue, None => continue,
Some(obj) => obj Some(obj) => obj,
}; };
let mut rel_values_by_model = grouped_values let rel_values_by_model = grouped_values
.remove(&rel.id).unwrap_or_default().into_iter() .remove(&rel.id)
.unwrap_or_default()
.into_iter()
.into_group_map_by(|value| value.model); .into_group_map_by(|value| value.model);
let mut view_rel_properties = vec![]; let mut view_rel_properties = vec![];
for (prop_model_id, values) in rel_values_by_model { for (prop_model_id, values) in rel_values_by_model {
view_rel_properties.push(PropertyView { view_rel_properties.push(PropertyView {
model: rg.get_property_model(prop_model_id).unwrap(), model: rg.get_property_model(prop_model_id).unwrap(),
values values,
}) })
} }
@ -304,7 +323,7 @@ pub(crate) async fn detail(
instances.push(RelationInstanceView { instances.push(RelationInstanceView {
related: related_obj, related: related_obj,
properties: view_rel_properties properties: view_rel_properties,
}) })
} }
@ -313,7 +332,7 @@ pub(crate) async fn detail(
relation_views.push(RelationView { relation_views.push(RelationView {
model: rg.get_relation_model(model_id).unwrap(), model: rg.get_relation_model(model_id).unwrap(),
related_name: rg.get_model_name(model_id), 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) TERA.build_response("objects/object_detail", &context)
} }
#[derive(Serialize,Debug,Clone)] #[derive(Serialize, Debug, Clone)]
struct EnrichedObject<'a> { struct EnrichedObject<'a> {
id: ID, id: ID,
model: ID, model: ID,
name: String, 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>>>, relations: HashMap<String /* ID */, Vec<EnrichedRelation<'a>>>,
} }
#[derive(Serialize,Debug,Clone)] #[derive(Serialize, Debug, Clone)]
struct EnrichedRelation<'a> { struct EnrichedRelation<'a> {
id: ID, id: ID,
object: ID, object: ID,
@ -347,17 +369,19 @@ struct EnrichedRelation<'a> {
pub(crate) async fn update_form( pub(crate) async fn update_form(
id: web::Path<ID>, id: web::Path<ID>,
store: crate::YopaStoreWrapper, store: crate::YopaStoreWrapper,
session: Session session: Session,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let mut context = tera::Context::new(); let mut context = tera::Context::new();
session.render_flash(&mut context); session.render_flash(&mut context);
let rg = store.read().await; 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"))?; .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"))?; .ok_or_else(|| actix_web::error::ErrorNotFound("Object has no model"))?;
// maybe its useful,idk // maybe its useful,idk
@ -370,47 +394,71 @@ pub(crate) async fn update_form(
let mut relation_map = HashMap::new(); let mut relation_map = HashMap::new();
// Some properties may have no values, so we first check what IDs to expect // Some properties may have no values, so we first check what IDs to expect
let prop_ids = create_data.schema.prop_models.iter() let prop_ids = create_data
.filter(|p| p.object == model.id).map(|p| p.id).collect_vec(); .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) let mut values_grouped = rg.get_values_for_object(*id).into_group_map_by(|v| v.model);
.into_group_map_by(|v| v.model);
prop_ids.into_iter().for_each(|id| { 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 // 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) .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 relations = rg.get_relations_for_object(*id).collect_vec();
let relation_ids = relations.iter().map(|r| r.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); .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 { 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 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 { for rel in relations {
let mut relation_values_map = HashMap::new(); let mut relation_values_map = HashMap::new();
// values keyed by model // 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); .into_group_map_by(|relation| relation.model);
prop_models_for_relation.iter().for_each(|prop_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 { instances.push(EnrichedRelation {
@ -418,7 +466,7 @@ pub(crate) async fn update_form(
object: rel.object, object: rel.object,
model: rel.model, model: rel.model,
related: rel.related, related: rel.related,
values: relation_values_map values: relation_values_map,
}); });
} }
@ -433,7 +481,7 @@ pub(crate) async fn update_form(
model: object.model, model: object.model,
name: object.name.clone(), name: object.name.clone(),
values: value_map, values: value_map,
relations: relation_map relations: relation_map,
}; };
form.dot_remove("model_id").unwrap(); form.dot_remove("model_id").unwrap();
@ -443,10 +491,9 @@ pub(crate) async fn update_form(
TERA.build_response("objects/object_update", &context) TERA.build_response("objects/object_update", &context)
} }
#[post("/object/update/{id}")] #[post("/object/update/{id}")]
pub(crate) async fn update( pub(crate) async fn update(
id: web::Path<ID>, _id: web::Path<ID>,
form: web::Json<UpdateObj>, form: web::Json<UpdateObj>,
store: crate::YopaStoreWrapper, store: crate::YopaStoreWrapper,
session: Session, session: Session,
@ -457,7 +504,8 @@ pub(crate) async fn update(
let form = form.into_inner(); let form = form.into_inner();
let name = form.name.clone(); 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"))?; .ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
let model_name = wg.get_model_name(object.model).to_owned().to_title_case(); 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 include_dir::Dir;
use tera::Tera; 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() { 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" { if halves.last().unwrap() != &"tera" {
debug!("Bad file: {:?}", f); debug!("Bad file: {:?}", f);
@ -30,11 +41,20 @@ fn tera_includedir_walk_folder_inner(collected: &mut Vec<(String, String)>, tera
pub(crate) trait TeraExt { pub(crate) trait TeraExt {
fn add_include_dir_templates(&mut self, dir: &Dir) -> tera::Result<()>; 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) 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 { impl TeraExt for Tera {
@ -45,10 +65,15 @@ impl TeraExt for Tera {
self.add_raw_templates(templates) self.add_raw_templates(templates)
} }
fn build_err_response(&self, code: StatusCode, template: &str, context: &tera::Context) -> actix_web::Result<HttpResponse> { fn build_err_response(
let html = self.render(template, context).map_err(|e| { &self,
actix_web::error::ErrorInternalServerError(e) 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)) Ok(HttpResponse::build(code).body(html))
} }

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

@ -4,7 +4,10 @@ use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
/// drain_filter() implemented for HashMap. It returns the removed items as a Vec /// 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 removed = vec![];
let mut retain = vec![]; let mut retain = vec![];
for (k, v) in map.drain() { for (k, v) in map.drain() {

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

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

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

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

@ -1,19 +1,19 @@
//! Serialize a HashMap of ID-keyed structs that also contain the ID inside //! Serialize a HashMap of ID-keyed structs that also contain the ID inside
//! to a plain list, and also back when deserializing. //! to a plain list, and also back when deserializing.
use std::collections::HashMap; use crate::id::HaveId;
use crate::ID; use crate::ID;
use serde::{Serialize, Deserializer, Deserialize}; use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq; use serde::ser::SerializeSeq;
use serde::de::{Visitor, SeqAccess}; use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use std::fmt; use std::fmt;
use crate::id::HaveId;
use std::marker::PhantomData; use std::marker::PhantomData;
pub fn serialize<S, X>(x: &HashMap<ID, X>, s: S) -> Result<S::Ok, S::Error> pub fn serialize<S, X>(x: &HashMap<ID, X>, s: S) -> Result<S::Ok, S::Error>
where where
S: serde::Serializer, S: serde::Serializer,
X: Serialize + HaveId X: Serialize + HaveId,
{ {
let mut seq = s.serialize_seq(Some(x.len()))?; let mut seq = s.serialize_seq(Some(x.len()))?;
for p in x.values() { 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> pub fn deserialize<'de, D, X>(deserializer: D) -> Result<HashMap<ID, X>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
X: Deserialize<'de> + HaveId X: Deserialize<'de> + HaveId,
{ {
deserializer.deserialize_seq(SeqToMap(Default::default())) deserializer.deserialize_seq(SeqToMap(Default::default()))
} }
pub struct SeqToMap<X>(PhantomData<X>); 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>; type Value = HashMap<ID, X>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 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> fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where where
A: SeqAccess<'de>, A: SeqAccess<'de>,
{ {
let mut map = HashMap::<ID, X>::new(); 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()); println!("{}", serde_json::to_string_pretty(&x).unwrap());
} }

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

Loading…
Cancel
Save