diff --git a/yopa-web/build.rs b/yopa-web/build.rs index 85ce038..76f0a67 100644 --- a/yopa-web/build.rs +++ b/yopa-web/build.rs @@ -10,5 +10,6 @@ fn main() { resource_dir("./resources/static") .with_generated_filename(out_path.join("static_files.rs")) .with_generated_fn("included_static_files") - .build().unwrap(); + .build() + .unwrap(); } diff --git a/yopa-web/src/main.rs b/yopa-web/src/main.rs index 1e3665d..733f9fa 100644 --- a/yopa-web/src/main.rs +++ b/yopa-web/src/main.rs @@ -3,35 +3,27 @@ extern crate actix_web; #[macro_use] extern crate log; -use std::borrow::Borrow; use std::collections::HashMap; use std::ops::Deref; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; use actix_session::CookieSession; -use actix_web::{App, get, HttpRequest, HttpResponse, HttpServer, post, Responder, web}; -use actix_web::http::StatusCode; -use actix_web::web::{scope, service}; +use actix_web::{web, App, HttpResponse, HttpServer}; use actix_web_static_files; use actix_web_static_files::ResourceFiles as StaticFiles; -use include_dir::Dir; use log::LevelFilter; use once_cell::sync::Lazy; -use parking_lot::Mutex; use rand::Rng; use tera::Tera; +use yopa::insert::{InsertObj, InsertRel, InsertValue}; use yopa::{Storage, TypedValue}; use crate::tera_ext::TeraExt; -use yopa::insert::{InsertObj, InsertValue, InsertRel}; -mod utils; -mod tera_ext; mod routes; mod session_ext; +mod tera_ext; +mod utils; // Embed static files include!(concat!(env!("OUT_DIR"), "/static_files.rs")); @@ -45,47 +37,52 @@ pub(crate) static TERA: Lazy = Lazy::new(|| { // Special filter for the TypedValue map use serde_json::Value; - tera.register_filter("print_typed_value", |v: &Value, _: &HashMap| -> tera::Result { - if v.is_null() { - return Ok(v.clone()); - } - if let Value::Object(map) = v { - if let Some((_, v)) = map.iter().next() { + tera.register_filter( + "print_typed_value", + |v: &Value, _: &HashMap| -> tera::Result { + if v.is_null() { return Ok(v.clone()); } - } - Err(tera::Error::msg("Expected nonenmpty object")) - }); + if let Value::Object(map) = v { + if let Some((_, v)) = map.iter().next() { + return Ok(v.clone()); + } + } + Err(tera::Error::msg("Expected nonenmpty object")) + }, + ); // opt(checked=foo.is_checked) - tera.register_function("opt", |args: &HashMap| -> tera::Result { - if args.len() != 1 { - return Err("Expected 1 argument".into()); - } - match args.iter().nth(0) { - Some((name, &Value::Bool(true))) => { - Ok(Value::String(name.clone())) - }, - Some((_, &Value::Bool(false))) => { - Ok(Value::Null) - }, - _ => Err("Expected bool argument".into()), - } - }); + tera.register_function( + "opt", + |args: &HashMap| -> tera::Result { + if args.len() != 1 { + return Err("Expected 1 argument".into()); + } + match args.iter().nth(0) { + Some((name, &Value::Bool(true))) => Ok(Value::String(name.clone())), + Some((_, &Value::Bool(false))) => Ok(Value::Null), + _ => Err("Expected bool argument".into()), + } + }, + ); // selected(val=foo.color,opt="Red") - tera.register_function("selected", |args: &HashMap| -> tera::Result { - match (args.get("val"), args.get("opt")) { - (Some(v), Some(w)) => { - if v == w { - Ok(Value::String("selected".into())) - } else { - Ok(Value::Null) + tera.register_function( + "selected", + |args: &HashMap| -> tera::Result { + match (args.get("val"), args.get("opt")) { + (Some(v), Some(w)) => { + if v == w { + Ok(Value::String("selected".into())) + } else { + Ok(Value::Null) + } } - }, - _ => Err("Expected val and opt args".into()), - } - }); + _ => Err("Expected val and opt args".into()), + } + }, + ); // TODO need to inject HttpRequest::url_for() into tera context, but it then can't be accessed by the functions. // tera.register_function("url_for", |args: HashMap| -> tera::Result { @@ -117,19 +114,14 @@ async fn main() -> std::io::Result<()> { debug!("Session key: {:?}", session_key); HttpServer::new(move || { - let static_files = StaticFiles::new("/static", included_static_files()) - .do_not_resolve_defaults(); + let static_files = + StaticFiles::new("/static", included_static_files()).do_not_resolve_defaults(); App::new() /* Middlewares */ - .wrap( - CookieSession::signed(&session_key) - .secure(false) - ) - + .wrap(CookieSession::signed(&session_key).secure(false)) /* Bind shared objects */ .app_data(yopa_store.clone()) - /* Routes */ .service(routes::index) .service(routes::takeout) @@ -163,13 +155,15 @@ async fn main() -> std::io::Result<()> { .service(routes::objects::delete) // .service(static_files) - .default_service(web::to(|| HttpResponse::NotFound().body("File or endpoint not found"))) + .default_service(web::to(|| { + HttpResponse::NotFound().body("File or endpoint not found") + })) }) - .bind("127.0.0.1:8080")? - .run().await + .bind("127.0.0.1:8080")? + .run() + .await } - fn init_yopa() -> YopaStoreWrapper { let mut store = Storage::new(); @@ -177,107 +171,123 @@ fn init_yopa() -> YopaStoreWrapper { use yopa::model; use yopa::DataType; - let id_recipe = store.define_object(model::ObjectModel { - id: Default::default(), - name: "Recipe".to_string(), - }).unwrap(); - - let id_book = store.define_object(model::ObjectModel { - id: Default::default(), - name: "Book".to_string(), - }).unwrap(); - - let id_ing = store.define_object(model::ObjectModel { - id: Default::default(), - name: "Ingredient".to_string(), - }).unwrap(); - - let val_descr = store.define_property(model::PropertyModel { - id: Default::default(), - object: id_recipe, - name: "description".to_string(), - optional: true, - multiple: true, - data_type: DataType::String, - default: TypedValue::String("".into()), - }).unwrap(); - - store.define_property(model::PropertyModel { - id: Default::default(), - object: id_book, - name: "author".to_string(), - optional: true, - multiple: true, - data_type: DataType::String, - default: TypedValue::String("Pepa Novák".into()), - }).unwrap(); - - let rel_book_id = store.define_relation(model::RelationModel { - id: Default::default(), - object: id_recipe, - name: "book reference".to_string(), - reciprocal_name: "recipes".to_string(), - optional: true, - multiple: true, - related: id_book, - }).unwrap(); - - let page = store.define_property(model::PropertyModel { - id: Default::default(), - object: rel_book_id, - name: "page".to_string(), - optional: true, - multiple: false, - data_type: DataType::Integer, - default: TypedValue::Integer(0), - }).unwrap(); - - store.define_relation(model::RelationModel { - id: Default::default(), - object: id_recipe, - name: "related recipe".to_string(), - reciprocal_name: "related recipe".to_string(), - optional: true, - multiple: true, - related: id_recipe, - }).unwrap(); - - let book1 = store.insert_object(InsertObj { - model: id_book, - name: "Book 1".to_string(), - values: vec![], - relations: vec![] - }).unwrap(); - - store.insert_object(InsertObj { - model: id_book, - name: "Book 2".to_string(), - values: vec![], - relations: vec![] - }).unwrap(); - - store.insert_object(InsertObj { - model: id_recipe, - name: "Recipe1".to_string(), - values: vec![ - InsertValue { + let id_recipe = store + .define_object(model::ObjectModel { + id: Default::default(), + name: "Recipe".to_string(), + }) + .unwrap(); + + let id_book = store + .define_object(model::ObjectModel { + id: Default::default(), + name: "Book".to_string(), + }) + .unwrap(); + + let _id_ing = store + .define_object(model::ObjectModel { + id: Default::default(), + name: "Ingredient".to_string(), + }) + .unwrap(); + + let val_descr = store + .define_property(model::PropertyModel { + id: Default::default(), + object: id_recipe, + name: "description".to_string(), + optional: true, + multiple: true, + data_type: DataType::String, + default: TypedValue::String("".into()), + }) + .unwrap(); + + store + .define_property(model::PropertyModel { + id: Default::default(), + object: id_book, + name: "author".to_string(), + optional: true, + multiple: true, + data_type: DataType::String, + default: TypedValue::String("Pepa Novák".into()), + }) + .unwrap(); + + let rel_book_id = store + .define_relation(model::RelationModel { + id: Default::default(), + object: id_recipe, + name: "book reference".to_string(), + reciprocal_name: "recipes".to_string(), + optional: true, + multiple: true, + related: id_book, + }) + .unwrap(); + + let page = store + .define_property(model::PropertyModel { + id: Default::default(), + object: rel_book_id, + name: "page".to_string(), + optional: true, + multiple: false, + data_type: DataType::Integer, + default: TypedValue::Integer(0), + }) + .unwrap(); + + store + .define_relation(model::RelationModel { + id: Default::default(), + object: id_recipe, + name: "related recipe".to_string(), + reciprocal_name: "related recipe".to_string(), + optional: true, + multiple: true, + related: id_recipe, + }) + .unwrap(); + + let book1 = store + .insert_object(InsertObj { + model: id_book, + name: "Book 1".to_string(), + values: vec![], + relations: vec![], + }) + .unwrap(); + + store + .insert_object(InsertObj { + model: id_book, + name: "Book 2".to_string(), + values: vec![], + relations: vec![], + }) + .unwrap(); + + store + .insert_object(InsertObj { + model: id_recipe, + name: "Recipe1".to_string(), + values: vec![InsertValue { model: val_descr, - value: TypedValue::String("Bla bla bla".into()) - } - ], - relations: vec![ - InsertRel { + value: TypedValue::String("Bla bla bla".into()), + }], + relations: vec![InsertRel { model: rel_book_id, related: book1, - values: vec![ - InsertValue { - model: page, - value: TypedValue::Integer(15) - } - ] - } - ] - }).unwrap(); + values: vec![InsertValue { + model: page, + value: TypedValue::Integer(15), + }], + }], + }) + .unwrap(); web::Data::new(tokio::sync::RwLock::new(store)) } diff --git a/yopa-web/src/routes.rs b/yopa-web/src/routes.rs index e347a12..e44de4f 100644 --- a/yopa-web/src/routes.rs +++ b/yopa-web/src/routes.rs @@ -1,28 +1,17 @@ -use std::fmt::{Debug, Display}; -use std::ops::{DerefMut, Deref}; -use std::str::FromStr; +use std::ops::Deref; use actix_session::Session; -use actix_web::{HttpRequest, HttpResponse, Responder, web}; -use actix_web::http::header::IntoHeaderValue; -use serde::{Deserialize, Serialize}; - -use yopa::{DataType, ID, Storage, StorageError, TypedValue}; -use yopa::model::{ObjectModel, PropertyModel, RelationModel}; - -use crate::session_ext::SessionExt; -use crate::TERA; -use crate::tera_ext::TeraExt; -use crate::routes::models::relation::RelationModelDisplay; -use crate::routes::models::object::ObjectModelDisplay; +use actix_web::{HttpResponse, Responder}; pub(crate) mod models; pub(crate) mod objects; #[get("/")] -pub(crate) async fn index(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result { - objects::list_inner(session, store) - .await +pub(crate) async fn index( + session: Session, + store: crate::YopaStoreWrapper, +) -> actix_web::Result { + objects::list_inner(session, store).await } #[get("/takeout")] diff --git a/yopa-web/src/routes/models.rs b/yopa-web/src/routes/models.rs index 8c941f6..7340e46 100644 --- a/yopa-web/src/routes/models.rs +++ b/yopa-web/src/routes/models.rs @@ -1,17 +1,20 @@ -use actix_session::Session; -use actix_web::Responder; -use crate::routes::models::relation::RelationModelDisplay; use crate::routes::models::object::ObjectModelDisplay; +use crate::routes::models::relation::RelationModelDisplay; use crate::session_ext::SessionExt; -use crate::TERA; use crate::tera_ext::TeraExt; +use crate::TERA; +use actix_session::Session; +use actix_web::Responder; pub(crate) mod object; -pub(crate) mod relation; pub(crate) mod property; +pub(crate) mod relation; #[get("/models")] -pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result { +pub(crate) async fn list( + session: Session, + store: crate::YopaStoreWrapper, +) -> actix_web::Result { let rg = store.read().await; let models_iter = rg.get_object_models(); @@ -24,31 +27,37 @@ pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> ac let mut models = vec![]; for om in models_iter { - let mut relations = model_relations.remove(&om.id).unwrap_or_default(); - let mut relations = relations.into_iter().map(|rm| { - let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default(); - rprops.sort_by_key(|m| &m.name); + let relations = model_relations.remove(&om.id).unwrap_or_default(); + let mut relations = relations + .into_iter() + .map(|rm| { + let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default(); + rprops.sort_by_key(|m| &m.name); - RelationModelDisplay { - model: rm, - related_name: rg.get_model_name(rm.related), - properties: rprops, - } - }).collect::>(); + RelationModelDisplay { + model: rm, + related_name: rg.get_model_name(rm.related), + properties: rprops, + } + }) + .collect::>(); relations.sort_by_key(|d| &d.model.name); // Relations coming INTO this model - let mut reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default(); - let mut reciprocal_relations = reciprocal_relations.into_iter().map(|rm| { - let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default(); - rprops.sort_by_key(|m| &m.name); + let reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default(); + let mut reciprocal_relations = reciprocal_relations + .into_iter() + .map(|rm| { + let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default(); + rprops.sort_by_key(|m| &m.name); - RelationModelDisplay { - model: rm, - related_name: rg.get_model_name(rm.object), - properties: rprops, - } - }).collect::>(); + RelationModelDisplay { + model: rm, + related_name: rg.get_model_name(rm.object), + properties: rprops, + } + }) + .collect::>(); reciprocal_relations.sort_by_key(|d| &d.model.reciprocal_name); let mut properties = model_props.remove(&om.id).unwrap_or_default(); diff --git a/yopa-web/src/routes/models/object.rs b/yopa-web/src/routes/models/object.rs index c1d0052..740f326 100644 --- a/yopa-web/src/routes/models/object.rs +++ b/yopa-web/src/routes/models/object.rs @@ -1,15 +1,15 @@ use actix_session::Session; -use actix_web::{Responder, web}; +use actix_web::{web, Responder}; use serde::{Deserialize, Serialize}; -use yopa::ID; use yopa::model::{ObjectModel, PropertyModel}; +use yopa::ID; use crate::routes::models::relation::RelationModelDisplay; use crate::session_ext::SessionExt; -use crate::TERA; use crate::tera_ext::TeraExt; use crate::utils::redirect; +use crate::TERA; #[derive(Serialize, Debug)] pub(crate) struct ObjectModelDisplay<'a> { @@ -60,7 +60,7 @@ pub(crate) async fn create( Err(e) => { warn!("Error creating model: {}", e); session.flash_error(e.to_string()); - session.set("old", form); + session.set("old", form).unwrap(); redirect("/model/object/create") } } @@ -76,7 +76,8 @@ pub(crate) async fn update_form( session.render_flash(&mut context); let rg = store.read().await; - let model = rg.get_object_model(*model_id) + let model = rg + .get_object_model(*model_id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; // Re-fill old values @@ -114,13 +115,12 @@ pub(crate) async fn update( Err(e) => { warn!("Error updating model: {}", e); session.flash_error(e.to_string()); - session.set("old", form); + session.set("old", form).unwrap(); redirect(format!("/model/object/update/{}", id)) } } } - #[get("/model/object/delete/{id}")] pub(crate) async fn delete( id: web::Path, @@ -128,7 +128,10 @@ pub(crate) async fn delete( session: Session, ) -> actix_web::Result { let mut wg = store.write().await; - match wg.undefine_object(id.parse().map_err(|e| actix_web::error::ErrorBadRequest(e))?) { + match wg.undefine_object( + id.parse() + .map_err(|e| actix_web::error::ErrorBadRequest(e))?, + ) { Ok(om) => { debug!("Object model deleted, redirecting to root"); session.flash_success(format!("Object model \"{}\" deleted.", om.name)); diff --git a/yopa-web/src/routes/models/property.rs b/yopa-web/src/routes/models/property.rs index 807b4d8..c1f5be3 100644 --- a/yopa-web/src/routes/models/property.rs +++ b/yopa-web/src/routes/models/property.rs @@ -1,15 +1,15 @@ use actix_session::Session; -use actix_web::{Responder, web}; -use serde::{Serialize, Deserialize}; +use actix_web::{web, Responder}; +use serde::{Deserialize, Serialize}; -use yopa::{DataType, ID, TypedValue}; -use yopa::model::{PropertyModel, RelationModel}; +use yopa::model::PropertyModel; +use yopa::{DataType, TypedValue, ID}; +use crate::routes::models::relation::ObjectOrRelationModelDisplay; use crate::session_ext::SessionExt; -use crate::TERA; use crate::tera_ext::TeraExt; -use crate::utils::{ParseOrBadReq, redirect}; -use crate::routes::models::relation::ObjectOrRelationModelDisplay; +use crate::utils::{redirect, ParseOrBadReq}; +use crate::TERA; #[get("/model/property/create/{object_id}")] pub(crate) async fn create_form( @@ -26,14 +26,17 @@ pub(crate) async fn create_form( if let Ok(Some(form)) = session.take::("old") { context.insert("old", &form); } else { - context.insert("old", &PropertyModelCreateForm { - object: Default::default(), - name: "".to_string(), - optional: true, - multiple: false, - data_type: DataType::String, - default: "".to_string() - }); + context.insert( + "old", + &PropertyModelCreateForm { + object: Default::default(), + name: "".to_string(), + optional: true, + multiple: false, + data_type: DataType::String, + default: "".to_string(), + }, + ); } debug!("ID = {}", object_id); @@ -60,7 +63,7 @@ pub(crate) async fn create_form( TERA.build_response("models/property_create", &context) } -#[derive(Serialize,Deserialize)] +#[derive(Serialize, Deserialize)] pub(crate) struct PropertyModelCreateForm { pub object: ID, pub name: String, @@ -74,20 +77,19 @@ pub(crate) struct PropertyModelCreateForm { pub default: String, } -fn parse_default(data_type : DataType, default : String) -> Result { +fn parse_default(data_type: DataType, default: String) -> Result { Ok(match data_type { - DataType::String => { - TypedValue::String(default.into()) - } + DataType::String => TypedValue::String(default.into()), DataType::Integer => { if default.is_empty() { TypedValue::Integer(0) } else { // TODO better error reporting - TypedValue::Integer(default.parse() - .map_err(|_| { - format!("Error parsing \"{}\" as integer", default) - })?) + TypedValue::Integer( + default + .parse() + .map_err(|_| format!("Error parsing \"{}\" as integer", default))?, + ) } } DataType::Decimal => { @@ -95,10 +97,11 @@ fn parse_default(data_type : DataType, default : String) -> Result { @@ -106,9 +109,8 @@ fn parse_default(data_type : DataType, default : String) -> Result { warn!("{}", msg); session.flash_error(msg); - session.set("old", &form); + session.set("old", &form).unwrap(); return redirect(format!("/model/property/create/{}", form.object)); } }; @@ -153,7 +155,7 @@ pub(crate) async fn create( Err(e) => { warn!("Error creating property model: {}", e); session.flash_error(e.to_string()); - session.set("old", &form); + session.set("old", &form).unwrap(); redirect(format!("/model/property/create/{}", form.object)) } } @@ -193,7 +195,6 @@ pub(crate) struct PropertyModelEditForm { pub default: String, } - #[get("/model/property/update/{model_id}")] pub(crate) async fn update_form( model_id: web::Path, @@ -204,7 +205,8 @@ pub(crate) async fn update_form( session.render_flash(&mut context); let rg = store.read().await; - let model = rg.get_property_model(*model_id) + let model = rg + .get_property_model(*model_id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; // Re-fill old values @@ -239,7 +241,7 @@ pub(crate) async fn update( Err(msg) => { warn!("{}", msg); session.flash_error(msg); - session.set("old", form); + session.set("old", form).unwrap(); return redirect(format!("/model/property/update/{}", id)); } }; @@ -261,7 +263,7 @@ pub(crate) async fn update( Err(e) => { warn!("Error updating model: {}", e); session.flash_error(e.to_string()); - session.set("old", form); + session.set("old", form).unwrap(); redirect(format!("/model/relation/update/{}", id)) } } diff --git a/yopa-web/src/routes/models/relation.rs b/yopa-web/src/routes/models/relation.rs index e33b209..e21a910 100644 --- a/yopa-web/src/routes/models/relation.rs +++ b/yopa-web/src/routes/models/relation.rs @@ -1,14 +1,14 @@ use actix_session::Session; -use actix_web::{Responder, web}; -use serde::{Serialize, Deserialize}; +use actix_web::{web, Responder}; +use serde::{Deserialize, Serialize}; -use yopa::ID; use yopa::model::{PropertyModel, RelationModel}; +use yopa::ID; use crate::session_ext::SessionExt; -use crate::TERA; use crate::tera_ext::TeraExt; -use crate::utils::{ParseOrBadReq, redirect}; +use crate::utils::{redirect, ParseOrBadReq}; +use crate::TERA; #[derive(Serialize, Debug)] pub(crate) struct RelationModelDisplay<'a> { @@ -33,17 +33,21 @@ pub(crate) async fn create_form( if let Ok(Some(form)) = session.take::("old") { context.insert("old", &form); } else { - context.insert("old", &RelationModelCreateForm { - object: Default::default(), - name: "".to_string(), - reciprocal_name: "".to_string(), - optional: false, - multiple: false, - related: Default::default() - }); + context.insert( + "old", + &RelationModelCreateForm { + object: Default::default(), + name: "".to_string(), + reciprocal_name: "".to_string(), + optional: false, + multiple: false, + related: Default::default(), + }, + ); } - let object = rg.get_object_model(object_id.parse_or_bad_request()?) + let object = rg + .get_object_model(object_id.parse_or_bad_request()?) .ok_or_else(|| actix_web::error::ErrorNotFound("No such source object"))?; let mut models: Vec<_> = rg.get_object_models().collect(); @@ -56,7 +60,7 @@ pub(crate) async fn create_form( TERA.build_response("models/relation_create", &context) } -#[derive(Serialize,Deserialize)] +#[derive(Serialize, Deserialize)] pub(crate) struct RelationModelCreateForm { pub object: ID, pub name: String, @@ -93,7 +97,7 @@ pub(crate) async fn create( Err(e) => { warn!("Error creating relation model: {}", e); session.flash_error(e.to_string()); - session.set("old", &form); + session.set("old", &form).unwrap(); redirect(format!("/model/relation/create/{}", form.object)) } } @@ -126,7 +130,7 @@ pub(crate) async fn delete( } } -#[derive(Serialize,Deserialize)] +#[derive(Serialize, Deserialize)] pub(crate) struct RelationModelEditForm { pub name: String, pub reciprocal_name: String, @@ -146,7 +150,8 @@ pub(crate) async fn update_form( session.render_flash(&mut context); let rg = store.read().await; - let model = rg.get_relation_model(*model_id) + let model = rg + .get_relation_model(*model_id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; // Re-fill old values @@ -181,7 +186,7 @@ pub(crate) async fn update( reciprocal_name: form.reciprocal_name.to_string(), optional: form.optional, multiple: form.multiple, - related: Default::default() // dummy + related: Default::default(), // dummy }) { Ok(_id) => { debug!("Relation updated, redirecting to root"); @@ -191,7 +196,7 @@ pub(crate) async fn update( Err(e) => { warn!("Error updating model: {}", e); session.flash_error(e.to_string()); - session.set("old", form); + session.set("old", form).unwrap(); redirect(format!("/model/relation/update/{}", id)) } } diff --git a/yopa-web/src/routes/objects.rs b/yopa-web/src/routes/objects.rs index fa86043..74e5dec 100644 --- a/yopa-web/src/routes/objects.rs +++ b/yopa-web/src/routes/objects.rs @@ -1,21 +1,20 @@ -use actix_session::Session; -use actix_web::{Responder, web, HttpResponse}; use crate::session_ext::SessionExt; -use crate::routes::models::object::ObjectModelForm; -use crate::TERA; +use actix_session::Session; +use actix_web::{web, HttpResponse, Responder}; + use crate::tera_ext::TeraExt; -use yopa::{ID, model, Storage, data, TypedValue}; -use yopa::data::Object; -use serde::{Serialize,Deserialize}; -use yopa::insert::{InsertObj, InsertValue}; use crate::utils::redirect; -use actix_web::web::Json; -use serde_json::Value; -use itertools::Itertools; -use yopa::model::{ObjectModel, PropertyModel, RelationModel}; +use crate::TERA; +use serde::Serialize; +use yopa::data::Object; +use yopa::insert::InsertObj; +use yopa::{data, model, Storage, ID}; + use heck::TitleCase; -use std::collections::HashMap; +use itertools::Itertools; use json_dotpath::DotPaths; +use std::collections::HashMap; +use yopa::model::{ObjectModel, PropertyModel, RelationModel}; use yopa::update::UpdateObj; // we only need references here, Context serializes everything to Value. @@ -28,7 +27,7 @@ pub struct Schema<'a> { pub prop_models: Vec<&'a model::PropertyModel>, } -#[derive(Serialize,Debug,Clone)] +#[derive(Serialize, Debug, Clone)] pub struct ObjectCreateData<'a> { pub model_id: ID, pub schema: Schema<'a>, @@ -39,14 +38,15 @@ pub struct ObjectCreateData<'a> { pub(crate) async fn create_form( model_id: web::Path, store: crate::YopaStoreWrapper, - session: Session + session: Session, ) -> actix_web::Result { let mut context = tera::Context::new(); session.render_flash(&mut context); let rg = store.read().await; - let model = rg.get_object_model(*model_id) + let model = rg + .get_object_model(*model_id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; context.insert("model", model); @@ -58,19 +58,20 @@ pub(crate) async fn create_form( TERA.build_response("objects/object_create", &context) } -fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result { - let model = rg.get_object_model(model_id) +fn prepare_object_create_data(rg: &Storage, model_id: ID) -> actix_web::Result { + let model = rg + .get_object_model(model_id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; - let relations : Vec<_> = rg.get_relation_models_for_object_model(model.id).collect(); + let relations: Vec<_> = rg.get_relation_models_for_object_model(model.id).collect(); - let mut prop_object_ids : Vec = relations.iter().map(|r| r.id).collect(); + let mut prop_object_ids: Vec = relations.iter().map(|r| r.id).collect(); prop_object_ids.push(model.id); prop_object_ids.sort(); prop_object_ids.dedup(); - let mut related_ids : Vec<_> = relations.iter().map(|r| r.related).collect(); + let mut related_ids: Vec<_> = relations.iter().map(|r| r.related).collect(); related_ids.sort(); related_ids.dedup(); @@ -80,9 +81,11 @@ fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result schema: Schema { obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here rel_models: relations, - prop_models: rg.get_property_models_for_parents(prop_object_ids).collect() + prop_models: rg + .get_property_models_for_parents(prop_object_ids) + .collect(), }, - objects: rg.get_objects_of_type(related_ids).collect() + objects: rg.get_objects_of_type(related_ids).collect(), }) } @@ -120,30 +123,35 @@ pub(crate) async fn create( #[derive(Debug, Serialize, Clone)] struct ModelWithObjects<'a> { - model : &'a ObjectModel, - objects: Vec<&'a Object> + model: &'a ObjectModel, + objects: Vec<&'a Object>, } #[get("/objects")] -pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result { +pub(crate) async fn list( + session: Session, + store: crate::YopaStoreWrapper, +) -> actix_web::Result { list_inner(session, store).await } -pub(crate) async fn list_inner(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result { +pub(crate) async fn list_inner( + session: Session, + store: crate::YopaStoreWrapper, +) -> actix_web::Result { let rg = store.read().await; let mut objects_by_model = rg.get_grouped_objects(); - let mut models : Vec<_> = rg.get_object_models() + let models: Vec<_> = rg + .get_object_models() .sorted_by_key(|m| &m.name) .map(|model| { let mut objects = objects_by_model.remove(&model.id).unwrap_or_default(); objects.sort_by_key(|o| &o.name); - ModelWithObjects { - model, - objects - } - }).collect(); + ModelWithObjects { model, objects } + }) + .collect(); let mut ctx = tera::Context::new(); ctx.insert("models", &models); @@ -152,31 +160,30 @@ pub(crate) async fn list_inner(session: Session, store: crate::YopaStoreWrapper) TERA.build_response("objects/index", &ctx) } -#[derive(Debug,Serialize)] +#[derive(Debug, Serialize)] struct PropertyView<'a> { - model : &'a PropertyModel, - values: Vec<&'a yopa::data::Value> + model: &'a PropertyModel, + values: Vec<&'a yopa::data::Value>, } -#[derive(Debug,Serialize)] +#[derive(Debug, Serialize)] struct RelationView<'a> { - model : &'a RelationModel, + model: &'a RelationModel, related_name: &'a str, - instances: Vec> + instances: Vec>, } -#[derive(Debug,Serialize)] +#[derive(Debug, Serialize)] struct RelationInstanceView<'a> { related: &'a Object, - properties: Vec> + properties: Vec>, } - #[get("/object/detail/{id}")] pub(crate) async fn detail( id: web::Path, store: crate::YopaStoreWrapper, - session: Session + session: Session, ) -> actix_web::Result { let object_id = *id; @@ -185,18 +192,22 @@ pub(crate) async fn detail( let rg = store.read().await; - let object = rg.get_object(object_id) + let object = rg + .get_object(object_id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?; - let model = rg.get_object_model(object.model) + let model = rg + .get_object_model(object.model) .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; context.insert("object", object); context.insert("model", model); context.insert("kind", &rg.get_model_name(object.model)); - let mut relations = rg.get_relations_for_object(object_id).collect_vec(); - let reci_relations = rg.get_reciprocal_relations_for_object(object_id).collect_vec(); + let relations = rg.get_relations_for_object(object_id).collect_vec(); + let reci_relations = rg + .get_reciprocal_relations_for_object(object_id) + .collect_vec(); // values by parent ID let mut ids_to_get_values_for = relations.iter().map(|r| r.id).collect_vec(); @@ -207,15 +218,17 @@ pub(crate) async fn detail( // object's own properties { - let mut object_values_by_model = grouped_values - .remove(&object_id).unwrap_or_default().into_iter() + let object_values_by_model = grouped_values + .remove(&object_id) + .unwrap_or_default() + .into_iter() .into_group_map_by(|value| value.model); let mut view_object_properties = vec![]; for (prop_model_id, values) in object_values_by_model { view_object_properties.push(PropertyView { model: rg.get_property_model(prop_model_id).unwrap(), - values + values, }) } @@ -226,7 +239,8 @@ pub(crate) async fn detail( // go through relations { - let grouped_relations = relations.iter() + let grouped_relations = relations + .iter() .into_group_map_by(|relation| relation.model); let mut relation_views = vec![]; @@ -236,18 +250,20 @@ pub(crate) async fn detail( for rel in relations { let related_obj = match rg.get_object(rel.related) { None => continue, - Some(obj) => obj + Some(obj) => obj, }; - let mut rel_values_by_model = grouped_values - .remove(&rel.id).unwrap_or_default().into_iter() + let rel_values_by_model = grouped_values + .remove(&rel.id) + .unwrap_or_default() + .into_iter() .into_group_map_by(|value| value.model); let mut view_rel_properties = vec![]; for (prop_model_id, values) in rel_values_by_model { view_rel_properties.push(PropertyView { model: rg.get_property_model(prop_model_id).unwrap(), - values + values, }) } @@ -255,7 +271,7 @@ pub(crate) async fn detail( instances.push(RelationInstanceView { related: related_obj, - properties: view_rel_properties + properties: view_rel_properties, }) } @@ -264,7 +280,7 @@ pub(crate) async fn detail( relation_views.push(RelationView { model: rg.get_relation_model(model_id).unwrap(), related_name: rg.get_model_name(model_id), - instances + instances, }) } @@ -275,7 +291,8 @@ pub(crate) async fn detail( // near-copypasta for reciprocal { - let grouped_relations = reci_relations.iter() + let grouped_relations = reci_relations + .iter() .into_group_map_by(|relation| relation.model); let mut relation_views = vec![]; @@ -285,18 +302,20 @@ pub(crate) async fn detail( for rel in relations { let related_obj = match rg.get_object(rel.object) { None => continue, - Some(obj) => obj + Some(obj) => obj, }; - let mut rel_values_by_model = grouped_values - .remove(&rel.id).unwrap_or_default().into_iter() + let rel_values_by_model = grouped_values + .remove(&rel.id) + .unwrap_or_default() + .into_iter() .into_group_map_by(|value| value.model); let mut view_rel_properties = vec![]; for (prop_model_id, values) in rel_values_by_model { view_rel_properties.push(PropertyView { model: rg.get_property_model(prop_model_id).unwrap(), - values + values, }) } @@ -304,7 +323,7 @@ pub(crate) async fn detail( instances.push(RelationInstanceView { related: related_obj, - properties: view_rel_properties + properties: view_rel_properties, }) } @@ -313,7 +332,7 @@ pub(crate) async fn detail( relation_views.push(RelationView { model: rg.get_relation_model(model_id).unwrap(), related_name: rg.get_model_name(model_id), - instances + instances, }) } @@ -325,16 +344,19 @@ pub(crate) async fn detail( TERA.build_response("objects/object_detail", &context) } -#[derive(Serialize,Debug,Clone)] +#[derive(Serialize, Debug, Clone)] struct EnrichedObject<'a> { id: ID, model: ID, name: String, - values: HashMap>, + values: HashMap< + String, /* ID but as string so serde will stop exploding */ + Vec<&'a data::Value>, + >, relations: HashMap>>, } -#[derive(Serialize,Debug,Clone)] +#[derive(Serialize, Debug, Clone)] struct EnrichedRelation<'a> { id: ID, object: ID, @@ -347,17 +369,19 @@ struct EnrichedRelation<'a> { pub(crate) async fn update_form( id: web::Path, store: crate::YopaStoreWrapper, - session: Session + session: Session, ) -> actix_web::Result { let mut context = tera::Context::new(); session.render_flash(&mut context); let rg = store.read().await; - let object = rg.get_object(*id) + let object = rg + .get_object(*id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?; - let model = rg.get_object_model(object.model) + let model = rg + .get_object_model(object.model) .ok_or_else(|| actix_web::error::ErrorNotFound("Object has no model"))?; // maybe its useful,idk @@ -370,47 +394,71 @@ pub(crate) async fn update_form( let mut relation_map = HashMap::new(); // Some properties may have no values, so we first check what IDs to expect - let prop_ids = create_data.schema.prop_models.iter() - .filter(|p| p.object == model.id).map(|p| p.id).collect_vec(); + let prop_ids = create_data + .schema + .prop_models + .iter() + .filter(|p| p.object == model.id) + .map(|p| p.id) + .collect_vec(); - let mut values_grouped = rg.get_values_for_object(*id) - .into_group_map_by(|v| v.model); + let mut values_grouped = rg.get_values_for_object(*id).into_group_map_by(|v| v.model); prop_ids.into_iter().for_each(|id| { - value_map.insert(id.to_string(), values_grouped.remove(&id).unwrap_or_default()); + value_map.insert( + id.to_string(), + values_grouped.remove(&id).unwrap_or_default(), + ); }); { // Some properties may have no values, so we first check what IDs to expect - let relation_model_ids = create_data.schema.rel_models.iter() + let relation_model_ids = create_data + .schema + .rel_models + .iter() .filter(|p| p.object == model.id) - .map(|p| p.id).collect_vec(); + .map(|p| p.id) + .collect_vec(); let relations = rg.get_relations_for_object(*id).collect_vec(); let relation_ids = relations.iter().map(|r| r.id).collect_vec(); - let mut relations_grouped_by_model = relations.iter() + let mut relations_grouped_by_model = relations + .iter() .into_group_map_by(|relation| relation.model); - let mut property_models_grouped_by_parent = rg.get_grouped_prop_models_for_parents(relation_model_ids.clone()); + let mut property_models_grouped_by_parent = + rg.get_grouped_prop_models_for_parents(relation_model_ids.clone()); - let mut relation_values_grouped_by_instance = rg.get_grouped_values_for_objects(relation_ids); + let mut relation_values_grouped_by_instance = + rg.get_grouped_values_for_objects(relation_ids); for rel_model_id in relation_model_ids { - let relations = relations_grouped_by_model.remove(&rel_model_id).unwrap_or_default(); + let relations = relations_grouped_by_model + .remove(&rel_model_id) + .unwrap_or_default(); let mut instances = vec![]; - let prop_models_for_relation = property_models_grouped_by_parent.remove(&rel_model_id).unwrap_or_default(); + let prop_models_for_relation = property_models_grouped_by_parent + .remove(&rel_model_id) + .unwrap_or_default(); for rel in relations { let mut relation_values_map = HashMap::new(); // values keyed by model - let mut rel_values = relation_values_grouped_by_instance.remove(&rel.id).unwrap_or_default().into_iter() + let mut rel_values = relation_values_grouped_by_instance + .remove(&rel.id) + .unwrap_or_default() + .into_iter() .into_group_map_by(|relation| relation.model); prop_models_for_relation.iter().for_each(|prop_model| { - relation_values_map.insert(prop_model.id.to_string(), rel_values.remove(&prop_model.id).unwrap_or_default()); + relation_values_map.insert( + prop_model.id.to_string(), + rel_values.remove(&prop_model.id).unwrap_or_default(), + ); }); instances.push(EnrichedRelation { @@ -418,7 +466,7 @@ pub(crate) async fn update_form( object: rel.object, model: rel.model, related: rel.related, - values: relation_values_map + values: relation_values_map, }); } @@ -433,7 +481,7 @@ pub(crate) async fn update_form( model: object.model, name: object.name.clone(), values: value_map, - relations: relation_map + relations: relation_map, }; form.dot_remove("model_id").unwrap(); @@ -443,10 +491,9 @@ pub(crate) async fn update_form( TERA.build_response("objects/object_update", &context) } - #[post("/object/update/{id}")] pub(crate) async fn update( - id: web::Path, + _id: web::Path, form: web::Json, store: crate::YopaStoreWrapper, session: Session, @@ -457,7 +504,8 @@ pub(crate) async fn update( let form = form.into_inner(); let name = form.name.clone(); - let object = wg.get_object(form.id) + let object = wg + .get_object(form.id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?; let model_name = wg.get_model_name(object.model).to_owned().to_title_case(); diff --git a/yopa-web/src/tera_ext.rs b/yopa-web/src/tera_ext.rs index 22ee1f2..bdd2e01 100644 --- a/yopa-web/src/tera_ext.rs +++ b/yopa-web/src/tera_ext.rs @@ -3,9 +3,20 @@ use actix_web::HttpResponse; use include_dir::Dir; use tera::Tera; -fn tera_includedir_walk_folder_inner(collected: &mut Vec<(String, String)>, tera: &mut Tera, dir: &Dir) { +fn tera_includedir_walk_folder_inner( + collected: &mut Vec<(String, String)>, + tera: &mut Tera, + dir: &Dir, +) { for f in dir.files() { - let halves: Vec<_> = f.path().file_name().unwrap().to_str().unwrap().split('.').collect(); + let halves: Vec<_> = f + .path() + .file_name() + .unwrap() + .to_str() + .unwrap() + .split('.') + .collect(); if halves.last().unwrap() != &"tera" { debug!("Bad file: {:?}", f); @@ -30,11 +41,20 @@ fn tera_includedir_walk_folder_inner(collected: &mut Vec<(String, String)>, tera pub(crate) trait TeraExt { fn add_include_dir_templates(&mut self, dir: &Dir) -> tera::Result<()>; - fn build_response(&self, template: &str, context: &tera::Context) -> actix_web::Result { + fn build_response( + &self, + template: &str, + context: &tera::Context, + ) -> actix_web::Result { self.build_err_response(StatusCode::OK, template, context) } - fn build_err_response(&self, code: StatusCode, template: &str, context: &tera::Context) -> actix_web::Result; + fn build_err_response( + &self, + code: StatusCode, + template: &str, + context: &tera::Context, + ) -> actix_web::Result; } impl TeraExt for Tera { @@ -45,10 +65,15 @@ impl TeraExt for Tera { self.add_raw_templates(templates) } - fn build_err_response(&self, code: StatusCode, template: &str, context: &tera::Context) -> actix_web::Result { - let html = self.render(template, context).map_err(|e| { - actix_web::error::ErrorInternalServerError(e) - })?; + fn build_err_response( + &self, + code: StatusCode, + template: &str, + context: &tera::Context, + ) -> actix_web::Result { + let html = self + .render(template, context) + .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; Ok(HttpResponse::build(code).body(html)) } diff --git a/yopa-web/src/utils.rs b/yopa-web/src/utils.rs index c482837..32a81c0 100644 --- a/yopa-web/src/utils.rs +++ b/yopa-web/src/utils.rs @@ -1,7 +1,7 @@ use actix_web::http::header::IntoHeaderValue; use actix_web::HttpResponse; +use std::fmt::{Debug, Display}; use std::str::FromStr; -use std::fmt::{Display, Debug}; pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result { Ok(HttpResponse::SeeOther() @@ -11,29 +11,30 @@ pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result { pub trait ParseOrBadReq { fn parse_or_bad_request(&self) -> actix_web::Result - where T: FromStr, - E: Display + Debug + 'static; + where + T: FromStr, + E: Display + Debug + 'static; } impl ParseOrBadReq for &str { fn parse_or_bad_request(&self) -> actix_web::Result - where T: FromStr, - E: Display + Debug + 'static + where + T: FromStr, + E: Display + Debug + 'static, { - self.parse::() - .map_err(|e| { - error!("Parse error for \"{}\"", self); - actix_web::error::ErrorBadRequest(e) - }) + self.parse::().map_err(|e| { + error!("Parse error for \"{}\"", self); + actix_web::error::ErrorBadRequest(e) + }) } } impl ParseOrBadReq for String { fn parse_or_bad_request(&self) -> actix_web::Result - where T: FromStr, - E: Display + Debug + 'static + where + T: FromStr, + E: Display + Debug + 'static, { - self.as_str() - .parse_or_bad_request() + self.as_str().parse_or_bad_request() } } diff --git a/yopa/src/cool.rs b/yopa/src/cool.rs index 3ba0774..84c983d 100644 --- a/yopa/src/cool.rs +++ b/yopa/src/cool.rs @@ -4,7 +4,10 @@ use std::collections::HashMap; use std::hash::Hash; /// drain_filter() implemented for HashMap. It returns the removed items as a Vec -pub fn map_drain_filter(map: &mut HashMap, filter: impl Fn(&K, &V) -> bool) -> Vec<(K, V)> { +pub fn map_drain_filter( + map: &mut HashMap, + filter: impl Fn(&K, &V) -> bool, +) -> Vec<(K, V)> { let mut removed = vec![]; let mut retain = vec![]; for (k, v) in map.drain() { diff --git a/yopa/src/data.rs b/yopa/src/data.rs index 1e63f54..b7c526e 100644 --- a/yopa/src/data.rs +++ b/yopa/src/data.rs @@ -4,11 +4,11 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; -use crate::ID; -use crate::model::DataType; use crate::id::HaveId; -use std::fmt::{Display, Formatter}; +use crate::model::DataType; +use crate::ID; use std::fmt; +use std::fmt::{Display, Formatter}; /// Value of a particular type #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -40,43 +40,41 @@ impl TypedValue { match (self, ty) { // to string (s @ TypedValue::String(_), DataType::String) => Ok(s), - (TypedValue::Integer(i), DataType::String) => Ok(TypedValue::String(i.to_string().into())), - (TypedValue::Decimal(f), DataType::String) => Ok(TypedValue::String(f.to_string().into())), - (TypedValue::Boolean(b), DataType::String) => Ok(TypedValue::String(Cow::Borrowed(if b { "1" } else { "0" }))), - // to int - (TypedValue::String(s), DataType::Integer) => { - match s.parse::() { - Ok(i) => Ok(TypedValue::Integer(i)), - Err(_) => Err(TypedValue::String(s)) - } + (TypedValue::Integer(i), DataType::String) => { + Ok(TypedValue::String(i.to_string().into())) + } + (TypedValue::Decimal(f), DataType::String) => { + Ok(TypedValue::String(f.to_string().into())) + } + (TypedValue::Boolean(b), DataType::String) => { + Ok(TypedValue::String(Cow::Borrowed(if b { "1" } else { "0" }))) } + // to int + (TypedValue::String(s), DataType::Integer) => match s.parse::() { + Ok(i) => Ok(TypedValue::Integer(i)), + Err(_) => Err(TypedValue::String(s)), + }, (s @ TypedValue::Integer(_), DataType::Integer) => Ok(s), - (TypedValue::Decimal(f), DataType::Integer) => Ok(TypedValue::Integer(f.round() as i64)), - (TypedValue::Boolean(b), DataType::Integer) => Ok(TypedValue::Integer(if b { 1 } else { 0 })), - // to float - (TypedValue::String(s), DataType::Decimal) => { - match s.parse::() { - Ok(i) => Ok(TypedValue::Decimal(i)), - Err(_) => Err(TypedValue::String(s)) - } + (TypedValue::Decimal(f), DataType::Integer) => { + Ok(TypedValue::Integer(f.round() as i64)) } + (TypedValue::Boolean(b), DataType::Integer) => { + Ok(TypedValue::Integer(if b { 1 } else { 0 })) + } + // to float + (TypedValue::String(s), DataType::Decimal) => match s.parse::() { + Ok(i) => Ok(TypedValue::Decimal(i)), + Err(_) => Err(TypedValue::String(s)), + }, (TypedValue::Integer(i), DataType::Decimal) => Ok(TypedValue::Decimal(i as f64)), (d @ TypedValue::Decimal(_), DataType::Decimal) => Ok(d), (e @ TypedValue::Boolean(_), DataType::Decimal) => Err(e), // to bool - (TypedValue::String(s), DataType::Boolean) => { - match &(&s).to_ascii_lowercase()[..] { - "y" | "yes" | "true" | "1" => { - Ok(TypedValue::Boolean(true)) - } - "n" | "no" | "false" | "0" => { - Ok(TypedValue::Boolean(false)) - } - _ => { - Err(TypedValue::String(s)) - } - } - } + (TypedValue::String(s), DataType::Boolean) => match &(&s).to_ascii_lowercase()[..] { + "y" | "yes" | "true" | "1" => Ok(TypedValue::Boolean(true)), + "n" | "no" | "false" | "0" => Ok(TypedValue::Boolean(false)), + _ => Err(TypedValue::String(s)), + }, (TypedValue::Integer(i), DataType::Boolean) => Ok(TypedValue::Boolean(i != 0)), (e @ TypedValue::Decimal(_), DataType::Boolean) => Err(e), (b @ TypedValue::Boolean(_), DataType::Boolean) => Ok(b), @@ -93,78 +91,213 @@ mod tests { #[test] fn test_cast_to_bool() { // Cast to bool - assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::Boolean(true).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::Boolean(false).cast_to(DataType::Boolean)); - - assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::Integer(123).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::Integer(0).cast_to(DataType::Boolean)); - - assert_eq!(Err(TypedValue::Decimal(0.0)), TypedValue::Decimal(0.0).cast_to(DataType::Boolean)); - assert_eq!(Err(TypedValue::Decimal(123.0)), TypedValue::Decimal(123.0).cast_to(DataType::Boolean)); - - assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("true".into()).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("1".into()).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("y".into()).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(true)), TypedValue::String("yes".into()).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("false".into()).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("0".into()).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("n".into()).cast_to(DataType::Boolean)); - assert_eq!(Ok(TypedValue::Boolean(false)), TypedValue::String("no".into()).cast_to(DataType::Boolean)); - assert_eq!(Err(TypedValue::String("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::Boolean)); + assert_eq!( + Ok(TypedValue::Boolean(true)), + TypedValue::Boolean(true).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(false)), + TypedValue::Boolean(false).cast_to(DataType::Boolean) + ); + + assert_eq!( + Ok(TypedValue::Boolean(true)), + TypedValue::Integer(123).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(false)), + TypedValue::Integer(0).cast_to(DataType::Boolean) + ); + + assert_eq!( + Err(TypedValue::Decimal(0.0)), + TypedValue::Decimal(0.0).cast_to(DataType::Boolean) + ); + assert_eq!( + Err(TypedValue::Decimal(123.0)), + TypedValue::Decimal(123.0).cast_to(DataType::Boolean) + ); + + assert_eq!( + Ok(TypedValue::Boolean(true)), + TypedValue::String("true".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(true)), + TypedValue::String("1".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(true)), + TypedValue::String("y".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(true)), + TypedValue::String("yes".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(false)), + TypedValue::String("false".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(false)), + TypedValue::String("0".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(false)), + TypedValue::String("n".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Ok(TypedValue::Boolean(false)), + TypedValue::String("no".into()).cast_to(DataType::Boolean) + ); + assert_eq!( + Err(TypedValue::String("blorg".into())), + TypedValue::String("blorg".into()).cast_to(DataType::Boolean) + ); } #[test] fn test_cast_to_int() { // Cast to bool - assert_eq!(Ok(TypedValue::Integer(1)), TypedValue::Boolean(true).cast_to(DataType::Integer)); - assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Boolean(false).cast_to(DataType::Integer)); + assert_eq!( + Ok(TypedValue::Integer(1)), + TypedValue::Boolean(true).cast_to(DataType::Integer) + ); + assert_eq!( + Ok(TypedValue::Integer(0)), + TypedValue::Boolean(false).cast_to(DataType::Integer) + ); - assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::Integer(123).cast_to(DataType::Integer)); - assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Integer(0).cast_to(DataType::Integer)); + assert_eq!( + Ok(TypedValue::Integer(123)), + TypedValue::Integer(123).cast_to(DataType::Integer) + ); + assert_eq!( + Ok(TypedValue::Integer(0)), + TypedValue::Integer(0).cast_to(DataType::Integer) + ); - assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::Decimal(0.0).cast_to(DataType::Integer)); - assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::Decimal(123.0).cast_to(DataType::Integer)); - assert_eq!(Ok(TypedValue::Integer(-124)), TypedValue::Decimal(-123.7).cast_to(DataType::Integer)); + assert_eq!( + Ok(TypedValue::Integer(0)), + TypedValue::Decimal(0.0).cast_to(DataType::Integer) + ); + assert_eq!( + Ok(TypedValue::Integer(123)), + TypedValue::Decimal(123.0).cast_to(DataType::Integer) + ); + assert_eq!( + Ok(TypedValue::Integer(-124)), + TypedValue::Decimal(-123.7).cast_to(DataType::Integer) + ); - assert_eq!(Ok(TypedValue::Integer(123)), TypedValue::String("123".into()).cast_to(DataType::Integer)); - assert_eq!(Ok(TypedValue::Integer(-123)), TypedValue::String("-123".into()).cast_to(DataType::Integer)); - assert_eq!(Ok(TypedValue::Integer(0)), TypedValue::String("0".into()).cast_to(DataType::Integer)); - assert_eq!(Err(TypedValue::String("123.456".into())), TypedValue::String("123.456".into()).cast_to(DataType::Integer)); - assert_eq!(Err(TypedValue::String("-123.456".into())), TypedValue::String("-123.456".into()).cast_to(DataType::Integer)); + assert_eq!( + Ok(TypedValue::Integer(123)), + TypedValue::String("123".into()).cast_to(DataType::Integer) + ); + assert_eq!( + Ok(TypedValue::Integer(-123)), + TypedValue::String("-123".into()).cast_to(DataType::Integer) + ); + assert_eq!( + Ok(TypedValue::Integer(0)), + TypedValue::String("0".into()).cast_to(DataType::Integer) + ); + assert_eq!( + Err(TypedValue::String("123.456".into())), + TypedValue::String("123.456".into()).cast_to(DataType::Integer) + ); + assert_eq!( + Err(TypedValue::String("-123.456".into())), + TypedValue::String("-123.456".into()).cast_to(DataType::Integer) + ); } #[test] fn test_cast_to_decimal() { // Cast to bool - assert_eq!(Err(TypedValue::Boolean(true)), TypedValue::Boolean(true).cast_to(DataType::Decimal)); - assert_eq!(Err(TypedValue::Boolean(false)), TypedValue::Boolean(false).cast_to(DataType::Decimal)); + assert_eq!( + Err(TypedValue::Boolean(true)), + TypedValue::Boolean(true).cast_to(DataType::Decimal) + ); + assert_eq!( + Err(TypedValue::Boolean(false)), + TypedValue::Boolean(false).cast_to(DataType::Decimal) + ); - assert_eq!(Ok(TypedValue::Decimal(123.0)), TypedValue::Integer(123).cast_to(DataType::Decimal)); - assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::Integer(0).cast_to(DataType::Decimal)); + assert_eq!( + Ok(TypedValue::Decimal(123.0)), + TypedValue::Integer(123).cast_to(DataType::Decimal) + ); + assert_eq!( + Ok(TypedValue::Decimal(0.0)), + TypedValue::Integer(0).cast_to(DataType::Decimal) + ); - assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::Decimal(0.0).cast_to(DataType::Decimal)); - assert_eq!(Ok(TypedValue::Decimal(-123.7)), TypedValue::Decimal(-123.7).cast_to(DataType::Decimal)); + assert_eq!( + Ok(TypedValue::Decimal(0.0)), + TypedValue::Decimal(0.0).cast_to(DataType::Decimal) + ); + assert_eq!( + Ok(TypedValue::Decimal(-123.7)), + TypedValue::Decimal(-123.7).cast_to(DataType::Decimal) + ); - assert_eq!(Ok(TypedValue::Decimal(123.0)), TypedValue::String("123".into()).cast_to(DataType::Decimal)); - assert_eq!(Ok(TypedValue::Decimal(-123.0)), TypedValue::String("-123".into()).cast_to(DataType::Decimal)); - assert_eq!(Ok(TypedValue::Decimal(0.0)), TypedValue::String("0".into()).cast_to(DataType::Decimal)); - assert_eq!(Ok(TypedValue::Decimal(-123.456)), TypedValue::String("-123.456".into()).cast_to(DataType::Decimal)); + assert_eq!( + Ok(TypedValue::Decimal(123.0)), + TypedValue::String("123".into()).cast_to(DataType::Decimal) + ); + assert_eq!( + Ok(TypedValue::Decimal(-123.0)), + TypedValue::String("-123".into()).cast_to(DataType::Decimal) + ); + assert_eq!( + Ok(TypedValue::Decimal(0.0)), + TypedValue::String("0".into()).cast_to(DataType::Decimal) + ); + assert_eq!( + Ok(TypedValue::Decimal(-123.456)), + TypedValue::String("-123.456".into()).cast_to(DataType::Decimal) + ); } #[test] fn test_cast_to_string() { // Cast to bool - assert_eq!(Ok(TypedValue::String("1".into())), TypedValue::Boolean(true).cast_to(DataType::String)); - assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Boolean(false).cast_to(DataType::String)); + assert_eq!( + Ok(TypedValue::String("1".into())), + TypedValue::Boolean(true).cast_to(DataType::String) + ); + assert_eq!( + Ok(TypedValue::String("0".into())), + TypedValue::Boolean(false).cast_to(DataType::String) + ); - assert_eq!(Ok(TypedValue::String("123".into())), TypedValue::Integer(123).cast_to(DataType::String)); - assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Integer(0).cast_to(DataType::String)); + assert_eq!( + Ok(TypedValue::String("123".into())), + TypedValue::Integer(123).cast_to(DataType::String) + ); + assert_eq!( + Ok(TypedValue::String("0".into())), + TypedValue::Integer(0).cast_to(DataType::String) + ); - assert_eq!(Ok(TypedValue::String("0".into())), TypedValue::Decimal(0.0).cast_to(DataType::String)); - assert_eq!(Ok(TypedValue::String("123".into())), TypedValue::Decimal(123.0).cast_to(DataType::String)); - assert_eq!(Ok(TypedValue::String("-123.5".into())), TypedValue::Decimal(-123.5).cast_to(DataType::String)); + assert_eq!( + Ok(TypedValue::String("0".into())), + TypedValue::Decimal(0.0).cast_to(DataType::String) + ); + assert_eq!( + Ok(TypedValue::String("123".into())), + TypedValue::Decimal(123.0).cast_to(DataType::String) + ); + assert_eq!( + Ok(TypedValue::String("-123.5".into())), + TypedValue::Decimal(-123.5).cast_to(DataType::String) + ); - assert_eq!(Ok(TypedValue::String("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::String)); + assert_eq!( + Ok(TypedValue::String("blorg".into())), + TypedValue::String("blorg".into()).cast_to(DataType::String) + ); } } @@ -177,7 +310,7 @@ pub struct Object { /// Object template ID pub model: ID, /// Model name, mainly shown in lists - pub name : String, + pub name: String, } /// Relation between two objects diff --git a/yopa/src/id.rs b/yopa/src/id.rs index 1aebe2d..9205139 100644 --- a/yopa/src/id.rs +++ b/yopa/src/id.rs @@ -42,10 +42,10 @@ mod impl_u64 { } } -#[cfg(feature = "uuid-ids")] -pub use impl_uuid::{ID, next_id, zero_id}; #[cfg(not(feature = "uuid-ids"))] -pub use impl_u64::{ID, next_id, zero_id}; +pub use impl_u64::{next_id, zero_id, ID}; +#[cfg(feature = "uuid-ids")] +pub use impl_uuid::{next_id, zero_id, ID}; /// Something that has ID pub trait HaveId { diff --git a/yopa/src/insert.rs b/yopa/src/insert.rs index 8d5875a..28cb7a5 100644 --- a/yopa/src/insert.rs +++ b/yopa/src/insert.rs @@ -43,13 +43,18 @@ impl InsertRel { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InsertObj { pub model: ID, - pub name : String, + pub name: String, pub values: Vec, pub relations: Vec, } impl InsertObj { - pub fn new(model_id: ID, name : String, values: Vec, relations: Vec) -> Self { + pub fn new( + model_id: ID, + name: String, + values: Vec, + relations: Vec, + ) -> Self { Self { model: model_id, name, @@ -58,4 +63,3 @@ impl InsertObj { } } } - diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs index 3ece77d..18eb0c4 100644 --- a/yopa/src/lib.rs +++ b/yopa/src/lib.rs @@ -1,36 +1,38 @@ -#[macro_use] extern crate serde_json; -#[macro_use] extern crate log; +#[macro_use] +extern crate serde_json; +#[macro_use] +extern crate log; use std::borrow::Cow; -use std::collections::{HashMap}; +use std::collections::HashMap; use itertools::Itertools; use serde::{Deserialize, Serialize}; use thiserror::Error; -use cool::{KVVecToKeysOrValues, map_drain_filter}; -pub use id::ID; +use crate::model::{PropertyModel, RelationModel}; +use cool::{map_drain_filter, KVVecToKeysOrValues}; use id::next_id; +pub use id::ID; use insert::InsertObj; use insert::InsertValue; use model::ObjectModel; -use crate::model::{PropertyModel, RelationModel}; -pub use data::{TypedValue}; -pub use model::{DataType}; -use crate::data::{Object}; +use crate::data::Object; use crate::update::{UpdateObj, UpsertValue}; +pub use data::TypedValue; +pub use model::DataType; -pub mod model; +mod cool; pub mod data; +pub mod id; pub mod insert; +pub mod model; pub mod update; -pub mod id; -mod cool; +mod serde_map_as_list; #[cfg(test)] mod tests; -mod serde_map_as_list; /// Stupid storage with no persistence #[derive(Debug, Default, Serialize, Deserialize, Clone)] @@ -50,7 +52,6 @@ pub struct Storage { values: HashMap, } - #[derive(Debug, Error)] pub enum StorageError { #[error("Referenced {0} does not exist")] @@ -68,11 +69,20 @@ impl Storage { /// Define a data object pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result { if tpl.name.is_empty() { - return Err(StorageError::ConstraintViolation("Name must not be empty".into())); + return Err(StorageError::ConstraintViolation( + "Name must not be empty".into(), + )); } - if self.obj_models.iter().find(|(_, t)| t.name == tpl.name).is_some() { - return Err(StorageError::ConstraintViolation(format!("Object model with the name \"{}\" already exists", tpl.name).into())); + if self + .obj_models + .iter() + .find(|(_, t)| t.name == tpl.name) + .is_some() + { + return Err(StorageError::ConstraintViolation( + format!("Object model with the name \"{}\" already exists", tpl.name).into(), + )); } debug!("Define object model \"{}\"", tpl.name); @@ -85,31 +95,45 @@ impl Storage { /// Define a relation between two data objects pub fn define_relation(&mut self, mut rel: model::RelationModel) -> Result { if rel.name.is_empty() || rel.reciprocal_name.is_empty() { - return Err(StorageError::ConstraintViolation("Names must not be empty".into())); + return Err(StorageError::ConstraintViolation( + "Names must not be empty".into(), + )); } if !self.obj_models.contains_key(&rel.object) { - return Err(StorageError::NotExist(format!("Source object model {}", rel.object).into())); + return Err(StorageError::NotExist( + format!("Source object model {}", rel.object).into(), + )); } if !self.obj_models.contains_key(&rel.related) { - return Err(StorageError::NotExist(format!("Related object model {}", rel.related).into())); + return Err(StorageError::NotExist( + format!("Related object model {}", rel.related).into(), + )); } if let Some((_, colliding)) = self.rel_models.iter().find(|(_, other)| { (other.name == rel.name && other.object == rel.object) // Exact match || (other.name == rel.reciprocal_name && other.object == rel.related) // Our reciprocal name collides with related's own relation name || (other.reciprocal_name == rel.name && other.related == rel.object) // Our name name collides with a reciprocal name on the other relation - || (other.reciprocal_name == rel.reciprocal_name && other.related == rel.related) // Reciprocal names collide for the same destination + || (other.reciprocal_name == rel.reciprocal_name && other.related == rel.related) + // Reciprocal names collide for the same destination }) { return Err(StorageError::ConstraintViolation( - format!("Name collision (\"{}\" / \"{}\") with existing relation (\"{}\" / \"{}\")", - rel.name, rel.reciprocal_name, - colliding.name, colliding.reciprocal_name - ).into())); + format!( + "Name collision (\"{}\" / \"{}\") with existing relation (\"{}\" / \"{}\")", + rel.name, rel.reciprocal_name, colliding.name, colliding.reciprocal_name + ) + .into(), + )); } - debug!("Define relation model \"{}\" from {} to {}, reciprocal name \"{}\"", - rel.name, self.describe_model(rel.object), self.describe_model(rel.related), rel.reciprocal_name); + debug!( + "Define relation model \"{}\" from {} to {}, reciprocal name \"{}\"", + rel.name, + self.describe_model(rel.object), + self.describe_model(rel.related), + rel.reciprocal_name + ); let id = next_id(); rel.id = id; @@ -120,28 +144,51 @@ impl Storage { /// Define a property attached to an object or a relation pub fn define_property(&mut self, mut prop: model::PropertyModel) -> Result { if prop.name.is_empty() { - return Err(StorageError::ConstraintViolation("Name must not be empty".into())); + return Err(StorageError::ConstraintViolation( + "Name must not be empty".into(), + )); } if !self.obj_models.contains_key(&prop.object) { // Maybe it's attached to a relation? if !self.rel_models.contains_key(&prop.object) { - return Err(StorageError::NotExist(format!("Object or relation model {}", prop.object).into())); + return Err(StorageError::NotExist( + format!("Object or relation model {}", prop.object).into(), + )); } } - if self.prop_models.iter().find(|(_, t)| t.object == prop.object && t.name == prop.name).is_some() { + if self + .prop_models + .iter() + .find(|(_, t)| t.object == prop.object && t.name == prop.name) + .is_some() + { return Err(StorageError::ConstraintViolation( - format!("Property with the name \"{}\" already exists on model {}", prop.name, self.describe_model(prop.object)).into())); + format!( + "Property with the name \"{}\" already exists on model {}", + prop.name, + self.describe_model(prop.object) + ) + .into(), + )); } // Ensure the default type is compatible prop.default = match prop.default.clone().cast_to(prop.data_type) { Ok(v) => v, - Err(_) => return Err(StorageError::NotExist(format!("default value {:?} has invalid type", prop.default).into())) + Err(_) => { + return Err(StorageError::NotExist( + format!("default value {:?} has invalid type", prop.default).into(), + )) + } }; - debug!("Define property model \"{}\" of {}", prop.name, self.describe_model(prop.object)); + debug!( + "Define property model \"{}\" of {}", + prop.name, + self.describe_model(prop.object) + ); let id = next_id(); prop.id = id; self.prop_models.insert(id, prop); @@ -153,13 +200,17 @@ impl Storage { return if let Some(t) = self.obj_models.remove(&id) { debug!("Undefine object model \"{}\"", t.name); // Remove relation templates - let removed_relation_ids = map_drain_filter(&mut self.rel_models, |_k, v| v.object == id || v.related == id) - .keys(); + let removed_relation_ids = map_drain_filter(&mut self.rel_models, |_k, v| { + v.object == id || v.related == id + }) + .keys(); debug!("Undefined {} relation models", removed_relation_ids.len()); // Remove related property templates - let removed_prop_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.object == id || removed_relation_ids.contains(&v.object)) - .keys(); + let removed_prop_ids = map_drain_filter(&mut self.prop_models, |_k, v| { + v.object == id || removed_relation_ids.contains(&v.object) + }) + .keys(); debug!("Undefined {} property models", removed_prop_ids.len()); // Remove objects @@ -167,18 +218,24 @@ impl Storage { debug!("Deleted {} objects", removed_objects.len()); // Remove property values - let removed_values = map_drain_filter(&mut self.values, |_k, v| removed_prop_ids.contains(&v.model)); + let removed_values = map_drain_filter(&mut self.values, |_k, v| { + removed_prop_ids.contains(&v.model) + }); debug!("Deleted {} object or relation values", removed_values.len()); // Remove relations - let removed_relations = map_drain_filter(&mut self.relations, |_k, v| removed_relation_ids.contains(&v.model)); + let removed_relations = map_drain_filter(&mut self.relations, |_k, v| { + removed_relation_ids.contains(&v.model) + }); debug!("Deleted {} object relations", removed_relations.len()); // Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with. Ok(t) } else { - Err(StorageError::NotExist(format!("object model {}", id).into())) + Err(StorageError::NotExist( + format!("object model {}", id).into(), + )) }; } @@ -192,17 +249,25 @@ impl Storage { debug!("Deleted {} object relations", removed.len()); // Remove related property templates - let removed_prop_tpl_ids = map_drain_filter(&mut self.prop_models, |_k, v| v.object == id).keys(); - debug!("Undefined {} relation property models", removed_prop_tpl_ids.len()); - - let removed_values = map_drain_filter(&mut self.values, |_k, v| removed_prop_tpl_ids.contains(&v.model)); + let removed_prop_tpl_ids = + map_drain_filter(&mut self.prop_models, |_k, v| v.object == id).keys(); + debug!( + "Undefined {} relation property models", + removed_prop_tpl_ids.len() + ); + + let removed_values = map_drain_filter(&mut self.values, |_k, v| { + removed_prop_tpl_ids.contains(&v.model) + }); debug!("Deleted {} relation values", removed_values.len()); // Related object remain untouched, so there can be a problem with orphans. This is up to the application to deal with. Ok(t) } else { - Err(StorageError::NotExist(format!("relation model {}", id).into())) + Err(StorageError::NotExist( + format!("relation model {}", id).into(), + )) }; } @@ -216,7 +281,9 @@ impl Storage { debug!("Deleted {} values", removed_values.len()); Ok(t) } else { - Err(StorageError::NotExist(format!("property model {}", id).into())) + Err(StorageError::NotExist( + format!("property model {}", id).into(), + )) }; } @@ -253,38 +320,70 @@ impl Storage { let obj_model = match self.obj_models.get(&obj_model_id) { Some(m) => m, - None => return Err(StorageError::NotExist(format!("object model {}", obj_model_id).into())) + None => { + return Err(StorageError::NotExist( + format!("object model {}", obj_model_id).into(), + )) + } }; // validate unique name - if self.objects.iter().find(|(_, o)| o.model == obj_model_id && o.name == insobj.name).is_some() { + if self + .objects + .iter() + .find(|(_, o)| o.model == obj_model_id && o.name == insobj.name) + .is_some() + { return Err(StorageError::ConstraintViolation( - format!("{} named \"{}\" already exists", self.get_model_name(obj_model_id), insobj.name).into())); + format!( + "{} named \"{}\" already exists", + self.get_model_name(obj_model_id), + insobj.name + ) + .into(), + )); } let object_id = next_id(); let object = data::Object { id: object_id, model: obj_model_id, - name: insobj.name + name: insobj.name, }; - let find_values_to_insert = |values: Vec, parent_id : ID, parent_model_id: ID| -> Result, StorageError> { + let find_values_to_insert = |values: Vec, + parent_id: ID, + parent_model_id: ID| + -> Result, StorageError> { let mut values_by_id = values.into_iter().into_group_map_by(|iv| iv.model); let mut values_to_insert = vec![]; - for (id, prop) in self.prop_models.iter().filter(|(_id, p)| p.object == parent_model_id) { + for (id, prop) in self + .prop_models + .iter() + .filter(|(_id, p)| p.object == parent_model_id) + { if let Some(values) = values_by_id.remove(id) { if values.len() > 1 && !prop.multiple { - return Err(StorageError::ConstraintViolation(format!("{} of {} cannot have multiple values", prop, self.describe_model(parent_model_id)).into())); + return Err(StorageError::ConstraintViolation( + format!( + "{} of {} cannot have multiple values", + prop, + self.describe_model(parent_model_id) + ) + .into(), + )); } for val_instance in values { values_to_insert.push(data::Value { id: next_id(), object: parent_id, model: prop.id, - value: val_instance.value.cast_to(prop.data_type) - .map_err(|v| StorageError::ConstraintViolation(format!("{} cannot accept value {:?}", prop, v).into()))?, + value: val_instance.value.cast_to(prop.data_type).map_err(|v| { + StorageError::ConstraintViolation( + format!("{} cannot accept value {:?}", prop, v).into(), + ) + })?, }); } } else { @@ -305,30 +404,49 @@ impl Storage { let mut values_to_insert = find_values_to_insert(insobj.values, object_id, obj_model_id)?; // And now ..... relations! - let mut relations_by_id = insobj.relations.into_iter().into_group_map_by(|ir| ir.model); + let mut relations_by_id = insobj + .relations + .into_iter() + .into_group_map_by(|ir| ir.model); let mut relations_to_insert = vec![]; - for (relation_model_id, relation_model) in self.rel_models.iter().filter(|(_id, r)| r.object == obj_model_id) { + for (relation_model_id, relation_model) in self + .rel_models + .iter() + .filter(|(_id, r)| r.object == obj_model_id) + { if let Some(instances) = relations_by_id.remove(relation_model_id) { if instances.len() > 1 && !relation_model.multiple { - return Err(StorageError::ConstraintViolation(format!("{} of {} cannot be set multiply", relation_model, obj_model).into())); + return Err(StorageError::ConstraintViolation( + format!("{} of {} cannot be set multiply", relation_model, obj_model) + .into(), + )); } for rel_instance in instances { if let Some(related) = self.objects.get(&rel_instance.related) { if related.model != relation_model.related { return Err(StorageError::ConstraintViolation( - format!("{} of {} requires object of type {}, got {}", - relation_model, obj_model, - self.describe_model(relation_model.related), - self.describe_model(related.model)).into())); + format!( + "{} of {} requires object of type {}, got {}", + relation_model, + obj_model, + self.describe_model(relation_model.related), + self.describe_model(related.model) + ) + .into(), + )); } } let relation_id = next_id(); // Relations can have properties - values_to_insert.extend(find_values_to_insert(rel_instance.values, relation_id, *relation_model_id)?); + values_to_insert.extend(find_values_to_insert( + rel_instance.values, + relation_id, + *relation_model_id, + )?); relations_to_insert.push(data::Relation { id: relation_id, @@ -339,7 +457,9 @@ impl Storage { } } else { if !relation_model.optional { - return Err(StorageError::ConstraintViolation(format!("{} is required for {}", relation_model, obj_model).into())); + return Err(StorageError::ConstraintViolation( + format!("{} is required for {}", relation_model, obj_model).into(), + )); } } } @@ -360,92 +480,120 @@ impl Storage { } // Reading - pub fn get_object_models(&self) -> impl Iterator { + pub fn get_object_models(&self) -> impl Iterator { self.obj_models.values() } - pub fn get_object_model(&self, id : ID) -> Option<&ObjectModel> { + pub fn get_object_model(&self, id: ID) -> Option<&ObjectModel> { self.obj_models.get(&id) } - pub fn get_relation_model(&self, id : ID) -> Option<&RelationModel> { + pub fn get_relation_model(&self, id: ID) -> Option<&RelationModel> { self.rel_models.get(&id) } - pub fn get_property_model(&self, id : ID) -> Option<&PropertyModel> { + pub fn get_property_model(&self, id: ID) -> Option<&PropertyModel> { self.prop_models.get(&id) } pub fn get_grouped_prop_models(&self) -> HashMap> { - self.prop_models.values() + self.prop_models + .values() .into_group_map_by(|model| model.object) } - pub fn get_grouped_prop_models_for_parents(&self, parents: Vec) -> HashMap> { - self.prop_models.values() + pub fn get_grouped_prop_models_for_parents( + &self, + parents: Vec, + ) -> HashMap> { + self.prop_models + .values() .filter(|p| parents.contains(&p.object)) .into_group_map_by(|model| model.object) } - pub fn get_relations_for_object(&self, object_id: ID) -> impl Iterator { - self.relations.values() + pub fn get_relations_for_object(&self, object_id: ID) -> impl Iterator { + self.relations + .values() .filter(move |rel| rel.object == object_id) } - pub fn get_reciprocal_relations_for_object(&self, object_id: ID) -> impl Iterator { - self.relations.values() + pub fn get_reciprocal_relations_for_object( + &self, + object_id: ID, + ) -> impl Iterator { + self.relations + .values() .filter(move |rel| rel.related == object_id) } - pub fn get_values_for_object(&self, object_id: ID) -> impl Iterator { - self.values.values() + pub fn get_values_for_object(&self, object_id: ID) -> impl Iterator { + self.values + .values() .filter(move |prop| prop.object == object_id) } - pub fn get_grouped_values_for_objects(&self, parents: Vec) -> HashMap> { - self.values.values() + pub fn get_grouped_values_for_objects( + &self, + parents: Vec, + ) -> HashMap> { + self.values + .values() .filter(move |prop| parents.contains(&prop.object)) .into_group_map_by(|model| model.object) } - pub fn get_relation_models_for_object_model(&self, model_id: ID) -> impl Iterator { - self.rel_models.values() + pub fn get_relation_models_for_object_model( + &self, + model_id: ID, + ) -> impl Iterator { + self.rel_models + .values() .filter(move |model| model.object == model_id) } - pub fn get_property_models_for_parents(&self, parents: Vec) -> impl Iterator { - self.prop_models.values() + pub fn get_property_models_for_parents( + &self, + parents: Vec, + ) -> impl Iterator { + self.prop_models + .values() .filter(move |model| parents.contains(&model.object)) } - pub fn get_objects_of_type(&self, model_ids : Vec) -> impl Iterator { - self.objects.values() + pub fn get_objects_of_type(&self, model_ids: Vec) -> impl Iterator { + self.objects + .values() .filter(move |object| model_ids.contains(&object.model)) } pub fn get_grouped_objects(&self) -> HashMap> { - self.objects.values() + self.objects + .values() .into_group_map_by(|object| object.model) } pub fn get_grouped_relation_models(&self) -> HashMap> { - self.rel_models.values() + self.rel_models + .values() .into_group_map_by(|model| model.object) } pub fn get_grouped_reciprocal_relation_models(&self) -> HashMap> { - self.rel_models.values() + self.rel_models + .values() .into_group_map_by(|model| model.related) } - pub fn get_object(&self, id : ID) -> Option<&Object> { + pub fn get_object(&self, id: ID) -> Option<&Object> { self.objects.get(&id) } // Updates pub fn update_object(&mut self, updobj: UpdateObj) -> Result<(), StorageError> { - let old_object = self.objects.get(&updobj.id) - .ok_or_else(|| StorageError::ConstraintViolation(format!("Object does not exist").into()))?; + let old_object = self.objects.get(&updobj.id).ok_or_else(|| { + StorageError::ConstraintViolation(format!("Object does not exist").into()) + })?; let updated_object_id = old_object.id; let updated_object_model_id = old_object.model; @@ -453,48 +601,92 @@ impl Storage { let obj_model = match self.obj_models.get(&updated_object_model_id) { Some(m) => m, - None => return Err(StorageError::NotExist(format!("object model {}", updated_object_model_id).into())) + None => { + return Err(StorageError::NotExist( + format!("object model {}", updated_object_model_id).into(), + )) + } }; // validate unique name - if self.objects.iter().find(|(_, o)| o.model == updated_object_model_id && o.name == updobj.name && o.id != updated_object_id).is_some() { + if self + .objects + .iter() + .find(|(_, o)| { + o.model == updated_object_model_id + && o.name == updobj.name + && o.id != updated_object_id + }) + .is_some() + { return Err(StorageError::ConstraintViolation( - format!("{} named \"{}\" already exists", self.get_model_name(updated_object_model_id), updobj.name).into())); + format!( + "{} named \"{}\" already exists", + self.get_model_name(updated_object_model_id), + updobj.name + ) + .into(), + )); } // Update the object after everything else is checked - let find_values_to_change = |values: Vec, parent_id : ID, parent_model_id: ID| -> Result<( - // Insert (can overwrite existing, the ID will not change) - Vec, - // Delete - Vec - ), StorageError> { + let find_values_to_change = |values: Vec, + parent_id: ID, + parent_model_id: ID| + -> Result< + ( + // Insert (can overwrite existing, the ID will not change) + Vec, + // Delete + Vec, + ), + StorageError, + > { let mut values_by_model = values.into_iter().into_group_map_by(|iv| iv.model); let mut values_to_insert = vec![]; let mut ids_to_delete = vec![]; - let mut existing_values_by_id = self.values.values() + let mut existing_values_by_id = self + .values + .values() .filter(|v| v.object == parent_id) .into_group_map_by(|v| v.model); - for (prop_model_id, prop) in self.prop_models.iter().filter(|(_id, p)| p.object == parent_model_id) { + for (prop_model_id, prop) in self + .prop_models + .iter() + .filter(|(_id, p)| p.object == parent_model_id) + { if let Some(values) = values_by_model.remove(prop_model_id) { if values.len() > 1 && !prop.multiple { - return Err(StorageError::ConstraintViolation(format!("{} of {} cannot have multiple values", prop, self.describe_model(parent_model_id)).into())); + return Err(StorageError::ConstraintViolation( + format!( + "{} of {} cannot have multiple values", + prop, + self.describe_model(parent_model_id) + ) + .into(), + )); } let updated_ids = values.iter().filter_map(|v| v.id).collect_vec(); - ids_to_delete.extend(existing_values_by_id.remove(&prop.id).unwrap_or_default().into_iter() - .filter(|v| !updated_ids.contains(&v.id)).map(|v| v.id)); + ids_to_delete.extend( + existing_values_by_id + .remove(&prop.id) + .unwrap_or_default() + .into_iter() + .filter(|v| !updated_ids.contains(&v.id)) + .map(|v| v.id), + ); for val_instance in values { values_to_insert.push(data::Value { id: val_instance.id.unwrap_or_else(|| next_id()), object: parent_id, model: prop.id, - value: val_instance.value + value: val_instance.value, }); } } else { @@ -511,42 +703,63 @@ impl Storage { Ok((values_to_insert, ids_to_delete)) }; - let (mut values_to_insert, mut value_ids_to_delete) = find_values_to_change(updobj.values, updated_object_id, updated_object_model_id)?; + let (mut values_to_insert, mut value_ids_to_delete) = + find_values_to_change(updobj.values, updated_object_id, updated_object_model_id)?; // And now ..... relations! - let mut relations_by_model = updobj.relations.into_iter().into_group_map_by(|ir| ir.model); + let mut relations_by_model = updobj + .relations + .into_iter() + .into_group_map_by(|ir| ir.model); let mut relations_to_insert = vec![]; let mut relations_to_delete = vec![]; - let mut existing_relations_by_id = self.relations.values() + let mut existing_relations_by_id = self + .relations + .values() .filter(|v| v.object == updated_object_id) .into_group_map_by(|v| v.model); - let rel_models_by_id = self.rel_models.iter().filter(|(_id, r)| r.object == updated_object_model_id); + let rel_models_by_id = self + .rel_models + .iter() + .filter(|(_id, r)| r.object == updated_object_model_id); for (relation_model_id, relation_model) in rel_models_by_id { let mut updated_ids = vec![]; if let Some(instances) = relations_by_model.remove(relation_model_id) { if instances.len() > 1 && !relation_model.multiple { - return Err(StorageError::ConstraintViolation(format!("{} of {} cannot be set multiply", relation_model, obj_model).into())); + return Err(StorageError::ConstraintViolation( + format!("{} of {} cannot be set multiply", relation_model, obj_model) + .into(), + )); } for rel_instance in instances { if let Some(related) = self.objects.get(&rel_instance.related) { if related.model != relation_model.related { return Err(StorageError::ConstraintViolation( - format!("{} of {} requires object of type {}, got {}", - relation_model, obj_model, - self.describe_model(relation_model.related), - self.describe_model(related.model)).into())); + format!( + "{} of {} requires object of type {}, got {}", + relation_model, + obj_model, + self.describe_model(relation_model.related), + self.describe_model(related.model) + ) + .into(), + )); } } let relation_id = rel_instance.id.unwrap_or_else(|| next_id()); // Relations can have properties - let (ins, del) = find_values_to_change(rel_instance.values, relation_id, *relation_model_id)?; + let (ins, del) = find_values_to_change( + rel_instance.values, + relation_id, + *relation_model_id, + )?; values_to_insert.extend(ins); value_ids_to_delete.extend(del); @@ -561,12 +774,20 @@ impl Storage { } } else { if !relation_model.optional { - return Err(StorageError::ConstraintViolation(format!("{} is required for {}", relation_model, obj_model).into())); + return Err(StorageError::ConstraintViolation( + format!("{} is required for {}", relation_model, obj_model).into(), + )); } } - relations_to_delete.extend(existing_relations_by_id.remove(&relation_model_id).unwrap_or_default().into_iter() - .filter(|rel| !updated_ids.contains(&rel.id)).map(|rel| rel.id)); + relations_to_delete.extend( + existing_relations_by_id + .remove(&relation_model_id) + .unwrap_or_default() + .into_iter() + .filter(|rel| !updated_ids.contains(&rel.id)) + .map(|rel| rel.id), + ); } let obj_mut = self.objects.get_mut(&updated_object_id).unwrap(); @@ -595,26 +816,38 @@ impl Storage { Ok(()) } - pub fn update_object_model(&mut self, model : ObjectModel) -> Result<(), StorageError> { + pub fn update_object_model(&mut self, model: ObjectModel) -> Result<(), StorageError> { if model.name.is_empty() { - return Err(StorageError::ConstraintViolation(format!("Model name must not be empty.").into())); + return Err(StorageError::ConstraintViolation( + format!("Model name must not be empty.").into(), + )); } if !self.obj_models.contains_key(&model.id) { - return Err(StorageError::NotExist(format!("Object model ID {} does not exist.", model.id).into())); + return Err(StorageError::NotExist( + format!("Object model ID {} does not exist.", model.id).into(), + )); } - if let Some(conflict) = self.obj_models.values().find(|m| m.id != model.id && m.name == model.name) { - return Err(StorageError::ConstraintViolation(format!("Object {} already has the name {}", conflict.id, model.name).into())); + if let Some(conflict) = self + .obj_models + .values() + .find(|m| m.id != model.id && m.name == model.name) + { + return Err(StorageError::ConstraintViolation( + format!("Object {} already has the name {}", conflict.id, model.name).into(), + )); } self.obj_models.insert(model.id, model); Ok(()) } - pub fn update_relation_model(&mut self, mut rel : RelationModel) -> Result<(), StorageError> { + pub fn update_relation_model(&mut self, mut rel: RelationModel) -> Result<(), StorageError> { if rel.name.is_empty() || rel.reciprocal_name.is_empty() { - return Err(StorageError::ConstraintViolation(format!("Relation names must not be empty.").into())); + return Err(StorageError::ConstraintViolation( + format!("Relation names must not be empty.").into(), + )); } // Object and Related can't be changed, so we re-fill them from the existing model @@ -622,7 +855,9 @@ impl Storage { rel.object = existing.object; rel.related = existing.related; } else { - return Err(StorageError::NotExist(format!("Relation model ID {} does not exist.", rel.id).into())); + return Err(StorageError::NotExist( + format!("Relation model ID {} does not exist.", rel.id).into(), + )); } // Difficult checks ... @@ -647,25 +882,44 @@ impl Storage { pub fn update_property_model(&mut self, mut prop: PropertyModel) -> Result<(), StorageError> { if prop.name.is_empty() { - return Err(StorageError::ConstraintViolation(format!("Property name must not be empty.").into())); + return Err(StorageError::ConstraintViolation( + format!("Property name must not be empty.").into(), + )); } // Object can't be changed, so we re-fill them from the existing model if let Some(existing) = self.prop_models.get(&prop.id) { prop.object = existing.object; } else { - return Err(StorageError::NotExist(format!("Property model ID {} does not exist.", prop.id).into())); + return Err(StorageError::NotExist( + format!("Property model ID {} does not exist.", prop.id).into(), + )); } - if self.prop_models.iter().find(|(_, t)| t.object == prop.object && t.name == prop.name && t.id != prop.id).is_some() { + if self + .prop_models + .iter() + .find(|(_, t)| t.object == prop.object && t.name == prop.name && t.id != prop.id) + .is_some() + { return Err(StorageError::ConstraintViolation( - format!("property with the name \"{}\" already exists on {}", prop.name, self.describe_model(prop.object)).into())); + format!( + "property with the name \"{}\" already exists on {}", + prop.name, + self.describe_model(prop.object) + ) + .into(), + )); } // Ensure the default type is compatible prop.default = match prop.default.clone().cast_to(prop.data_type) { Ok(v) => v, - Err(_) => return Err(StorageError::NotExist(format!("default value {:?} has invalid type", prop.default).into())) + Err(_) => { + return Err(StorageError::NotExist( + format!("default value {:?} has invalid type", prop.default).into(), + )) + } }; self.prop_models.insert(prop.id, prop); @@ -677,12 +931,16 @@ impl Storage { return if let Some(t) = self.objects.remove(&id) { debug!("Delete object \"{}\"", t.name); // Remove relation templates - let removed_relation_ids = map_drain_filter(&mut self.relations, |_k, v| v.object == id || v.related == id) - .keys(); + let removed_relation_ids = map_drain_filter(&mut self.relations, |_k, v| { + v.object == id || v.related == id + }) + .keys(); debug!("Deleted {} object relations", removed_relation_ids.len()); // Remove values - let removed_values = map_drain_filter(&mut self.values, |_k, v| removed_relation_ids.contains(&v.object) || v.object == id); + let removed_values = map_drain_filter(&mut self.values, |_k, v| { + removed_relation_ids.contains(&v.object) || v.object == id + }); debug!("Deleted {} object values", removed_values.len()); Ok(t) diff --git a/yopa/src/model.rs b/yopa/src/model.rs index e124f9a..b2bd33a 100644 --- a/yopa/src/model.rs +++ b/yopa/src/model.rs @@ -1,7 +1,7 @@ //! Data model structs and enums -use std::fmt::{Display, Formatter}; use std::fmt; +use std::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize}; diff --git a/yopa/src/serde_map_as_list.rs b/yopa/src/serde_map_as_list.rs index 717ccb9..ed545d7 100644 --- a/yopa/src/serde_map_as_list.rs +++ b/yopa/src/serde_map_as_list.rs @@ -1,19 +1,19 @@ //! Serialize a HashMap of ID-keyed structs that also contain the ID inside //! to a plain list, and also back when deserializing. -use std::collections::HashMap; +use crate::id::HaveId; use crate::ID; -use serde::{Serialize, Deserializer, Deserialize}; +use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; -use serde::de::{Visitor, SeqAccess}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::collections::HashMap; use std::fmt; -use crate::id::HaveId; use std::marker::PhantomData; pub fn serialize(x: &HashMap, s: S) -> Result - where - S: serde::Serializer, - X: Serialize + HaveId +where + S: serde::Serializer, + X: Serialize + HaveId, { let mut seq = s.serialize_seq(Some(x.len()))?; for p in x.values() { @@ -23,16 +23,16 @@ pub fn serialize(x: &HashMap, s: S) -> Result } pub fn deserialize<'de, D, X>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - X: Deserialize<'de> + HaveId +where + D: Deserializer<'de>, + X: Deserialize<'de> + HaveId, { deserializer.deserialize_seq(SeqToMap(Default::default())) } pub struct SeqToMap(PhantomData); -impl<'de, X : Deserialize<'de> + HaveId> Visitor<'de> for SeqToMap { +impl<'de, X: Deserialize<'de> + HaveId> Visitor<'de> for SeqToMap { type Value = HashMap; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -40,8 +40,8 @@ impl<'de, X : Deserialize<'de> + HaveId> Visitor<'de> for SeqToMap { } fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, + where + A: SeqAccess<'de>, { let mut map = HashMap::::new(); diff --git a/yopa/src/tests.rs b/yopa/src/tests.rs index e9e1130..a93a54c 100644 --- a/yopa/src/tests.rs +++ b/yopa/src/tests.rs @@ -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()); } diff --git a/yopa/src/update.rs b/yopa/src/update.rs index e508bd4..5b20242 100644 --- a/yopa/src/update.rs +++ b/yopa/src/update.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{ID, TypedValue}; +use crate::{TypedValue, ID}; /// Update or insert a value #[derive(Debug, Clone, Serialize, Deserialize)] @@ -27,12 +27,12 @@ pub struct UpsertRelation { } /// Update an existing object -#[derive(Deserialize,Debug)] +#[derive(Deserialize, Debug)] pub struct UpdateObj { pub id: ID, pub name: String, pub values: Vec, - pub relations: Vec + pub relations: Vec, } #[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(); } }