use std::collections::HashSet;
use chrono::{DateTime, Utc};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{IntervalConfig, SingleEventCounter, TimeUnit};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct EventCounterConfig {
configs: Vec<IntervalConfig>,
}
impl PartialEq for EventCounterConfig {
fn eq(&self, other: &Self) -> bool {
if self.configs.len() != other.configs.len() {
return false;
}
for self_config in &self.configs {
match other
.configs
.iter()
.find(|c| c.time_unit() == self_config.time_unit())
{
Some(other_config) => {
if self_config.bucket_count() != other_config.bucket_count() {
return false;
}
}
None => return false,
}
}
true
}
}
impl EventCounterConfig {
pub fn new(configs: Vec<IntervalConfig>) -> Self {
let mut seen_units = HashSet::new();
for config in &configs {
let unit = config.time_unit();
if !seen_units.insert(unit) {
panic!("Duplicate TimeUnit in EventCounterConfig: {:?}", unit);
}
}
let mut configs = configs;
configs.sort_by_key(|c| c.time_unit());
Self { configs }
}
#[allow(dead_code)] pub(crate) fn from_intervals(configs: &[IntervalConfig]) -> Self {
Self::new(configs.to_vec())
}
pub fn create_counter(&self, starting_instant: DateTime<Utc>) -> SingleEventCounter {
SingleEventCounter::new(self, starting_instant)
}
#[allow(dead_code)]
pub(crate) fn get(&self, time_unit: TimeUnit) -> Option<&IntervalConfig> {
self.configs
.iter()
.find(|config| config.time_unit() == time_unit)
}
#[allow(dead_code)]
pub(crate) fn time_units(&self) -> impl Iterator<Item = TimeUnit> + '_ {
self.configs.iter().map(|config| config.time_unit())
}
pub(crate) fn configs(&self) -> &[IntervalConfig] {
&self.configs
}
pub(crate) fn as_vec(&self) -> Vec<IntervalConfig> {
self.configs.clone()
}
#[allow(dead_code)]
pub(crate) fn len(&self) -> usize {
self.configs.len()
}
pub(crate) fn specified_time_unit(&self, time_unit: TimeUnit) -> TimeUnit {
match time_unit {
TimeUnit::Ever => self.longest_time_unit().unwrap_or(TimeUnit::Days),
_ => time_unit,
}
}
pub(crate) fn longest_time_unit(&self) -> Option<TimeUnit> {
self.configs.iter().map(|c| c.time_unit()).max()
}
}
impl Default for EventCounterConfig {
fn default() -> Self {
Self::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
IntervalConfig::new_unchecked(56, TimeUnit::Days),
IntervalConfig::new_unchecked(52, TimeUnit::Weeks),
IntervalConfig::new_unchecked(12, TimeUnit::Months),
IntervalConfig::new_unchecked(4, TimeUnit::Years),
])
}
}
#[cfg(test)]
mod tests {
use super::*;
impl EventCounterConfig {
fn empty() -> Self {
Self::new(Vec::default())
}
}
#[test]
fn test_new_stores_configs_in_vec() {
let config = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
assert_eq!(config.len(), 2);
}
#[test]
fn test_get_finds_by_time_unit() {
let config = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
let minutes = config.get(TimeUnit::Minutes);
assert!(minutes.is_some());
assert_eq!(minutes.unwrap().bucket_count(), 60);
let hours = config.get(TimeUnit::Hours);
assert!(hours.is_some());
assert_eq!(hours.unwrap().bucket_count(), 72);
let days = config.get(TimeUnit::Days);
assert!(days.is_none());
}
#[test]
fn test_as_vec_clones_internal_vec() {
let config = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
let vec = config.as_vec();
assert_eq!(vec.len(), 2);
}
#[test]
fn test_time_units_iterator() {
let config = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
IntervalConfig::new_unchecked(56, TimeUnit::Days),
]);
let units: Vec<TimeUnit> = config.time_units().collect();
assert_eq!(units.len(), 3);
assert!(units.contains(&TimeUnit::Minutes));
assert!(units.contains(&TimeUnit::Hours));
assert!(units.contains(&TimeUnit::Days));
}
#[test]
#[should_panic(expected = "Duplicate TimeUnit")]
fn test_new_panics_on_duplicate_time_units() {
EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(120, TimeUnit::Minutes), ]);
}
#[test]
fn test_default_config_has_six_intervals() {
let config = EventCounterConfig::default();
assert_eq!(config.len(), 6);
}
#[test]
fn test_empty_creates_config_with_no_intervals() {
let config = EventCounterConfig::empty();
assert_eq!(config.len(), 0);
assert!(config.get(TimeUnit::Minutes).is_none());
assert!(config.get(TimeUnit::Hours).is_none());
}
#[test]
fn test_empty_config_as_vec_is_empty() {
let config = EventCounterConfig::empty();
let vec = config.as_vec();
assert_eq!(vec.len(), 0);
}
#[test]
fn test_from_intervals_creates_config_from_slice() {
let intervals = [
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
];
let config = EventCounterConfig::from_intervals(&intervals);
assert_eq!(config.len(), 2);
assert!(config.get(TimeUnit::Minutes).is_some());
assert!(config.get(TimeUnit::Hours).is_some());
}
#[test]
fn test_from_intervals_empty_slice() {
let intervals: [IntervalConfig; 0] = [];
let config = EventCounterConfig::from_intervals(&intervals);
assert_eq!(config.len(), 0);
}
#[test]
#[should_panic(expected = "Duplicate TimeUnit")]
fn test_from_intervals_panics_on_duplicates() {
let intervals = [
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(120, TimeUnit::Minutes), ];
EventCounterConfig::from_intervals(&intervals);
}
#[test]
fn test_partial_eq_same_configs_same_order() {
let config1 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
let config2 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
assert_eq!(config1, config2);
}
#[test]
fn test_partial_eq_same_configs_different_order() {
let config1 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
let config2 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
]);
assert_eq!(config1, config2);
}
#[test]
fn test_partial_eq_different_bucket_counts() {
let config1 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
let config2 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(24, TimeUnit::Hours), ]);
assert_ne!(config1, config2);
}
#[test]
fn test_partial_eq_different_time_units() {
let config1 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
let config2 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(56, TimeUnit::Days), ]);
assert_ne!(config1, config2);
}
#[test]
fn test_partial_eq_different_lengths() {
let config1 = EventCounterConfig::new(vec![
IntervalConfig::new_unchecked(60, TimeUnit::Minutes),
IntervalConfig::new_unchecked(72, TimeUnit::Hours),
]);
let config2 =
EventCounterConfig::new(vec![IntervalConfig::new_unchecked(60, TimeUnit::Minutes)]);
assert_ne!(config1, config2);
}
#[test]
fn test_partial_eq_empty_configs() {
let config1 = EventCounterConfig::empty();
let config2 = EventCounterConfig::empty();
assert_eq!(config1, config2);
}
#[test]
fn test_partial_eq_default_configs() {
let config1 = EventCounterConfig::default();
let config2 = EventCounterConfig::default();
assert_eq!(config1, config2);
}
}