use indexmap::IndexMap;
use std::hash::Hash;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq)]
pub struct ExpiryEntry<V, T> {
value: V,
expiry: T,
}
impl<V, T> ExpiryEntry<V, T> {
fn new(value: V, expiry: T) -> Self {
Self { value, expiry }
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Default)]
pub struct ExpireMap<K, V, T>
where
K: Eq + Hash + Clone,
{
map: IndexMap<K, ExpiryEntry<V, T>>,
}
impl<K, V, T> ExpireMap<K, V, T>
where
K: Eq + Hash + Clone,
{
pub fn new() -> Self {
Self {
map: IndexMap::new(),
}
}
}
impl<K, V, T> ExpireMap<K, V, T>
where
K: Eq + Hash + Clone,
T: Ord,
{
pub fn insert(&mut self, key: K, value: V, expiry: T) -> Option<ExpiryEntry<V, T>> {
let insert_idx = self.map.partition_point(|_, entry| entry.expiry < expiry);
let entry = ExpiryEntry::new(value, expiry);
self.map.insert_before(insert_idx, key, entry).1
}
pub fn get(&self, key: &K) -> Option<&V> {
self.map.get(key).map(|x| &x.value)
}
pub fn expire<'a>(&'a mut self, expiry: &T) -> ExpiredIterator<'a, K, ExpiryEntry<V, T>> {
let expiry_index = self.map.partition_point(|_, entry| entry.expiry <= *expiry);
ExpiredIterator(self.map.drain(0..expiry_index))
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
}
pub struct ExpiredIterator<'a, K, V>(indexmap::map::Drain<'a, K, V>);
impl<K, V> Iterator for ExpiredIterator<'_, K, V> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_and_get() {
let mut map = ExpireMap::new();
map.insert("key", "HelloWorld", 42);
let value = map.get(&"key");
assert!(matches!(value, Some(&"HelloWorld")))
}
#[test]
fn insert_retrieves() {
let mut map = ExpireMap::new();
map.insert("key", "HelloWorld", 42);
let prev = map.insert("key", "HelloMom", 11);
assert!(matches!(
prev,
Some(ExpiryEntry {
value: "HelloWorld",
expiry: 42
})
))
}
#[test]
fn get_after_expired() {
let mut map = ExpireMap::new();
map.insert("key", "HelloWorld", 42);
map.expire(&42);
assert!(map.get(&"key").is_none())
}
#[test]
fn get_not_expired() {
let mut map = ExpireMap::new();
map.insert("key", "HelloWorld", 42);
map.expire(&41);
assert!(map.get(&"key").is_some())
}
#[test]
fn expiry_insertion_order() {
let mut map = ExpireMap::new();
map.insert("key0", "HelloWorld", 5);
map.insert("key1", "HelloWorld", 2);
map.insert("key2", "HelloWorld", 7);
map.insert("key3", "HelloWorld", -22);
map.expire(&4);
for k in ["key1", "key3"] {
assert!(map.get(&k).is_none())
}
for k in ["key0", "key2"] {
assert!(map.get(&k).is_some())
}
}
#[test]
fn expired_as_iterator() {
let mut map = ExpireMap::new();
let vals = ["foo", "bar", "baz"];
for (i, x) in vals.clone().into_iter().enumerate() {
map.insert(x, x, i);
}
let expired: Vec<(&str, ExpiryEntry<&str, usize>)> = map.expire(&3).collect();
let expected: Vec<(&str, ExpiryEntry<&str, usize>)> = vals
.into_iter()
.enumerate()
.map(|(i, x)| (x, ExpiryEntry::new(x, i)))
.collect();
assert_eq!(expired, expected)
}
#[test]
fn is_empty() {
let mut map = ExpireMap::new();
assert!(map.is_empty());
let vals = ["foo", "bar", "baz"];
for (i, x) in vals.into_iter().enumerate() {
map.insert(x, x, i);
}
assert!(!map.is_empty());
let _ = map.expire(&1);
assert!(!map.is_empty());
let _ = map.expire(&2);
assert!(map.is_empty());
}
#[test]
fn len() {
let mut map = ExpireMap::new();
assert_eq!(0, map.len());
let vals = ["foo", "bar", "baz"];
for (i, x) in vals.into_iter().enumerate() {
map.insert(x, x, i);
}
assert_eq!(3, map.len());
let _ = map.expire(&1);
assert_eq!(1, map.len());
let _ = map.expire(&2);
assert_eq!(0, map.len());
}
}