//! Data value structs use std::borrow::Cow; use serde::{Deserialize, Serialize}; use crate::ID; use crate::model::DataType; use crate::id::HaveId; /// 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::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 } }