use chrono::prelude::*; use std::{ cmp::Ordering, ops::{Add, AddAssign}, time::Duration, }; use uuid::Uuid; #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq)] pub enum Field { Boolean(bool), DateTime(DateTime), Duration(Duration), Integer(i128), None, Revision(Revision), StaticString(String), Uuid(Uuid), } impl Field { pub fn get_type(&self) -> FieldType { self.into() } } 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: Revision) -> Self { Self::Revision(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) } } impl PartialOrd for Field { fn partial_cmp(&self, other: &Field) -> Option { match (self, other) { (Self::Boolean(d1), Self::Boolean(d2)) => d1.partial_cmp(d2), (Self::DateTime(d1), Self::DateTime(d2)) => d1.partial_cmp(d2), (Self::Duration(d1), Self::Duration(d2)) => d1.partial_cmp(d2), (Self::Integer(d1), Self::Integer(d2)) => d1.partial_cmp(d2), (Self::Revision(d1), Self::Revision(d2)) => d1.partial_cmp(d2), (Self::StaticString(d1), Self::StaticString(d2)) => d1.partial_cmp(d2), (Self::Uuid(d1), Self::Uuid(d2)) => d1.partial_cmp(d2), (_, _) => None, } } } #[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 do_comparason_functions_work() { let fields = [Field::Integer(0), Field::Integer(1), Field::Integer(2)]; assert!(fields[1] == fields[1], "equal did not work"); assert!(fields[1] < fields[2], "less than did not work"); assert!(fields[1] <= fields[2], "less than equal to did not work"); assert!(fields[1] <= fields[1], "less tahn equal 50 did not work"); assert!(fields[1] > fields[0], "greater than did not work"); assert!(fields[1] >= fields[0], "greater than equal to did not work"); assert!(fields[1] >= fields[1], "greater than equal to did not work"); } #[test] fn does_mismatched_comparason_fields_return_false() { let fields = [Field::Integer(0), Field::Uuid(Uuid::nil())]; assert!(!(fields[0] == fields[1]), "equal did not work"); assert!(!(fields[0] < fields[1]), "less than did not work"); assert!(!(fields[0] <= fields[1]), "less than equal to did not work"); assert!(!(fields[0] <= fields[1]), "less tahn equal 50 did not work"); assert!(!(fields[1] > fields[0]), "greater than did not work"); assert!( !(fields[1] >= fields[0]), "greater than equal to did not work" ); assert!( !(fields[1] >= fields[0]), "greater than equal to did not work" ); } } #[derive(Clone, Debug, PartialEq)] pub enum FieldType { Boolean, DateTime, Duration, Integer, None, Revision, 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::Revision => Revision::new().into(), 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::Revision(_) => Self::Revision, 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), } } } #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] struct Revision; impl Revision { fn new() -> Self { Self {} } fn revision(&self) -> isize { 0 } } #[cfg(test)] mod revisions { use super::*; #[test] fn can_create_empty_revision() { let rev = Revision::new(); assert_eq!(rev.revision(), 0); } }