a small relational database with user-editable schema for manual data entry
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 
yopa/yopa/src/data.rs

368 lignes
11 KiB

//! Data value structs
use std::borrow::{Cow, Borrow};
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)
);
}
}