|
|
|
//! Data value structs
|
|
|
|
|
|
|
|
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 std::fmt;
|
|
|
|
|
|
|
|
/// 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 Display for TypedValue {
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
TypedValue::String(v) => write!(f, "{}", v),
|
|
|
|
TypedValue::Integer(v) => write!(f, "{}", v),
|
|
|
|
TypedValue::Decimal(v) => write!(f, "{}", v),
|
|
|
|
TypedValue::Boolean(v) => write!(f, "{}", if *v { "True" } else { "False" }),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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::data::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
|
|
|
|
#[serde(default)]
|
|
|
|
pub id: ID,
|
|
|
|
/// Object template ID
|
|
|
|
pub model: ID,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Relation between two objects
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub struct Relation {
|
|
|
|
/// PK
|
|
|
|
#[serde(default)]
|
|
|
|
pub id: ID,
|
|
|
|
/// Source object ID
|
|
|
|
pub object: ID,
|
|
|
|
/// Relation template ID
|
|
|
|
pub model: ID,
|
|
|
|
/// Related object ID
|
|
|
|
pub related: ID,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Value attached to an object
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub struct Value {
|
|
|
|
/// PK
|
|
|
|
#[serde(default)]
|
|
|
|
pub id: ID,
|
|
|
|
/// Owning object ID
|
|
|
|
pub object: ID,
|
|
|
|
/// Property template ID
|
|
|
|
pub model: ID,
|
|
|
|
/// Property value
|
|
|
|
pub value: TypedValue,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HaveId for Object {
|
|
|
|
fn get_id(&self) -> ID {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HaveId for Relation {
|
|
|
|
fn get_id(&self) -> ID {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HaveId for Value {
|
|
|
|
fn get_id(&self) -> ID {
|
|
|
|
self.id
|
|
|
|
}
|
|
|
|
}
|