diff --git a/src/action.rs b/src/action.rs index 01a5289..79a41a6 100644 --- a/src/action.rs +++ b/src/action.rs @@ -10,7 +10,7 @@ pub use crate::document::{ field::{Field, FieldType}, }; pub use action_type::Action; -pub use calculation::CalcValue; +pub use calculation::{CalcValue, Calculation}; pub use message::MsgAction; pub use query::Query; use request_data::RequestData; diff --git a/src/action/calculation.rs b/src/action/calculation.rs index 721385f..464d10d 100644 --- a/src/action/calculation.rs +++ b/src/action/calculation.rs @@ -1,5 +1,5 @@ use super::{Field, FieldType}; -use crate::message::wrapper::{Calculation, Operand}; +use crate::{message::wrapper::Operand, mtterror::MTTError}; use chrono::{DateTime, Utc}; use std::time::Duration; use uuid::Uuid; @@ -215,3 +215,343 @@ mod calcvalues { ); } } + +#[derive(Clone, Debug)] +pub struct Calculation { + operation: Operand, + values: Vec, +} + +impl Calculation { + pub fn new(operand: Operand) -> Self { + Self { + operation: operand, + values: Vec::new(), + } + } + + #[allow(dead_code)] + fn operation(&self) -> &Operand { + &self.operation + } + + #[allow(dead_code)] + fn get_fields(&self, existing: Field) -> Vec { + let mut output = Vec::new(); + for item in self.values.iter() { + output.push(item.get(&existing)); + } + output + } + + pub fn get_type(&self) -> FieldType { + if self.values.is_empty() { + FieldType::None + } else { + self.values[0].get_type() + } + } + + pub fn add_value(&mut self, data: CV) -> Result<(), MTTError> + where + CV: Into, + { + let holder: CalcValue = data.into(); + if self.values.is_empty() { + self.values.push(holder); + return Ok(()); + } + let mut base = self.get_type(); + match self.operation { + Operand::Add => match base { + FieldType::DateTime => base = FieldType::Duration, + _ => {} + }, + _ => {} + } + let ftype = holder.get_type(); + if base == ftype { + self.values.push(holder); + } else { + return Err(MTTError::DocumentFieldWrongDataType(base, ftype)); + } + Ok(()) + } + + #[allow(dead_code)] + fn validate_value(&self, value: CV) -> Result<(), MTTError> + where + CV: Into, + { + if self.values.is_empty() { + return Ok(()); + } + let holder = value.into(); + let mut base = self.get_type(); + match self.operation { + Operand::Add => { + if base == FieldType::DateTime { + base = FieldType::Duration; + } + } + _ => {} + } + let ftype = holder.get_type(); + if base == ftype { + Ok(()) + } else { + Err(MTTError::DocumentFieldWrongDataType(base, ftype)) + } + } + + pub fn calculate(&self, existing: &Field) -> Field { + let mut result = Field::None; + match self.operation { + Operand::Add => { + let mut first = true; + for value in self.values.iter() { + let data = value.get(existing); + if first { + result = data; + first = false; + } else { + result += data; + } + } + } + Operand::Equal => { + if self.values.len() >= 2 { + result = self.values[0] + .get(existing) + .equal(&self.values[1].get(existing)); + } + } + Operand::GreaterThan => { + if self.values.len() >= 2 { + result = self.values[0] + .get(existing) + .greater(&self.values[1].get(existing)); + } + } + Operand::GreaterThanEqual => { + if self.values.len() >= 2 { + result = self.values[0] + .get(existing) + .greater_equal(&self.values[1].get(existing)); + } + } + Operand::LessThan => { + if self.values.len() >= 2 { + result = self.values[0] + .get(existing) + .lesser(&self.values[1].get(existing)); + } + } + Operand::LessThanEqual => { + if self.values.len() >= 2 { + result = self.values[0] + .get(existing) + .lesser_equal(&self.values[1].get(existing)); + } + } + } + result + } +} + +#[cfg(test)] +mod calculations { + use super::*; + use rand::random; + + #[test] + fn errors_on_different_field_types() { + let mut calc = Calculation::new(Operand::Equal); + calc.add_value(Uuid::nil()).unwrap(); + match calc.add_value("other") { + Ok(_) => unreachable!("should have errored with wrong type"), + Err(err) => match err { + MTTError::DocumentFieldWrongDataType(expected, got) => { + assert_eq!(expected, FieldType::Uuid); + assert_eq!(got, FieldType::StaticString); + } + _ => unreachable!("got {:?}, expected wrong field type", err), + }, + } + } + + #[test] + fn returns_reference_to_operand() { + let calc = Calculation::new(Operand::Add); + match calc.operation() { + Operand::Add => {} + _ => unreachable!("got {:?}, shold have gotten assign", calc.operation()), + } + let calc = Calculation::new(Operand::Equal); + match calc.operation() { + Operand::Equal => {} + _ => unreachable!("got {:?}, shold have gotten assign", calc.operation()), + } + } + + #[test] + fn can_equal_true() { + let mut calc = Calculation::new(Operand::Equal); + let data: Field = Uuid::new_v4().into(); + calc.add_value(data.clone()).unwrap(); + calc.add_value(data.clone()).unwrap(); + let expected: Field = true.into(); + let result = calc.calculate(&Field::None); + assert_eq!(result, expected); + } + + #[test] + fn can_equal_false() { + let mut calc = Calculation::new(Operand::Equal); + let value1: Field = "fred".into(); + let value2: Field = "barney".into(); + calc.add_value(value1).unwrap(); + calc.add_value(value2).unwrap(); + let expected: Field = false.into(); + let result = calc.calculate(&Field::None); + assert_eq!(result, expected); + } + + #[test] + fn can_greater_than() { + let data: Vec<(Field, Field)> = vec![ + (0.into(), false.into()), + (1.into(), false.into()), + (2.into(), true.into()), + ]; + for (item, expected) in data.iter() { + let mut calc = Calculation::new(Operand::GreaterThan); + calc.add_value(item.clone()).unwrap(); + calc.add_value(data[1].0.clone()).unwrap(); + let result = calc.calculate(&Field::None); + assert_eq!(&result, expected); + } + } + + #[test] + fn can_greater_than_equal() { + let data: Vec<(Field, Field)> = vec![ + (0.into(), false.into()), + (1.into(), true.into()), + (2.into(), true.into()), + ]; + for (item, expected) in data.iter() { + let mut calc = Calculation::new(Operand::GreaterThanEqual); + calc.add_value(item.clone()).unwrap(); + calc.add_value(data[1].0.clone()).unwrap(); + let result = calc.calculate(&Field::None); + assert_eq!(&result, expected); + } + } + + #[test] + fn can_lesser_than() { + let data: Vec<(Field, Field)> = vec![ + (0.into(), true.into()), + (1.into(), false.into()), + (2.into(), false.into()), + ]; + for (item, expected) in data.iter() { + let mut calc = Calculation::new(Operand::LessThan); + calc.add_value(item.clone()).unwrap(); + calc.add_value(data[1].0.clone()).unwrap(); + let result = calc.calculate(&Field::None); + assert_eq!(&result, expected); + } + } + + #[test] + fn can_lesser_than_equal() { + let data: Vec<(Field, Field)> = vec![ + (0.into(), true.into()), + (1.into(), true.into()), + (2.into(), false.into()), + ]; + for (item, expected) in data.iter() { + let mut calc = Calculation::new(Operand::LessThanEqual); + calc.add_value(item.clone()).unwrap(); + calc.add_value(data[1].0.clone()).unwrap(); + let result = calc.calculate(&Field::None); + assert_eq!(&result, expected); + } + } + + #[test] + fn can_add_numbers() { + let mut calc = Calculation::new(Operand::Add); + 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(); + calc.add_value(value1.clone()).unwrap(); + calc.add_value(value2.clone()).unwrap(); + let result = calc.calculate(&Field::None); + assert_eq!( + result, expected, + "{:?} plus {:?} should equal {:?}", + value1, value2, expected + ); + } + + #[test] + fn can_use_existing_values() { + let mut calc = Calculation::new(Operand::Add); + 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(); + calc.add_value(value1.clone()).unwrap(); + calc.add_value(CalcValue::Existing(FieldType::Integer)) + .unwrap(); + let result = calc.calculate(&value2); + assert_eq!( + result, expected, + "{:?} plus {:?} should equal {:?}", + value1, value2, expected + ); + } + + #[test] + fn returns_error_on_mismatch() { + let mut calc = Calculation::new(Operand::Add); + calc.add_value(Uuid::nil()).unwrap(); + match calc.add_value("mismatch") { + Ok(_) => unreachable!("should have returned an error"), + Err(err) => match err { + MTTError::DocumentFieldWrongDataType(expected, got) => { + assert_eq!(got, FieldType::StaticString); + assert_eq!(expected, FieldType::Uuid); + } + _ => unreachable!("got {:?}, expected wrong field type", err), + }, + } + } + + #[test] + fn datetime_accepts_duration() { + let mut calc = Calculation::new(Operand::Add); + let duration = Duration::from_secs(3600); + let start = Utc::now() + duration; + calc.add_value(FieldType::DateTime).unwrap(); + match calc.add_value(duration.clone()) { + Ok(_) => {} + Err(err) => unreachable!("got {:?}, should have returned normally", err), + } + let result = calc.calculate(&Field::None); + let stop = Utc::now() + duration; + match result { + Field::DateTime(data) => { + assert!(data > start); + assert!(data < stop); + } + _ => unreachable!("got {:?}, should have been datetime", result), + } + } +} diff --git a/src/action/query.rs b/src/action/query.rs index 39b266c..582f6f1 100644 --- a/src/action/query.rs +++ b/src/action/query.rs @@ -1,5 +1,6 @@ -use crate::{message::wrapper::Calculation, name::NameType}; +use crate::name::NameType; use std::collections::{HashMap, HashSet}; +use super::Calculation; #[derive(Clone, Debug)] pub struct Query { diff --git a/src/document/create.rs b/src/document/create.rs index 3878d79..8306195 100644 --- a/src/document/create.rs +++ b/src/document/create.rs @@ -1,11 +1,11 @@ use crate::{ - action::{Action, CalcValue, MsgAction, Query}, + action::{Action, CalcValue, Calculation, MsgAction, Query}, document::{ definition::{DocDef, DocFuncType}, field::Field, }, message::wrapper::{ - Calculation, InternalRecord, InternalRecords, Message, Oid, Records, Reply, Update, + InternalRecord, InternalRecords, Message, Oid, Records, Reply, Update, }, mtterror::MTTError, name::NameType, diff --git a/src/document/definition.rs b/src/document/definition.rs index c093125..aa275c1 100644 --- a/src/document/definition.rs +++ b/src/document/definition.rs @@ -72,7 +72,7 @@ impl FieldSetting { #[cfg(test)] mod fieldsettings { use super::*; - use crate::message::wrapper::{Calculation, Operand}; + use crate::{action::Calculation, message::wrapper::Operand}; use chrono::Utc; use std::time::Duration; diff --git a/src/document/session.rs b/src/document/session.rs index 6a28ae7..88687f7 100644 --- a/src/document/session.rs +++ b/src/document/session.rs @@ -1,11 +1,11 @@ use crate::{ - action::{Action, CalcValue, FieldType, Query}, + action::{Action, CalcValue, Calculation, FieldType, Query}, document::{ clock::Clock, create::IndexType, definition::{DocDef, DocFuncType}, }, - message::wrapper::{Calculation, Delete, Message, Operand, Update}, + message::wrapper::{Delete, Message, Operand, Update}, name::{Name, NameType}, queue::{ data_director::{Include, Path, RegMsg, Register}, diff --git a/src/lib.rs b/src/lib.rs index 2661b3a..ef6ea60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,9 @@ mod mtterror; mod name; mod queue; -use action::{Action, CalcValue, DocDef, Field, FieldType, MsgAction, Query, UserAction}; +use action::{Action, CalcValue, Calculation, DocDef, Field, FieldType, MsgAction, Query, UserAction}; use document::{clock::Clock, create::CreateDoc, session::Session}; -use message::wrapper::{Addition, Calculation, Message, Operand}; +use message::wrapper::{Addition, Message, Operand}; pub use mtterror::MTTError; pub use name::{Name, NameType}; use queue::{ diff --git a/src/message/wrapper.rs b/src/message/wrapper.rs index b0bc020..0ca57b0 100644 --- a/src/message/wrapper.rs +++ b/src/message/wrapper.rs @@ -408,219 +408,6 @@ mod operands { } /* -#[derive(Clone, Debug)] -pub enum CalcValue { - Calculate(Calculation), - Existing(FieldType), - FType(FieldType), - None, - Value(Field), -} - -impl CalcValue { - pub fn get(&self, existing: &Field) -> Field { - match self { - Self::Calculate(calc) => calc.calculate(existing), - Self::Existing(_) => existing.clone(), - Self::FType(ftype) => ftype.get_default(), - Self::None => Field::None, - Self::Value(field) => field.clone(), - } - } - - fn get_type(&self) -> FieldType { - match self { - Self::Calculate(calc) => calc.get_type(), - Self::Existing(ftype) => ftype.clone(), - Self::FType(ftype) => ftype.clone(), - Self::None => FieldType::None, - Self::Value(field) => field.into(), - } - } -} - -impl From for CalcValue { - fn from(value: Calculation) -> Self { - Self::Calculate(value) - } -} - -impl From for CalcValue { - fn from(value: Field) -> Self { - Self::Value(value) - } -} - -impl From<&Field> for CalcValue { - fn from(value: &Field) -> Self { - Self::from(value.clone()) - } -} - -impl From for CalcValue { - fn from(value: FieldType) -> Self { - Self::FType(value) - } -} - -impl From for CalcValue { - fn from(value: bool) -> Self { - let output: Field = value.into(); - Self::from(output).into() - } -} - -impl From> for CalcValue { - fn from(value: DateTime) -> Self { - let output: Field = value.into(); - Self::from(output).into() - } -} - -impl From for CalcValue { - fn from(value: Duration) -> Self { - let output: Field = value.into(); - Self::from(output).into() - } -} - -impl From for CalcValue { - fn from(value: i128) -> Self { - let output: Field = value.into(); - Self::from(output).into() - } -} - -impl From<&str> for CalcValue { - fn from(value: &str) -> Self { - let output: Field = value.into(); - Self::from(output).into() - } -} - -impl From for CalcValue { - fn from(value: String) -> Self { - let output: Field = value.into(); - Self::from(output).into() - } -} - -impl From for CalcValue { - fn from(value: Uuid) -> Self { - let output: Field = value.into(); - Self::from(output).into() - } -} - -#[cfg(test)] -mod calcvalues { - use super::*; - - #[test] - fn from_uuid() { - let value = Uuid::new_v4(); - let expected: Field = value.into(); - let result: CalcValue = value.into(); - match result.clone() { - CalcValue::Value(data) => assert_eq!(data, expected), - _ => unreachable!("got {:?}, should have gotten a field", result), - } - assert_eq!(result.get(&Field::None), expected); - } - - #[test] - fn from_str() { - let value = "something"; - let expected: Field = value.into(); - let result: CalcValue = value.into(); - match result.clone() { - CalcValue::Value(data) => assert_eq!(data, expected), - _ => unreachable!("got {:?}, should have gotten a field", result), - } - assert_eq!(result.get(&Field::None), expected); - } - - #[test] - fn from_string() { - let value = "data".to_string(); - let expected: Field = value.clone().into(); - let result: CalcValue = value.into(); - match result.clone() { - CalcValue::Value(data) => assert_eq!(data, expected), - _ => unreachable!("got {:?}, should have gotten a field", result), - } - assert_eq!(result.get(&Field::None), expected); - } - - #[test] - fn from_boolean() { - let value = true; - let expected: Field = value.clone().into(); - let result: CalcValue = value.into(); - match result.clone() { - CalcValue::Value(data) => assert_eq!(data, expected), - _ => unreachable!("got {:?}, should have gotten a field", result), - } - assert_eq!(result.get(&Field::None), expected); - } - - #[test] - fn from_datetime() { - let value = Utc::now(); - let expected: Field = value.clone().into(); - let result: CalcValue = value.into(); - match result.clone() { - CalcValue::Value(data) => assert_eq!(data, expected), - _ => unreachable!("got {:?}, should have gotten a field", result), - } - assert_eq!(result.get(&Field::None), expected); - } - - #[test] - fn from_duration() { - let value = Duration::from_secs(5); - let expected: Field = value.clone().into(); - let result: CalcValue = value.into(); - match result.clone() { - CalcValue::Value(data) => assert_eq!(data, expected), - _ => unreachable!("got {:?}, should have gotten a field", result), - } - assert_eq!(result.get(&Field::None), expected); - } - - #[test] - fn from_integer() { - let value: i128 = 5; - let expected: Field = value.clone().into(); - let result: CalcValue = value.into(); - match result.clone() { - CalcValue::Value(data) => assert_eq!(data, expected), - _ => unreachable!("got {:?}, should have gotten a field", result), - } - assert_eq!(result.get(&Field::None), expected); - } - - #[test] - fn from_calculation() { - let duration = Duration::from_secs(300); - let start = Utc::now() + duration; - let mut calc = Calculation::new(Operand::Add); - calc.add_value(FieldType::DateTime).unwrap(); - calc.add_value(duration.clone()).unwrap(); - let result: CalcValue = calc.into(); - let data = match result.get(&Field::None) { - Field::DateTime(data) => data, - _ => unreachable!(), - }; - let stop = Utc::now() + duration; - assert!( - data > start && data < stop, - "should be about 5 minutes ahead" - ); - } -} -*/ - #[derive(Clone, Debug)] pub struct Calculation { operation: Operand, @@ -960,6 +747,7 @@ mod calculations { } } } +*/ #[allow(dead_code)] #[derive(Clone, Debug)]