use chrono::prelude::*; use std::{ ops::{Add, AddAssign}, time::Duration, }; use uuid::Uuid; #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum Field { Boolean(bool), DateTime(DateTime), Duration(Duration), Integer(i128), None, StaticString(String), Uuid(Uuid), } impl Field { pub fn get_type(&self) -> FieldType { self.into() } pub fn equal(&self, other: &Field) -> Field { if self.get_type() == other.get_type() { { self == other }.into() } else { Field::None } } pub fn not_equal(&self, other: &Field) -> Field { if self.get_type() == other.get_type() { { self != other }.into() } else { Field::None } } pub fn greater(&self, other: &Self) -> Field { if self.get_type() == other.get_type() { { self > other }.into() } else { Field::None } } pub fn greater_equal(&self, other: &Self) -> Field { if self.get_type() == other.get_type() { { self >= other }.into() } else { Field::None } } pub fn lesser(&self, other: &Self) -> Field { if self.get_type() == other.get_type() { { self < other }.into() } else { Field::None } } pub fn lesser_equal(&self, other: &Self) -> Field { if self.get_type() == other.get_type() { { self <= other }.into() } else { Field::None } } } impl Add for Field { type Output = Self; fn add(self, other: Self) -> Self { match self { Field::DateTime(value1) => match other { Field::Duration(value2) => { value1 + value2 }.into(), _ => Field::None, }, Field::Duration(value1) => match other { Field::Duration(value2) => { value1 + value2 }.into(), _ => Field::None, }, Field::Integer(value1) => match other { Field::Integer(value2) => { value1 + value2 }.into(), _ => Field::None, }, _ => Self::None, } } } impl AddAssign for Field { fn add_assign(&mut self, other: Self) { *self = self.clone().add(other); } } impl From for Field { fn from(value: bool) -> Self { Self::Boolean(value) } } impl From> for Field { fn from(value: DateTime) -> Self { Self::DateTime(value) } } impl From for Field { fn from(value: Duration) -> Self { Self::Duration(value) } } impl From for Field { fn from(value: String) -> Self { Self::StaticString(value) } } impl From<&str> for Field { fn from(value: &str) -> Self { Self::from(value.to_string()) } } impl From for Field { fn from(value: Uuid) -> Self { Self::Uuid(value) } } impl From for Field { fn from(value: i128) -> Self { Self::Integer(value) } } impl From for Field { fn from(value: isize) -> Self { let data: i128 = value.try_into().unwrap(); Self::from(data) } } impl From for Field { fn from(value: i32) -> Self { let data: i128 = value.into(); Self::from(data) } } #[cfg(test)] mod fields { use super::*; use rand::random; #[test] fn can_create_static_string() { let data = Uuid::new_v4().to_string(); let result: Field = data.clone().into(); match result.clone() { Field::StaticString(output) => assert_eq!(output, data), _ => unreachable!("got {:?}: should have been static string", result), } assert_eq!(result.get_type(), FieldType::StaticString); } #[test] fn can_create_from_str() { let holder = ["one", "two"]; for data in holder.into_iter() { let result: Field = data.into(); match result.clone() { Field::StaticString(output) => assert_eq!(output, data), _ => unreachable!("got {:?}: should have been static string", result), } assert_eq!(result.get_type(), FieldType::StaticString); } } #[test] fn create_from_uuid() { let data = Uuid::new_v4(); let result: Field = data.clone().into(); match result.clone() { Field::Uuid(output) => assert_eq!(output, data), _ => unreachable!("got {:?}: should have been uuid", result), } assert_eq!(result.get_type(), FieldType::Uuid); } #[test] fn create_from_datatime() { let data = Utc::now(); let result: Field = data.into(); match result.clone() { Field::DateTime(output) => assert_eq!(output, data), _ => unreachable!("got {:?}: should have been uuid", result), } assert_eq!(result.get_type(), FieldType::DateTime); } #[test] fn does_adding_return_none_for_things_that_cannot_add() { let value1: Field = Uuid::new_v4().into(); let value2: Field = Uuid::new_v4().into(); assert_eq!(value1 + value2, Field::None); } #[test] fn can_integers_be_added() { let value1: i128 = random::().into(); let value2: i128 = random::().into(); let expected: Field = { value1 + value2 }.into(); let value1: Field = value1.into(); let value2: Field = value2.into(); assert_eq!(value1 + value2, expected); } #[test] fn can_integer_add_mismatch_returns_none() { let value1: Field = 5.into(); let value2: Field = "nope".into(); assert_eq!(value1 + value2, Field::None); } #[test] fn can_durations_be_added() { let data1: u64 = random::().into(); let data2: u64 = random::().into(); let value1: Field = Duration::from_secs(data1).into(); let value2: Field = Duration::from_secs(data2).into(); let expected: Field = Duration::from_secs(data1 + data2).into(); assert_eq!(value1 + value2, expected); } #[test] fn does_duration_mismatch_return_none() { let value1: Field = Duration::from_secs(20).into(); let value2: Field = "nope".into(); assert_eq!(value1 + value2, Field::None); } #[test] fn can_durations_be_added_to_datetimes() { let timestamp = Utc::now(); let data: u64 = random::().into(); let duration = Duration::from_secs(data); let expected: Field = { timestamp + duration }.into(); let value1: Field = timestamp.into(); let value2: Field = duration.into(); assert_eq!(value1 + value2, expected); } #[test] fn does_datetime_mismatch_return_none() { let value1: Field = Utc::now().into(); let value2: Field = "nope".into(); assert_eq!(value1 + value2, Field::None); } #[test] fn does_field_equal_return_properly() { let mut values: Vec = Vec::new(); let count = 2; while values.len() < count { let value: Field = Uuid::new_v4().into(); if !values.contains(&value) { values.push(value); } } assert_eq!(values[0].equal(&values[0]), true.into()); assert_eq!(values[0].equal(&values[1]), false.into()); assert_eq!(values[0].equal(&"nope".into()), Field::None); } #[test] fn does_field_not_equal_return_properly() { let mut values: Vec = Vec::new(); let count = 2; while values.len() < count { let value: Field = Uuid::new_v4().into(); if !values.contains(&value) { values.push(value); } } assert_eq!(values[0].not_equal(&values[0]), false.into()); assert_eq!(values[0].not_equal(&values[1]), true.into()); assert_eq!(values[0].not_equal(&"nope".into()), Field::None); } #[test] fn can_field_have_greater_value() { let value1: Field = 1.into(); let value2: Field = 2.into(); let value3: Field = 3.into(); let mismatch: Field = "bad".into(); assert_eq!(value2.greater(&value1), true.into()); assert_eq!(value2.greater(&value2), false.into()); assert_eq!(value2.greater(&value3), false.into()); assert_eq!(value2.greater(&mismatch), Field::None); } #[test] fn can_field_have_greater_or_equal_value() { let value1: Field = 1.into(); let value2: Field = 2.into(); let value3: Field = 3.into(); let mismatch: Field = "bad".into(); assert_eq!(value2.greater_equal(&value1), true.into()); assert_eq!(value2.greater_equal(&value2), true.into()); assert_eq!(value2.greater_equal(&value3), false.into()); assert_eq!(value2.greater_equal(&mismatch), Field::None); } #[test] fn can_field_have_lesser_value() { let value1: Field = 1.into(); let value2: Field = 2.into(); let value3: Field = 3.into(); let mismatch: Field = "bad".into(); assert_eq!(value2.lesser(&value1), false.into()); assert_eq!(value2.lesser(&value2), false.into()); assert_eq!(value2.lesser(&value3), true.into()); assert_eq!(value2.lesser(&mismatch), Field::None); } #[test] fn can_field_have_lesser_or_equal_value() { let value1: Field = 1.into(); let value2: Field = 2.into(); let value3: Field = 3.into(); let mismatch: Field = "bad".into(); assert_eq!(value2.lesser_equal(&value1), false.into()); assert_eq!(value2.lesser_equal(&value2), true.into()); assert_eq!(value2.lesser_equal(&value3), true.into()); assert_eq!(value2.lesser_equal(&mismatch), Field::None); } } #[derive(Clone, Debug, PartialEq)] pub enum FieldType { Boolean, DateTime, Duration, Integer, None, StaticString, Uuid, } impl FieldType { pub fn get_default(&self) -> Field { match self { FieldType::Boolean => false.into(), FieldType::DateTime => Utc::now().into(), FieldType::Duration => Duration::from_secs(0).into(), FieldType::Integer => 0.into(), FieldType::None => Field::None, FieldType::StaticString => "".into(), FieldType::Uuid => Uuid::new_v4().into(), } } } impl From<&Field> for FieldType { fn from(value: &Field) -> Self { match value { Field::Boolean(_) => Self::Boolean, Field::DateTime(_) => Self::DateTime, Field::Duration(_) => Self::Duration, Field::Integer(_) => Self::Integer, Field::None => Self::None, Field::StaticString(_) => Self::StaticString, Field::Uuid(_) => Self::Uuid, } } } #[cfg(test)] mod fieldtypes { use super::*; #[test] fn can_get_defaults_for_uuid() { let ftype = FieldType::Uuid; let mut ids: Vec = Vec::new(); for _ in 0..5 { let result = ftype.get_default(); match result { Field::Uuid(data) => { assert!( !ids.contains(&data), "found duplicate id {:?} in {:?}", data, ids ); ids.push(data.clone()); } _ => unreachable!("got {:?}: should have been uuid", result), } } } #[test] fn can_get_defaults_for_static_string() { let ftype = FieldType::StaticString; let result = ftype.get_default(); match result { Field::StaticString(data) => assert_eq!(data, ""), _ => unreachable!("got {:?}: should have been static string", result), } } }