comments, splitting

master
Ondřej Hruška 3 years ago
parent 57fd444f4e
commit 7bc4026d05
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 28
      Cargo.lock
  2. 5
      src/main.rs
  3. 272
      yopa/Cargo.lock
  4. 7
      yopa/Cargo.toml
  5. 192
      yopa/src/data.rs
  6. 57
      yopa/src/insert.rs
  7. 353
      yopa/src/lib.rs
  8. 92
      yopa/src/model.rs

28
Cargo.lock generated

@ -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]]

@ -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);

272
yopa/Cargo.lock generated

@ -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",
]

@ -1,5 +1,6 @@
[package]
name = "yopa"
description = "Your Own Private Ass"
version = "0.1.0"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
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"]

@ -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<TypedValue, TypedValue> {
match (self, ty) {
// to string
(s @ TypedValue::String(_), DataType::String) => Ok(s),
(TypedValue::Integer(i), DataType::String) => Ok(TypedValue::String(i.to_string().into())),
(TypedValue::Decimal(f), DataType::String) => Ok(TypedValue::String(f.to_string().into())),
(TypedValue::Boolean(b), DataType::String) => Ok(TypedValue::String(Cow::Borrowed(if b { "1" } else { "0" }))),
// to int
(TypedValue::String(s), DataType::Integer) => {
match s.parse::<i64>() {
Ok(i) => Ok(TypedValue::Integer(i)),
Err(_) => Err(TypedValue::String(s))
}
},
(s @ TypedValue::Integer(_), DataType::Integer) => Ok(s),
(TypedValue::Decimal(f), DataType::Integer) => Ok(TypedValue::Integer(f.round() as i64)),
(TypedValue::Boolean(b), DataType::Integer) => Ok(TypedValue::Integer(if b { 1 } else { 0 })),
// to float
(TypedValue::String(s), DataType::Decimal) => {
match s.parse::<f64>() {
Ok(i) => Ok(TypedValue::Decimal(i)),
Err(_) => Err(TypedValue::String(s))
}
},
(TypedValue::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,
}

@ -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<InsertValue>
}
impl InsertRel {
pub fn new(model_id : ID, related_id: ID, values : Vec<InsertValue>) -> 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<InsertValue>,
pub relations: Vec<InsertRel>,
}
impl InsertObj {
pub fn new(model_id : ID, values : Vec<InsertValue>, relations: Vec<InsertRel>) -> Self {
Self {
model_id,
values,
relations
}
}
}

@ -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<ID>,
}
/// 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<TypedValue>,
}
/// 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<TypedValue, TypedValue> {
match (self, ty) {
// to string
(s @ TypedValue::String(_), DataType::String) => Ok(s),
(TypedValue::Integer(i), DataType::String) => Ok(TypedValue::String(i.to_string().into())),
(TypedValue::Decimal(f), DataType::String) => Ok(TypedValue::String(f.to_string().into())),
(TypedValue::Boolean(b), DataType::String) => Ok(TypedValue::String(Cow::Borrowed(if b { "1" } else { "0" }))),
// to int
(TypedValue::String(s), DataType::Integer) => {
match s.parse::<i64>() {
Ok(i) => Ok(TypedValue::Integer(i)),
Err(_) => Err(TypedValue::String(s))
}
},
(s @ TypedValue::Integer(_), DataType::Integer) => Ok(s),
(TypedValue::Decimal(f), DataType::Integer) => Ok(TypedValue::Integer(f.round() as i64)),
(TypedValue::Boolean(b), DataType::Integer) => Ok(TypedValue::Integer(if b { 1 } else { 0 })),
// to float
(TypedValue::String(s), DataType::Decimal) => {
match s.parse::<f64>() {
Ok(i) => Ok(TypedValue::Decimal(i)),
Err(_) => Err(TypedValue::String(s))
}
},
(TypedValue::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<InsertValue>
}
impl InsertRel {
pub fn new(model_id : ID, related_id: ID, values : Vec<InsertValue>) -> 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<InsertValue>,
pub relations: Vec<InsertRel>,
}
impl InsertObj {
pub fn new(model_id : ID, values : Vec<InsertValue>, relations: Vec<InsertRel>) -> Self {
Self {
model_id,
values,
relations
}
}
}
}
pub mod model;
pub mod data;
pub mod insert;
#[derive(Debug, Default)]
pub struct InMemoryStorage {

@ -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<ID>,
}
/// 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<TypedValue>,
}
/// 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)
}
}
Loading…
Cancel
Save