use std::collections::VecDeque;
use std::time::Instant;
pub struct Deduplicator {
seen: VecDeque<(String, Instant)>,
max_entries: usize,
ttl_secs: u64,
}
impl Deduplicator {
pub fn new(max_entries: usize, ttl_secs: u64) -> Self {
Self {
seen: VecDeque::new(),
max_entries,
ttl_secs,
}
}
pub fn is_new(&mut self, event_id: &str) -> bool {
let now = Instant::now();
while let Some((_, ts)) = self.seen.front() {
if now.duration_since(*ts).as_secs() >= self.ttl_secs {
self.seen.pop_front();
} else {
break;
}
}
if self.seen.iter().any(|(id, _)| id == event_id) {
return false;
}
if self.seen.len() >= self.max_entries {
self.seen.pop_front();
}
self.seen.push_back((event_id.to_string(), now));
true
}
}
impl Default for Deduplicator {
fn default() -> Self {
Self::new(200, 600)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_new_returns_true_then_false() {
let mut dedup = Deduplicator::new(10, 600);
assert!(dedup.is_new("evt_1"));
assert!(!dedup.is_new("evt_1"));
}
#[test]
fn test_distinct_ids_are_new() {
let mut dedup = Deduplicator::new(10, 600);
assert!(dedup.is_new("evt_1"));
assert!(dedup.is_new("evt_2"));
assert!(dedup.is_new("evt_3"));
}
#[test]
fn test_evicts_oldest_at_capacity() {
let mut dedup = Deduplicator::new(3, 600);
assert!(dedup.is_new("evt_1"));
assert!(dedup.is_new("evt_2"));
assert!(dedup.is_new("evt_3"));
assert!(dedup.is_new("evt_4"));
assert!(dedup.is_new("evt_1"));
assert!(dedup.is_new("evt_2"));
}
}