You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
11 KiB
368 lines
11 KiB
//! Data value structs
|
|
|
|
use std::borrow::{Borrow, Cow};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::id::HaveId;
|
|
use crate::model::DataType;
|
|
use crate::ID;
|
|
use std::fmt;
|
|
use std::fmt::{Display, Formatter};
|
|
|
|
/// Value of a particular type
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
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 {
|
|
/// To Cow<str>
|
|
pub fn to_cow(&self) -> Cow<str> {
|
|
match self {
|
|
TypedValue::String(v) => Cow::Borrowed(v.borrow()),
|
|
TypedValue::Integer(v) => v.to_string().into(),
|
|
TypedValue::Decimal(v) => v.to_string().into(),
|
|
TypedValue::Boolean(v) => if *v { "True" } else { "False" }.into(),
|
|
}
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
}
|
|
|
|
#[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)
|
|
);
|
|
}
|
|
}
|
|
|