diff --git a/Cargo.lock b/Cargo.lock index 9a8c5b8..5aa6b75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,17 +18,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "getrandom" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "instant" version = "0.1.9" @@ -240,22 +229,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "winapi" version = "0.3.9" @@ -290,7 +263,6 @@ dependencies = [ "serde", "serde_json", "thiserror", - "uuid", ] [[package]] diff --git a/src/main.rs b/src/main.rs index 74eec8b..5c5fb4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,10 @@ use log::LevelFilter; -use yopa; use yopa::model; use yopa::model::DataType; -use crate::yopa::insert::{InsertObj, InsertValue, InsertRel}; -use crate::yopa::data::TypedValue; +use yopa::insert::{InsertObj, InsertValue, InsertRel}; +use yopa::data::TypedValue; fn main() { simple_logging::log_to_stderr(LevelFilter::Debug); diff --git a/yopa/Cargo.lock b/yopa/Cargo.lock new file mode 100644 index 0000000..4ffccd6 --- /dev/null +++ b/yopa/Cargo.lock @@ -0,0 +1,272 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "anyhow" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yopa" +version = "0.1.0" +dependencies = [ + "anyhow", + "itertools", + "lazy_static", + "log", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "uuid", +] diff --git a/yopa/Cargo.toml b/yopa/Cargo.toml index e3c88bf..26944b1 100644 --- a/yopa/Cargo.toml +++ b/yopa/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "yopa" +description = "Your Own Private Ass" version = "0.1.0" authors = ["Ondřej Hruška "] edition = "2018" @@ -9,7 +10,7 @@ edition = "2018" [dependencies] log = "0.4.13" -uuid = { version = "0.8", features = ["serde", "v4"] } +uuid = { version = "0.8", features = ["serde", "v4"], optional = true } serde_json = "1.0.61" serde = { version = "1.0.120", features = ["derive"] } @@ -18,3 +19,7 @@ anyhow = "1.0.38" thiserror = "1.0.23" itertools = "0.10.0" lazy_static = "1.4.0" + +[features] +default = [] +uuid-ids = ["uuid"] diff --git a/yopa/src/data.rs b/yopa/src/data.rs new file mode 100644 index 0000000..cdd24a2 --- /dev/null +++ b/yopa/src/data.rs @@ -0,0 +1,192 @@ +//! Data value structs + +use serde::{Serialize, Deserialize}; +use crate::ID; +use std::borrow::Cow; +use crate::StorageError; +use std::num::ParseIntError; +use crate::model::DataType; + +/// Value of a particular type +#[derive(Debug,Clone,Serialize,Deserialize,PartialEq)] +pub enum TypedValue { + /// Text + String(Cow<'static, str>), + /// Integer + Integer(i64), + /// Floating point number + Decimal(f64), + /// Boolean yes/no + Boolean(bool), +} + +impl TypedValue { + /// Try ot cast to another type. On error, the original value is returned as Err. + pub fn cast_to(self, ty : DataType) -> Result { + 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)) + } + }, + (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::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::Integer(i), DataType::Boolean) => Ok(TypedValue::Boolean(i != 0)), + (e @ TypedValue::Decimal(_), DataType::Boolean) => Err(e), + (b @ TypedValue::Boolean(_), DataType::Boolean) => Ok(b), + (s, _) => { + Err(s) + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::TypedValue; + use crate::model::DataType; + + #[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)); + } + + #[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(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(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!(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(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("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("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::String)); + } +} + +/// Instance of an object +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Object { + /// PK + pub id : ID, + /// Object template ID + pub model_id: ID, +} + +/// Relation between two objects +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Relation { + /// PK + pub id : ID, + /// Source object ID + pub object_id : ID, + /// Relation template ID + pub model_id: ID, + /// Related object ID + pub related_id : ID, +} + +/// Value attached to an object +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Value { + /// PK + pub id : ID, + /// Owning object ID + pub object_id : ID, + /// Property template ID + pub model_id: ID, + /// Property value + pub value : TypedValue, +} diff --git a/yopa/src/insert.rs b/yopa/src/insert.rs new file mode 100644 index 0000000..fb2b5d5 --- /dev/null +++ b/yopa/src/insert.rs @@ -0,0 +1,57 @@ +//! Helper struct for inserting relational data in the database + +use super::ID; +use super::data::TypedValue; +use serde::{Serialize,Deserialize}; + +/// Value to insert +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct InsertValue { + pub model_id: ID, + pub value: TypedValue +} + +impl InsertValue { + pub fn new(model_id : ID, value : TypedValue) -> Self { + Self { + model_id, + value + } + } +} + +/// Info for inserting a relation +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct InsertRel { + pub model_id: ID, + pub related_id: ID, + pub values: Vec +} + +impl InsertRel { + pub fn new(model_id : ID, related_id: ID, values : Vec) -> Self { + Self { + model_id, + related_id, + values + } + } +} + +/// Info for inserting a relation +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct InsertObj { + pub model_id: ID, + pub values: Vec, + pub relations: Vec, +} + +impl InsertObj { + pub fn new(model_id : ID, values : Vec, relations: Vec) -> Self { + Self { + model_id, + values, + relations + } + } +} diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs index 38c6f62..a1525d3 100644 --- a/yopa/src/lib.rs +++ b/yopa/src/lib.rs @@ -12,7 +12,7 @@ use data::TypedValue; mod cool; -/* +#[cfg(feature="uuid-ids")] pub mod id { /// Common identifier type #[allow(non_camel_case_types)] @@ -26,8 +26,8 @@ pub mod id { uuid::Uuid::nil() } } -*/ +#[cfg(not(feature="uuid-ids"))] pub mod id { /// Common identifier type #[allow(non_camel_case_types)] @@ -52,352 +52,9 @@ pub mod id { pub use id::ID; use id::next_id; -/// Data model structs and enums -pub mod model { - use serde::{Serialize, Deserialize}; - use std::borrow::Cow; - use super::ID; - use super::data::TypedValue; - use std::fmt::{Display, Formatter}; - use std::fmt; - - /// Get a description of a struct - pub trait Describe { - /// Short but informative description for error messages - fn describe(&self) -> String; - } - - /// Object template - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct ObjectModel { - /// PK - pub id : ID, - /// Template name, unique within the database - pub name: String, - /// Parent object template ID - pub parent_tpl_id: Option, - } - - /// Relation between templates - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct RelationModel { - /// PK - pub id: ID, - /// Object template ID - pub object_tpl_id: ID, - /// Relation name, unique within the parent object - pub name: String, - /// Relation is optional - pub optional: bool, - /// Relation can be multiple - pub multiple: bool, - /// Related object template ID - pub related_tpl_id: ID, - } - - /// Property definition - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct PropertyModel { - /// PK - pub id: ID, - /// Object or Reference template ID - pub parent_tpl_id: ID, - /// Property name, unique within the parent object or reference - pub name: String, - /// Property is optional - pub optional: bool, - /// Property can be multiple - pub multiple: bool, - /// Property data type - pub data_type: DataType, - /// Default value, used for newly created objects - pub default: Option, - } - - /// Value data type - #[derive(Debug,Clone,Copy,Serialize,Deserialize,Eq,PartialEq)] - pub enum DataType { - /// Text - String, - /// Integer - Integer, - /// Floating point number - Decimal, - /// Boolean yes/no - Boolean, - } - - impl Display for ObjectModel { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "object \"{}\" ({})", self.name, self.id) - } - } - - impl Display for RelationModel { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "relation \"{}\" ({})", self.name, self.id) - } - } - - impl Display for PropertyModel { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "property \"{}\" ({})", self.name, self.id) - } - } -} - -/// Data value structs -pub mod data { - use serde::{Serialize, Deserialize}; - use crate::ID; - use std::borrow::Cow; - use crate::StorageError; - use std::num::ParseIntError; - use crate::model::DataType; - - /// Value of a particular type - #[derive(Debug,Clone,Serialize,Deserialize,PartialEq)] - pub enum TypedValue { - /// Text - String(Cow<'static, str>), - /// Integer - Integer(i64), - /// Floating point number - Decimal(f64), - /// Boolean yes/no - Boolean(bool), - } - - impl TypedValue { - /// Try ot cast to another type. On error, the original value is returned as Err. - pub fn cast_to(self, ty : DataType) -> Result { - 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)) - } - }, - (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::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::Integer(i), DataType::Boolean) => Ok(TypedValue::Boolean(i != 0)), - (e @ TypedValue::Decimal(_), DataType::Boolean) => Err(e), - (b @ TypedValue::Boolean(_), DataType::Boolean) => Ok(b), - (s, _) => { - Err(s) - } - } - } - } - - #[cfg(test)] - mod tests { - use crate::TypedValue; - use crate::model::DataType; - - #[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)); - } - - #[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(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(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!(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(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("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("blorg".into())), TypedValue::String("blorg".into()).cast_to(DataType::String)); - } - } - - /// Instance of an object - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct Object { - /// PK - pub id : ID, - /// Object template ID - pub model_id: ID, - } - - /// Relation between two objects - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct Relation { - /// PK - pub id : ID, - /// Source object ID - pub object_id : ID, - /// Relation template ID - pub model_id: ID, - /// Related object ID - pub related_id : ID, - } - - /// Value attached to an object - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct Value { - /// PK - pub id : ID, - /// Owning object ID - pub object_id : ID, - /// Property template ID - pub model_id: ID, - /// Property value - pub value : TypedValue, - } -} - -/// Helper struct for inserting relational data in the database -pub mod insert { - use super::ID; - use super::data::TypedValue; - use serde::{Serialize,Deserialize}; - - /// Value to insert - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct InsertValue { - pub model_id: ID, - pub value: TypedValue - } - - impl InsertValue { - pub fn new(model_id : ID, value : TypedValue) -> Self { - Self { - model_id, - value - } - } - } - - /// Info for inserting a relation - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct InsertRel { - pub model_id: ID, - pub related_id: ID, - pub values: Vec - } - - impl InsertRel { - pub fn new(model_id : ID, related_id: ID, values : Vec) -> Self { - Self { - model_id, - related_id, - values - } - } - } - - /// Info for inserting a relation - #[derive(Debug,Clone,Serialize,Deserialize)] - pub struct InsertObj { - pub model_id: ID, - pub values: Vec, - pub relations: Vec, - } - - impl InsertObj { - pub fn new(model_id : ID, values : Vec, relations: Vec) -> Self { - Self { - model_id, - values, - relations - } - } - } -} +pub mod model; +pub mod data; +pub mod insert; #[derive(Debug, Default)] pub struct InMemoryStorage { diff --git a/yopa/src/model.rs b/yopa/src/model.rs new file mode 100644 index 0000000..50c8df3 --- /dev/null +++ b/yopa/src/model.rs @@ -0,0 +1,92 @@ +//! Data model structs and enums + +use serde::{Serialize, Deserialize}; +use std::borrow::Cow; +use super::ID; +use super::data::TypedValue; +use std::fmt::{Display, Formatter}; +use std::fmt; + +/// Get a description of a struct +pub trait Describe { + /// Short but informative description for error messages + fn describe(&self) -> String; +} + +/// Object template +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct ObjectModel { + /// PK + pub id : ID, + /// Template name, unique within the database + pub name: String, + /// Parent object template ID + pub parent_tpl_id: Option, +} + +/// Relation between templates +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct RelationModel { + /// PK + pub id: ID, + /// Object template ID + pub object_tpl_id: ID, + /// Relation name, unique within the parent object + pub name: String, + /// Relation is optional + pub optional: bool, + /// Relation can be multiple + pub multiple: bool, + /// Related object template ID + pub related_tpl_id: ID, +} + +/// Property definition +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct PropertyModel { + /// PK + pub id: ID, + /// Object or Reference template ID + pub parent_tpl_id: ID, + /// Property name, unique within the parent object or reference + pub name: String, + /// Property is optional + pub optional: bool, + /// Property can be multiple + pub multiple: bool, + /// Property data type + pub data_type: DataType, + /// Default value, used for newly created objects + pub default: Option, +} + +/// Value data type +#[derive(Debug,Clone,Copy,Serialize,Deserialize,Eq,PartialEq)] +pub enum DataType { + /// Text + String, + /// Integer + Integer, + /// Floating point number + Decimal, + /// Boolean yes/no + Boolean, +} + +impl Display for ObjectModel { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "object \"{}\" ({})", self.name, self.id) + } +} + +impl Display for RelationModel { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "relation \"{}\" ({})", self.name, self.id) + } +} + +impl Display for PropertyModel { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "property \"{}\" ({})", self.name, self.id) + } +}