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

212 lines
9.1 KiB

//! 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<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
}
}