pyruse/src/infra/counter.rs

181 lines
5.9 KiB
Rust

use crate::domain::{CounterData, CounterRef, CountersPort, Value};
use std::collections::HashMap;
type CounterKeys = HashMap<Value, CounterData>;
pub struct InMemoryCounterAdapter {
counters: HashMap<String, CounterKeys>,
}
impl InMemoryCounterAdapter {
pub fn new() -> Self {
InMemoryCounterAdapter {
counters: HashMap::new(),
}
}
}
impl CountersPort for InMemoryCounterAdapter {
fn modify(
&mut self,
entry: CounterRef,
data: CounterData,
mut f: impl FnMut(&mut CounterData, CounterData) -> usize,
) -> usize {
if !self.counters.contains_key(entry.0) {
self.counters.insert(entry.0.to_string(), HashMap::new());
}
let keys = self.counters.get_mut(entry.0).unwrap();
if !keys.contains_key(entry.1) {
keys.insert(entry.1.clone(), (0, None));
}
f(keys.get_mut(entry.1).unwrap(), data)
}
fn remove(&mut self, entry: CounterRef) -> Option<CounterData> {
let (to_remove, option) = match self.counters.get_mut(entry.0) {
None => (false, None),
Some(keys) => match keys.remove(entry.1) {
None => (false, None),
Some(d) => (keys.is_empty(), Some(d)),
},
};
if to_remove {
self.counters.remove(entry.0);
}
option
}
fn remove_if(&mut self, predicate: impl Fn(&CounterData) -> bool) {
self.counters.retain(|_, name| {
name.retain(|_, data| !predicate(data));
!name.is_empty()
});
}
}
#[cfg(test)]
mod tests {
use super::{CounterKeys, InMemoryCounterAdapter};
use crate::domain::{CountersPort, Value};
use chrono::Utc;
use std::collections::HashMap;
#[test]
fn modify_allows_modifying_an_entry_and_returns_the_new_value() {
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
let counter = "counter";
let key = Value::Str("1.2.3.4".to_string());
let new_data = (2, None);
counters.insert(counter.to_string(), HashMap::new());
counters
.get_mut(counter)
.unwrap()
.insert(key.clone(), (1, Some(Utc::now())));
let mut adapter = InMemoryCounterAdapter { counters };
let new_value = adapter.modify((counter, &key), new_data.clone(), |existing, new| {
*existing = new;
(*existing).0
});
assert_eq!(2, new_value);
assert_eq!(
&(2 as usize, None),
adapter.counters.get(counter).unwrap().get(&key).unwrap()
);
}
#[test]
fn after_remove_the_entry_is_not_there() {
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
let counter = "counter";
let key1 = Value::Str("1.2.3.4".to_string());
let key2 = Value::Bool(true);
let data1 = (2, None);
let data2 = (5, None);
counters.insert(counter.to_string(), HashMap::new());
let map = counters.get_mut(counter).unwrap();
map.insert(key1.clone(), data1);
map.insert(key2.clone(), data2);
let mut adapter = InMemoryCounterAdapter { counters };
let removed = adapter.remove((counter, &key1));
assert_eq!(Some(data1), removed);
assert!(!adapter.counters.get(counter).unwrap().contains_key(&key1));
assert_eq!(
&data2,
adapter.counters.get(counter).unwrap().get(&key2).unwrap()
);
}
#[test]
fn remove_on_unexisting_entry_does_nothing_and_returns_none() {
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
let counter = "counter";
let key1 = Value::Str("1.2.3.4".to_string());
let key2 = Value::Bool(true);
let data1 = (2, None);
counters.insert(counter.to_string(), HashMap::new());
let map = counters.get_mut(counter).unwrap();
map.insert(key1.clone(), data1);
let mut adapter = InMemoryCounterAdapter { counters };
let removed = adapter.remove((counter, &key2));
assert_eq!(None, removed);
assert!(adapter.counters.get(counter).unwrap().contains_key(&key1));
assert_eq!(
&data1,
adapter.counters.get(counter).unwrap().get(&key1).unwrap()
);
}
#[test]
fn after_last_key_is_removed_by_remove_counter_is_also_removed() {
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
let counter = "counter";
let key1 = Value::Str("1.2.3.4".to_string());
let key2 = Value::Bool(true);
let data1 = (2, None);
let data2 = (5, None);
counters.insert(counter.to_string(), HashMap::new());
let map = counters.get_mut(counter).unwrap();
map.insert(key1.clone(), data1);
map.insert(key2.clone(), data2);
let mut adapter = InMemoryCounterAdapter { counters };
assert_eq!(Some(data1), adapter.remove((counter, &key1)));
assert_eq!(Some(data2), adapter.remove((counter, &key2)));
assert!(!adapter.counters.contains_key(counter));
}
#[test]
fn removeif_removes_entries_that_match_the_predicate() {
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
let counter = "counter";
let key1 = Value::Str("1.2.3.4".to_string());
let key2 = Value::Bool(true);
let data1 = (2, None);
let data2 = (5, None);
counters.insert(counter.to_string(), HashMap::new());
let map = counters.get_mut(counter).unwrap();
map.insert(key1.clone(), data1);
map.insert(key2.clone(), data2);
let mut adapter = InMemoryCounterAdapter { counters };
adapter.remove_if(|(u, _)| *u == 2);
assert_eq!(1, adapter.counters.get(counter).unwrap().len());
assert_eq!(
Some(&(5 as usize, None)),
adapter.counters.get(counter).unwrap().get(&key2)
);
}
#[test]
fn after_last_key_is_removed_by_removeif_counter_is_also_removed() {
let mut counters: HashMap<String, CounterKeys> = HashMap::new();
let counter = "counter";
let key1 = Value::Str("1.2.3.4".to_string());
let data1 = (2, None);
counters.insert(counter.to_string(), HashMap::new());
let map = counters.get_mut(counter).unwrap();
map.insert(key1.clone(), data1);
let mut adapter = InMemoryCounterAdapter { counters };
adapter.remove_if(|(u, _)| *u == 2);
assert_eq!(0, adapter.counters.len());
}
}