a small relational database with user-editable schema for manual data entry
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.
 
 
 
 
 
 
yopa/yopa/src/data.rs

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