pyruse/src/domain/counter.rs

267 lines
8.0 KiB
Rust

use crate::domain::Value;
use chrono::{DateTime, Utc};
pub type CounterRef<'t> = (&'t str, &'t Value);
pub type CounterData = (usize, Option<DateTime<Utc>>);
pub trait CountersPort {
fn modify(
&mut self,
entry: CounterRef,
data: CounterData,
f: impl FnMut(&mut CounterData, CounterData) -> usize,
) -> usize;
fn remove(&mut self, entry: CounterRef) -> Option<CounterData>;
fn remove_if(&mut self, predicate: impl Fn(&CounterData) -> bool);
}
pub struct Counters<P: CountersPort> {
backend: P,
}
impl<P: CountersPort> Counters<P> {
pub fn new<X: CountersPort>(backend: X) -> Counters<X> {
Counters { backend }
}
fn grace_active(data: &CounterData) -> bool {
if let Some(dt) = data.1 {
data.0 == 0 && dt > Utc::now()
} else {
false
}
}
fn clean(&mut self) {
let now = Utc::now();
self.backend.remove_if(|c_data| {
if let (_, Some(dt)) = c_data {
let ref_now = &now;
return dt <= ref_now;
}
false
});
}
pub fn set(&mut self, entry: CounterRef, data: CounterData) -> usize {
self.clean();
self.backend.modify(entry, data, |value, data| {
*value = data;
(*value).0
})
}
pub fn augment(&mut self, entry: CounterRef, data: CounterData) -> usize {
self.clean();
self.backend.modify(entry, data, |value, data| {
if !Counters::<P>::grace_active(&value) {
(*value).0 = (*value).0 + data.0;
if let Some(wanted_dt) = data.1 {
match value.1 {
Some(existing_dt) if existing_dt < wanted_dt => value.1 = data.1,
None => value.1 = data.1,
_ => (),
}
}
}
(*value).0
})
}
pub fn reset(&mut self, entry: CounterRef, grace_until: Option<DateTime<Utc>>) -> usize {
self.clean();
match grace_until {
Some(_) => {
// a grace-time is wanted, so the entry must exist…
self.backend.modify(entry, (0, grace_until), |value, data| {
match value {
// … and its grace-time is set to the farther value between existing and requested
(0, Some(existing_dt)) if *existing_dt > data.1.unwrap() => (),
_ => (*value) = data,
};
0
})
}
None => {
// no grace-time wanted, so the entry is deleted…
if let Some((0, Some(existing_dt))) = self.backend.remove(entry) {
// … unless an existing grace-time was found
self
.backend
.modify(entry, (0, Some(existing_dt)), |value, data| {
*value = data;
0
})
} else {
0
}
}
}
}
}
#[macro_use]
#[cfg(test)]
mod tests {
use crate::domain::test_util::FakeCountersAdapter;
use crate::domain::{CounterData, Counters, Singleton, Value};
use crate::{singleton_borrow, singleton_new, singleton_share};
use chrono::{Duration, Utc};
use std::collections::HashMap;
use std::{thread, time};
#[test]
fn set_forces_the_value_of_a_counter() {
let (counters_store, mut counters) = get_store_counters();
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Int(5));
let value = counters.set(c_ref, (9, None));
assert_eq!(value, 9);
let stored_value = singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
.0;
assert_eq!(stored_value, 9);
}
#[test]
fn a_counter_starts_from_0() {
let (counters_store, mut counters) = get_store_counters();
let (_, stored_key) = get_ref_and_key("test", &Value::Bool(true));
let stored_value = singleton_borrow!(counters_store)
.get_mut(&stored_key)
.map(|_| 0);
assert_eq!(stored_value, None);
let value = counters.augment(("test", &Value::Bool(true)), (1, None));
assert_eq!(value, 1);
let stored_value = singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
.0;
assert_eq!(stored_value, 1);
}
#[test]
fn augment_raises_a_counter_by_its_amount() {
let (_, mut counters) = get_store_counters();
let str_value = Value::Str("string".to_string());
counters.set(("test", &str_value), (4, None));
let value = counters.augment(("test", &str_value), (3, None));
assert_eq!(value, 7);
}
#[test]
fn reset_without_gracetime_removes_a_counter() {
let (counters_store, mut counters) = get_store_counters();
let now = Utc::now();
let date_value = Value::Date(now.clone());
let (c_ref, stored_key) = get_ref_and_key("test", &date_value);
counters.augment(c_ref, (5, None));
let stored_value = singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
.0;
assert_eq!(stored_value, 5);
let value = counters.reset(("test", &Value::Date(now)), None);
assert_eq!(value, 0);
let stored_value = singleton_borrow!(counters_store)
.get_mut(&stored_key)
.map(|_| 0);
assert_eq!(stored_value, None);
}
#[test]
fn augment_records_the_longest_datetime() {
let (counters_store, mut counters) = get_store_counters();
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
let old_dt = Utc::now() + Duration::minutes(1);
let new_dt = Utc::now() + Duration::minutes(1);
assert!(old_dt < new_dt);
counters.augment(c_ref.clone(), (1, Some(new_dt)));
counters.augment(c_ref, (3, Some(old_dt)));
let stored_dt = singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
.1
.unwrap();
assert_eq!(stored_dt, new_dt);
}
#[test]
fn augment_without_timeout_is_ignored_in_the_presence_of_a_gracetime() {
let (counters_store, mut counters) = get_store_counters();
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
let future_dt = Utc::now() + Duration::days(1);
counters.reset(c_ref.clone(), Some(future_dt));
let value = counters.augment(c_ref, (3, None));
assert_eq!(value, 0);
assert_eq!(
&(0 as usize, Some(future_dt)),
singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
);
}
#[test]
fn augment_with_timeout_is_ignored_in_the_presence_of_a_gracetime() {
let (counters_store, mut counters) = get_store_counters();
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
let future_dt = Utc::now() + Duration::days(1);
let soon_dt = Utc::now() + Duration::hours(1);
counters.reset(c_ref.clone(), Some(future_dt));
let value = counters.augment(c_ref, (3, Some(soon_dt)));
assert_eq!(value, 0);
assert_eq!(
&(0 as usize, Some(future_dt)),
singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
);
}
#[test]
fn augment_also_cleans_obsolete_counters() {
let (counters_store, mut counters) = get_store_counters();
let (c_ref, stored_key) = get_ref_and_key("test", &Value::Bool(true));
let future_dt = Utc::now() + Duration::milliseconds(500);
counters.augment(c_ref, (3, Some(future_dt)));
assert_eq!(1, singleton_borrow!(counters_store).len());
assert_eq!(
3,
singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
.0
);
thread::sleep(time::Duration::from_secs(1));
let (c_ref, stored_key) = get_ref_and_key("test2", &Value::Bool(false));
let value = counters.augment(c_ref, (5, None));
assert_eq!(value, 5);
assert_eq!(1, singleton_borrow!(counters_store).len());
assert_eq!(
5,
singleton_borrow!(counters_store)
.get_mut(&stored_key)
.unwrap()
.0
);
}
fn get_store_counters() -> (
Singleton<HashMap<(String, Value), CounterData>>,
Counters<FakeCountersAdapter>,
) {
let counters_store = singleton_new!(HashMap::new());
let counters = Counters::<FakeCountersAdapter>::new(FakeCountersAdapter {
counters: singleton_share!(&counters_store),
});
(counters_store, counters)
}
fn get_ref_and_key<'t>(s: &'t str, v: &'t Value) -> ((&'t str, &'t Value), (String, Value)) {
let storage_key = (s.to_string(), v.clone());
let key_ref = (s, v);
(key_ref, storage_key)
}
}