Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
631 lines
19 KiB
Rust
631 lines
19 KiB
Rust
use super::{Field, FieldType};
|
|
use crate::{
|
|
mtterror::{ErrorID, MTTError},
|
|
name::NameType,
|
|
};
|
|
use chrono::{DateTime, Utc};
|
|
use std::time::Duration;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Operand {
|
|
Add,
|
|
Equal,
|
|
GreaterThan,
|
|
GreaterThanEqual,
|
|
LessThan,
|
|
LessThanEqual,
|
|
}
|
|
|
|
impl Operand {
|
|
pub fn validate(&self, x: &Field, y: &Field) -> bool {
|
|
match self {
|
|
Self::Equal => x == y,
|
|
Self::GreaterThan => x > y,
|
|
Self::GreaterThanEqual => x >= y,
|
|
Self::LessThan => x < y,
|
|
Self::LessThanEqual => x <= y,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod operands {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn equals_true() {
|
|
let data: Field = Uuid::new_v4().into();
|
|
assert!(Operand::Equal.validate(&data, &data));
|
|
}
|
|
|
|
#[test]
|
|
fn equals_false() {
|
|
let x: Field = Uuid::new_v4().into();
|
|
let mut y: Field = Uuid::new_v4().into();
|
|
while x == y {
|
|
y = Uuid::new_v4().into();
|
|
}
|
|
assert!(!Operand::Equal.validate(&x, &y));
|
|
}
|
|
|
|
#[test]
|
|
fn does_greater() {
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
assert!(!Operand::GreaterThan.validate(&data[0], &data[1]));
|
|
assert!(!Operand::GreaterThan.validate(&data[1], &data[1]));
|
|
assert!(Operand::GreaterThan.validate(&data[2], &data[1]));
|
|
}
|
|
|
|
#[test]
|
|
fn does_greater_equal() {
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
assert!(!Operand::GreaterThanEqual.validate(&data[0], &data[1]));
|
|
assert!(Operand::GreaterThanEqual.validate(&data[1], &data[1]));
|
|
assert!(Operand::GreaterThanEqual.validate(&data[2], &data[1]));
|
|
}
|
|
|
|
#[test]
|
|
fn does_lesser() {
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
assert!(Operand::LessThan.validate(&data[0], &data[1]));
|
|
assert!(!Operand::LessThan.validate(&data[1], &data[1]));
|
|
assert!(!Operand::LessThan.validate(&data[2], &data[1]));
|
|
}
|
|
|
|
#[test]
|
|
fn does_lesser_equal() {
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
assert!(Operand::LessThanEqual.validate(&data[0], &data[1]));
|
|
assert!(Operand::LessThanEqual.validate(&data[1], &data[1]));
|
|
assert!(!Operand::LessThanEqual.validate(&data[2], &data[1]));
|
|
}
|
|
}
|
|
|
|
#[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(),
|
|
}
|
|
}
|
|
|
|
pub 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<Calculation> for CalcValue {
|
|
fn from(value: Calculation) -> Self {
|
|
Self::Calculate(value)
|
|
}
|
|
}
|
|
|
|
impl From<Field> 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<FieldType> for CalcValue {
|
|
fn from(value: FieldType) -> Self {
|
|
Self::FType(value)
|
|
}
|
|
}
|
|
|
|
impl From<bool> for CalcValue {
|
|
fn from(value: bool) -> Self {
|
|
let output: Field = value.into();
|
|
Self::from(output).into()
|
|
}
|
|
}
|
|
|
|
impl From<DateTime<Utc>> for CalcValue {
|
|
fn from(value: DateTime<Utc>) -> Self {
|
|
let output: Field = value.into();
|
|
Self::from(output).into()
|
|
}
|
|
}
|
|
|
|
impl From<Duration> for CalcValue {
|
|
fn from(value: Duration) -> Self {
|
|
let output: Field = value.into();
|
|
Self::from(output).into()
|
|
}
|
|
}
|
|
|
|
impl From<i128> 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<String> for CalcValue {
|
|
fn from(value: String) -> Self {
|
|
let output: Field = value.into();
|
|
Self::from(output).into()
|
|
}
|
|
}
|
|
|
|
impl From<Uuid> 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,
|
|
values: Vec<CalcValue>,
|
|
}
|
|
|
|
impl Calculation {
|
|
pub fn new(operand: Operand) -> Self {
|
|
Self {
|
|
operation: operand,
|
|
values: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn operation(&self) -> &Operand {
|
|
&self.operation
|
|
}
|
|
|
|
fn get_fields(&self, existing: Field) -> Vec<Field> {
|
|
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<CV>(&mut self, data: CV) -> Result<(), MTTError>
|
|
where
|
|
CV: Into<CalcValue>,
|
|
{
|
|
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 {
|
|
let err = MTTError::new(ErrorID::FieldInvalidType);
|
|
return Err(err);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_value<CV>(&self, value: CV) -> Result<(), MTTError>
|
|
where
|
|
CV: Into<CalcValue>,
|
|
{
|
|
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 {
|
|
let err = MTTError::new(ErrorID::FieldInvalidType);
|
|
return Err(err);
|
|
}
|
|
}
|
|
|
|
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 = Field::Boolean(
|
|
self.values[0].get(existing) == self.values[1].get(existing),
|
|
);
|
|
}
|
|
}
|
|
Operand::GreaterThan => {
|
|
if self.values.len() == 2 {
|
|
result =
|
|
Field::Boolean(self.values[0].get(existing) > self.values[1].get(existing));
|
|
}
|
|
}
|
|
Operand::GreaterThanEqual => {
|
|
if self.values.len() == 2 {
|
|
result = Field::Boolean(
|
|
self.values[0].get(existing) >= self.values[1].get(existing),
|
|
);
|
|
}
|
|
}
|
|
Operand::LessThan => {
|
|
if self.values.len() == 2 {
|
|
result =
|
|
Field::Boolean(self.values[0].get(existing) < self.values[1].get(existing));
|
|
}
|
|
}
|
|
Operand::LessThanEqual => {
|
|
if self.values.len() == 2 {
|
|
result = Field::Boolean(
|
|
self.values[0].get(existing) <= 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) => {
|
|
let err_id = err.get_error_ids().back().unwrap();
|
|
match err_id {
|
|
ErrorID::FieldInvalidType => {}
|
|
_ => unreachable!("got {:?}, expected wrong field type", err_id),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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::<u8>().into();
|
|
let value2: i128 = random::<u8>().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::<u8>().into();
|
|
let value2: i128 = random::<u8>().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.get_error_ids().back().unwrap() {
|
|
ErrorID::FieldInvalidType => {}
|
|
_ => 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),
|
|
}
|
|
}
|
|
}
|