flash messages etc

master
Ondřej Hruška 3 years ago
parent 8b87fd0079
commit 9706cf0a62
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 194
      Cargo.lock
  2. 2
      yopa-web/Cargo.toml
  3. 15
      yopa-web/resources/static/style.css
  4. 19
      yopa-web/resources/templates/_layout.html.tera
  5. 8
      yopa-web/resources/templates/_macros.html.tera
  6. 20
      yopa-web/resources/templates/index.html.tera
  7. 8
      yopa-web/resources/templates/model_create.html.tera
  8. 207
      yopa-web/src/main.rs
  9. 59
      yopa-web/src/routes.rs
  10. 39
      yopa-web/src/session_ext.rs
  11. 24
      yopa/src/lib.rs
  12. 2
      yopa/src/model.rs
  13. 1
      yopa/src/tests.rs

194
Cargo.lock generated

@ -47,7 +47,7 @@ dependencies = [
"actix-service",
"actix-threadpool",
"actix-utils",
"base64",
"base64 0.13.0",
"bitflags",
"brotli2",
"bytes 0.5.6",
@ -150,6 +150,22 @@ dependencies = [
"pin-project 0.4.27",
]
[[package]]
name = "actix-session"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe0c795741b7a1a6f8eb46680c5a0f6f53484d054226a7af9a86195dfc2c14d"
dependencies = [
"actix-service",
"actix-web",
"bytes 0.5.6",
"derive_more",
"futures-util",
"serde",
"serde_json",
"time 0.2.25",
]
[[package]]
name = "actix-testing"
version = "1.0.1"
@ -290,6 +306,60 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "aead"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "aes"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
dependencies = [
"aes-soft",
"aesni",
"cipher",
]
[[package]]
name = "aes-gcm"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
dependencies = [
"aead",
"aes",
"cipher",
"ctr",
"ghash",
"subtle",
]
[[package]]
name = "aes-soft"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "aesni"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher",
"opaque-debug 0.3.0",
]
[[package]]
name = "aho-corasick"
version = "0.7.15"
@ -332,7 +402,7 @@ dependencies = [
"actix-http",
"actix-rt",
"actix-service",
"base64",
"base64 0.13.0",
"bytes 0.5.6",
"cfg-if 1.0.0",
"derive_more",
@ -366,6 +436,12 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]]
name = "base64"
version = "0.13.0"
@ -517,6 +593,15 @@ dependencies = [
"parse-zoneinfo",
]
[[package]]
name = "cipher"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "const_fn"
version = "0.4.5"
@ -529,7 +614,13 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
dependencies = [
"aes-gcm",
"base64 0.12.3",
"hkdf",
"hmac",
"percent-encoding",
"rand 0.7.3",
"sha2",
"time 0.2.25",
"version_check",
]
@ -546,6 +637,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "cpuid-bool"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -566,6 +663,25 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "crypto-mac"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "ctr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
dependencies = [
"cipher",
]
[[package]]
name = "derive_more"
version = "0.99.11"
@ -817,6 +933,16 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "ghash"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
dependencies = [
"opaque-debug 0.3.0",
"polyval",
]
[[package]]
name = "gimli"
version = "0.23.0"
@ -897,6 +1023,26 @@ dependencies = [
"libc",
]
[[package]]
name = "hkdf"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
dependencies = [
"digest 0.9.0",
"hmac",
]
[[package]]
name = "hmac"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
dependencies = [
"crypto-mac",
"digest 0.9.0",
]
[[package]]
name = "hostname"
version = "0.3.1"
@ -1425,6 +1571,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polyval"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd"
dependencies = [
"cpuid-bool 0.2.0",
"opaque-debug 0.3.0",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
@ -1696,7 +1853,7 @@ checksum = "f4b312c3731e3fe78a185e6b9b911a7aa715b8e31cce117975219aab2acf285d"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpuid-bool",
"cpuid-bool 0.1.2",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
@ -1707,6 +1864,19 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpuid-bool 0.1.2",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
@ -1817,6 +1987,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "subtle"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.60"
@ -2183,6 +2359,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "universal-hash"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
dependencies = [
"generic-array 0.14.4",
"subtle",
]
[[package]]
name = "url"
version = "2.2.0"
@ -2375,12 +2561,14 @@ dependencies = [
name = "yopa-web"
version = "0.1.0"
dependencies = [
"actix-session",
"actix-web",
"actix-web-static-files",
"include_dir",
"log",
"once_cell",
"parking_lot",
"rand 0.8.3",
"serde",
"serde_json",
"simple-logging",

@ -15,11 +15,13 @@ serde_json = "1"
log = "0.4.14"
simple-logging = "2.0.2"
actix-web = "3"
actix-session = "0.4.0"
parking_lot = "0.11.1"
include_dir = "0.6.0"
tera = "1.6.1"
actix-web-static-files = "3.0"
once_cell = "1.5.2"
rand = "0.8.3"
tokio = { version="0.2.6", features=["full"] }

@ -205,3 +205,18 @@ textarea:focus,
li {
padding-bottom: .5rem;
}
.toast {
border: 1px solid black;
border-radius: 5px;
padding: .5rem;
margin: .5rem 0;
}
.toast.error {
border-color: #dc143c;
}
.toast.success {
border-color: #32cd32;
}

@ -2,17 +2,30 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock title %} &bull; YOPA</title>
<title>{% block title -%}{%- endblock title %} &bull; YOPA</title>
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="/static/taggle.css">
<script src="/static/taggle.min.js"></script>
</head>
<body>
<nav class="top-nav">
{% block nav %}{% endblock %}
{%- block nav -%}{%- endblock -%}
</nav>
<div class="content">
{% block content %}{% endblock %}
{%- if flash_error -%}
<div class="toast error">
{{ flash_error }}
</div>
{%- endif -%}
{%- if flash_success -%}
<div class="toast success">
{{ flash_success }}
</div>
{%- endif -%}
{%- block content -%}{%- endblock -%}
</div>
</body>
</html>

@ -0,0 +1,8 @@
{% macro describe_property(prop) %}
{{prop.name}}, {{prop.data_type}}
{%- if prop.default -%}
, default: "{{prop.default | print_typed_value}}"
{%- endif -%}
{%- if prop.optional %}, OPTIONAL{% endif %}
{%- if prop.multiple %}, MULTIPLE{% endif %}
{% endmacro input %}

@ -1,4 +1,5 @@
{% extends "_layout" %}
{% import "_macros" as macros %}
{% block title -%}
Index
@ -26,12 +27,7 @@
<ul>
{% for prop in model.properties %}
<li>
{{prop.name}}, {{prop.data_type}}
{%- if prop.default -%}
, default: "{{prop.default | print_typed_value}}"
{%- endif -%}
{%- if prop.optional %}, OPTIONAL{% endif %}
{%- if prop.multiple %}, MULTIPLE{% endif %}
{{ macros::describe_property(prop=prop) }}
</li>
{% endfor %}
</ul>
@ -44,19 +40,17 @@
<ul>
{% for rel in model.relations %}
<li>
<span title="{{rel.model.id}}">"{{rel.model.name}}", pointing to: <i>{{rel.related_name}}</i></span><br>
<span title="{{rel.model.id}}">"{{rel.model.name}}", pointing to: <i>{{rel.related_name}}</i></span>
{%- if rel.model.optional %}, OPTIONAL{% endif %}
{%- if rel.model.multiple %}, MULTIPLE{% endif %}
<br>
{% if rel.properties %}
Properties:
<ul>
{% for prop in rel.properties %}
<li title="{{prop.id}}">
{{prop.name}}, {{prop.data_type}}
{%- if prop.default -%}
, default: "{{prop.default | print_typed_value}}"
{%- endif -%}
{%- if prop.optional %}, OPTIONAL{% endif %}
{%- if prop.multiple %}, MULTIPLE{% endif %}
{{ macros::describe_property(prop=prop) }}
</li>
{% endfor %}
</ul>

@ -13,14 +13,6 @@ Define object
<h1>Define new object model</h1>
<form action="/model/object/create" method="POST">
<label for="parent">Parent:</label>
<select name="parent" id="parent">
<option value="">No parent</option>
{%- for model in all_models %}
<option value="{{model.id}}">{{model.name}}</option>
{%- endfor %}
</select><br>
<label for="name">Name:</label>
<input type="text" id="name" name="name"><br>

@ -18,9 +18,13 @@ use std::borrow::Borrow;
use std::ops::Deref;
use yopa::{Storage, TypedValue};
use std::collections::HashMap;
use actix_session::CookieSession;
use rand::Rng;
use actix_web_static_files::ResourceFiles as StaticFiles;
mod tera_ext;
mod routes;
mod session_ext;
// Embed static files
include!(concat!(env!("OUT_DIR"), "/static_files.rs"));
@ -30,7 +34,7 @@ static TEMPLATES: include_dir::Dir = include_dir::include_dir!("./resources/temp
pub(crate) static TERA : Lazy<Tera> = Lazy::new(|| {
let mut tera = Tera::default();
tera.add_include_dir_templates(&TEMPLATES);
tera.add_include_dir_templates(&TEMPLATES).unwrap();
// Special filter for the TypedValue map
use serde_json::Value;
@ -46,6 +50,16 @@ pub(crate) static TERA : Lazy<Tera> = Lazy::new(|| {
Err(tera::Error::msg("Expected nonenmpty object"))
});
// 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> {
// match args.get("name") {
// Some(Value::String(s)) => {
// let r =
// },
// _ => Err("Expected string argument".into()),
// }
// });
tera
});
@ -58,98 +72,117 @@ async fn main() -> std::io::Result<()> {
// Ensure the lazy ref is initialized early (to catch template bugs ASAP)
let _ = TERA.deref();
let database : YopaStoreWrapper = {
let mut store = Storage::new();
// Seed the store with some dummy data for view development
use yopa::model;
use yopa::DataType;
let id_recipe = store.define_object(model::ObjectModel {
id: Default::default(),
name: "Recipe".to_string(),
parent: None
}).unwrap();
let id_book = store.define_object(model::ObjectModel {
id: Default::default(),
name: "Book".to_string(),
parent: None
}).unwrap();
let id_ing = store.define_object(model::ObjectModel {
id: Default::default(),
name: "Ingredient".to_string(),
parent: None
}).unwrap();
store.define_property(model::PropertyModel {
id: Default::default(),
object: id_recipe,
name: "name".to_string(),
optional: false,
multiple: true,
data_type: DataType::String,
default: None
}).unwrap();
store.define_property(model::PropertyModel {
id: Default::default(),
object: id_book,
name: "title".to_string(),
optional: false,
multiple: false,
data_type: DataType::String,
default: None
}).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: Some(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(),
optional: true,
multiple: true,
related: id_book
}).unwrap();
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: None
}).unwrap();
store.define_relation(model::RelationModel {
id: Default::default(),
object: id_recipe,
name: "related recipe".to_string(),
optional: true,
multiple: true,
related: id_recipe
}).unwrap();
web::Data::new(tokio::sync::RwLock::new(store))
};
let yopa_store: YopaStoreWrapper = init_yopa();
let mut session_key = [0u8; 32];
rand::thread_rng().fill(&mut session_key);
debug!("Session key: {:?}", session_key);
HttpServer::new(move || {
let static_files = actix_web_static_files::ResourceFiles::new("/static", included_static_files())
let static_files = StaticFiles::new("/static", included_static_files())
.do_not_resolve_defaults();
App::new()
.app_data(database.clone())
/* Middlewares */
.wrap(
CookieSession::signed(&session_key)
.secure(false)
)
/* Bind shared objects */
.app_data(yopa_store.clone())
/* Routes */
.service(routes::index)
.service(routes::object_model_create_form)
.service(routes::object_model_create)
.service(static_files)
.default_service(web::to(|| HttpResponse::NotFound().body("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();
// Seed the store with some dummy data for view development
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();
store.define_property(model::PropertyModel {
id: Default::default(),
object: id_recipe,
name: "name".to_string(),
optional: false,
multiple: true,
data_type: DataType::String,
default: None
}).unwrap();
store.define_property(model::PropertyModel {
id: Default::default(),
object: id_book,
name: "title".to_string(),
optional: false,
multiple: false,
data_type: DataType::String,
default: None
}).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: Some(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(),
optional: true,
multiple: true,
related: id_book
}).unwrap();
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: None
}).unwrap();
store.define_relation(model::RelationModel {
id: Default::default(),
object: id_recipe,
name: "related recipe".to_string(),
optional: true,
multiple: true,
related: id_recipe
}).unwrap();
web::Data::new(tokio::sync::RwLock::new(store))
}

@ -1,9 +1,12 @@
use actix_web::{web, HttpRequest, Responder};
use actix_web::{web, HttpRequest, Responder, HttpResponse};
use crate::TERA;
use crate::tera_ext::TeraExt;
use yopa::Storage;
use serde::Serialize;
use yopa::model::{PropertyModel, RelationModel};
use yopa::{Storage, StorageError};
use serde::{Deserialize, Serialize};
use yopa::model::{PropertyModel, RelationModel, ObjectModel};
use std::ops::DerefMut;
use actix_session::Session;
use crate::session_ext::SessionExt;
#[derive(Serialize, Debug)]
struct ObjectModelDisplay<'a> {
@ -21,7 +24,7 @@ struct RelationModelDisplay<'a> {
}
#[get("/")]
pub(crate) async fn index(req: HttpRequest, store : crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> {
pub(crate) async fn index(session : Session, store : crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> {
let rg = store.read().await;
@ -63,6 +66,52 @@ pub(crate) async fn index(req: HttpRequest, store : crate::YopaStoreWrapper) ->
let mut ctx = tera::Context::new();
ctx.insert("models", &models);
session.render_flash(&mut ctx);
TERA.build_response("index", &ctx)
}
#[get("/model/object/create")]
pub(crate) async fn object_model_create_form(session : Session) -> actix_web::Result<impl Responder> {
let mut context = tera::Context::new();
session.render_flash(&mut context);
TERA.build_response("model_create", &context)
}
#[derive(Deserialize)]
pub(crate) struct ObjectModelCreate {
pub name : String,
}
#[post("/model/object/create")]
pub(crate) async fn object_model_create(
form : web::Form<ObjectModelCreate>,
store : crate::YopaStoreWrapper,
session : Session
) -> actix_web::Result<impl Responder> {
let mut wg = store.write().await;
let form = form.into_inner();
match wg.define_object(ObjectModel {
id: Default::default(),
name: form.name.clone()
}) {
Ok(_id) => {
debug!("Object created, redirecting to root");
session.flash_success(format!("Object model \"{}\" created.", form.name));
Ok(HttpResponse::SeeOther()
.header("location", "/")
.finish())
}
Err(e) => {
warn!("Error creating model: {}", e);
session.flash_error(e.to_string());
// Redirect back
Ok(HttpResponse::SeeOther()
.header("location", "/model/object/create")
.finish())
}
}
}

@ -0,0 +1,39 @@
use serde::de::DeserializeOwned;
use actix_session::Session;
pub trait SessionExt {
/// Get a `value` from the session.
fn take<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, actix_web::error::Error>;
fn render_flash(&self, context : &mut tera::Context);
fn flash_error(&self, msg : impl AsRef<str>);
fn flash_success(&self, msg : impl AsRef<str>);
}
impl SessionExt for Session {
fn take<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, actix_web::error::Error> {
let val = self.get(key);
self.remove(key); // Always remove, even if parsing failed
Ok(val?)
}
fn render_flash(&self, context : &mut tera::Context) {
if let Ok(Some(msg)) = self.take::<String>("flash_error") {
context.insert("flash_error", &msg);
}
if let Ok(Some(msg)) = self.take::<String>("flash_success") {
context.insert("flash_success", &msg);
}
}
fn flash_error(&self, msg : impl AsRef<str>) {
self.set("flash_error", msg.as_ref()).unwrap();
}
fn flash_success(&self, msg : impl AsRef<str>) {
self.set("flash_success", msg.as_ref()).unwrap();
}
}

@ -1,4 +1,5 @@
#[macro_use] extern crate serde_json;
#[macro_use] extern crate log;
use std::borrow::Cow;
use std::collections::HashMap;
@ -63,16 +64,15 @@ impl Storage {
/// Define a data object
pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> {
if let Some(pid) = tpl.parent {
if !self.obj_models.contains_key(&pid) {
return Err(StorageError::NotExist(format!("parent object model {}", pid).into()));
}
if tpl.name.is_empty() {
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()));
}
debug!("Define object model \"{}\"", tpl.name);
let id = next_id();
tpl.id = id;
self.obj_models.insert(id, tpl);
@ -81,6 +81,10 @@ impl Storage {
/// Define a relation between two data objects
pub fn define_relation(&mut self, mut rel: model::RelationModel) -> Result<ID, StorageError> {
if rel.name.is_empty() {
return Err(StorageError::ConstraintViolation("name must not be empty".into()));
}
if !self.obj_models.contains_key(&rel.object) {
return Err(StorageError::NotExist(format!("source object model {}", rel.object).into()));
}
@ -93,6 +97,7 @@ impl Storage {
format!("relation with the name \"{}\" and on model {} already exists", rel.name, rel.object).into()));
}
debug!("Define relation model \"{}\" from {} to {}", rel.name, self.describe_model(rel.object), self.describe_model(rel.related));
let id = next_id();
rel.id = id;
self.rel_models.insert(id, rel);
@ -101,6 +106,10 @@ impl Storage {
/// Define a property attached to an object or a relation
pub fn define_property(&mut self, mut prop: model::PropertyModel) -> Result<ID, StorageError> {
if prop.name.is_empty() {
return Err(StorageError::ConstraintViolation("name must not be empty".into()));
}
if !self.obj_models.contains_key(&prop.object) {
// Maybe it's attached to a relation?
if !self.rel_models.contains_key(&prop.object) {
@ -121,6 +130,7 @@ impl Storage {
});
}
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);
@ -130,6 +140,7 @@ impl Storage {
/// Delete an object definition and associated data
pub fn undefine_object(&mut self, id: ID) -> Result<ObjectModel, StorageError> {
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();
@ -158,6 +169,8 @@ impl Storage {
/// Delete a relation definition and associated data
pub fn undefine_relation(&mut self, id: ID) -> Result<model::RelationModel, StorageError> {
return if let Some(t) = self.rel_models.remove(&id) {
debug!("Undefine relation model \"{}\"", t.name);
// Remove relations
let _ = map_drain_filter(&mut self.relations, |_k, v| v.model == id).keys();
@ -177,6 +190,8 @@ impl Storage {
/// Delete a property definition and associated data
pub fn undefine_property(&mut self, id: ID) -> Result<model::PropertyModel, StorageError> {
return if let Some(t) = self.prop_models.remove(&id) {
debug!("Undefine property model \"{}\"", t.name);
// Remove relations
let _ = map_drain_filter(&mut self.properties, |_k, v| v.model == id);
Ok(t)
@ -214,6 +229,7 @@ impl Storage {
/// Insert object with relations, validating the data model constraints
pub fn insert_object(&mut self, insobj: InsertObj) -> Result<ID, StorageError> {
let obj_model_id = insobj.model_id;
debug!("Insert object {:?}", insobj);
let obj_model = match self.obj_models.get(&obj_model_id) {
Some(m) => m,

@ -23,8 +23,6 @@ pub struct ObjectModel {
pub id: ID,
/// Template name, unique within the database
pub name: String,
/// Parent object template ID
pub parent: Option<ID>,
}
/// Relation between templates

@ -5,7 +5,6 @@ fn test1() {
let model = crate::model::ObjectModel {
id: Default::default(),
name: "Name".to_string(),
parent: None
};
println!("{}", serde_json::to_string_pretty(&model).unwrap());

Loading…
Cancel
Save