hashlru 0.11.1

HashLRU is a LRU cache.
Documentation
// Copyright (C) 2024 Christian Mauduit <ufoot@ufoot.org>

use crate::cache::Cache;
use crate::sync_cache::SyncCache;
use std::collections::HashMap;
use std::hash::Hash;

/// Complete dump of a LRU cache, easily (de)serializable.
///
/// It reflects the complete current state of the cache, including
/// key/value pairs but also capacity and order.
///
/// Can be used to import/export the cache and serialize/unserialize it.
///
/// # Examples
///
/// ```
/// use hashlru::{Cache, Dump};
///
/// let mut cache: Cache<&str, usize> = Cache::new(10);
/// cache.insert("x", 1);
/// cache.insert("y", 10);
/// cache.insert("z", 100);
///
/// let dump: Dump<&str, usize> = Dump::from(cache);
/// assert_eq!("Dump { capacity: 10, data: [(\"x\", 1), (\"y\", 10), (\"z\", 100)] }", format!("{:?}", dump));
/// let restore: Cache<&str, usize> = Cache::from(dump);
/// assert_eq!(10, restore.capacity());
/// assert_eq!("[x: 1, y: 10, z: 100]", format!("{}", &restore));
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug)]
pub struct Dump<K, V> {
    pub capacity: usize,
    pub data: Vec<(K, V)>,
}

impl<K, V> From<Vec<(K, V)>> for Cache<K, V>
where
    K: Hash + Eq,
{
    fn from(vec: Vec<(K, V)>) -> Cache<K, V> {
        let mut cache = Cache::new(vec.len());
        vec.into_iter().map(|x| cache.insert(x.0, x.1)).count();
        cache
    }
}

impl<K, V> From<HashMap<K, V>> for Cache<K, V>
where
    K: Hash + Eq,
{
    fn from(map: HashMap<K, V>) -> Cache<K, V> {
        let mut cache = Cache::new(map.len());
        map.into_iter().map(|x| cache.insert(x.0, x.1)).count();
        cache
    }
}

impl<K, V> From<Dump<K, V>> for Cache<K, V>
where
    K: Hash + Eq,
{
    fn from(dump: Dump<K, V>) -> Cache<K, V> {
        let mut cache = Cache::from(dump.data);
        cache.resize(dump.capacity);
        cache
    }
}

impl<K, V> From<Vec<(K, V)>> for SyncCache<K, V>
where
    K: Hash + Eq + Clone,
    V: Clone,
{
    fn from(vec: Vec<(K, V)>) -> SyncCache<K, V> {
        let cache = SyncCache::new(vec.len());
        vec.into_iter().map(|x| cache.insert(x.0, x.1)).count();
        cache
    }
}

impl<K, V> From<HashMap<K, V>> for SyncCache<K, V>
where
    K: Hash + Eq + Clone,
    V: Clone,
{
    fn from(map: HashMap<K, V>) -> SyncCache<K, V> {
        let cache = SyncCache::new(map.len());
        map.into_iter().map(|x| cache.insert(x.0, x.1)).count();
        cache
    }
}

impl<K, V> From<Dump<K, V>> for SyncCache<K, V>
where
    K: Hash + Eq + Clone,
    V: Clone,
{
    fn from(dump: Dump<K, V>) -> SyncCache<K, V> {
        let cache = SyncCache::from(dump.data);
        cache.resize(dump.capacity);
        cache
    }
}

impl<K, V> From<Cache<K, V>> for Vec<(K, V)>
where
    K: Hash + Eq,
{
    fn from(cache: Cache<K, V>) -> Vec<(K, V)> {
        let mut vec = Vec::with_capacity(cache.len());
        cache.into_iter().map(|x| vec.push(x)).count();
        vec
    }
}

impl<K, V> From<Cache<K, V>> for HashMap<K, V>
where
    K: Hash + Eq,
{
    fn from(cache: Cache<K, V>) -> HashMap<K, V> {
        let mut map = HashMap::with_capacity(cache.len());
        cache.into_iter().map(|x| map.insert(x.0, x.1)).count();
        map
    }
}

impl<K, V> From<Cache<K, V>> for Dump<K, V>
where
    K: Hash + Eq,
{
    fn from(cache: Cache<K, V>) -> Dump<K, V> {
        let capacity = cache.capacity();
        let data: Vec<(K, V)> = Vec::from(cache);
        Dump { capacity, data }
    }
}

impl<K, V> PartialEq<Dump<K, V>> for Dump<K, V>
where
    K: Hash + Eq,
    V: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        if self.capacity != other.capacity {
            return false;
        }
        self.data == other.data
    }
}

impl<K, V> Eq for Dump<K, V>
where
    K: Hash + Eq + Clone,
    V: Eq,
{
}

#[cfg(test)]
mod tests {
    #[cfg(feature = "serde")]
    #[test]
    fn test_json() {
        use super::{Cache, Dump};
        use serde_json::json;

        let mut cache = Cache::new(10);

        cache.insert(1, 10);
        cache.insert(2, 20);

        let dump1 = cache.dump();

        let export = json!(&dump1).to_string();

        assert_eq!("{\"capacity\":10,\"data\":[[1,10],[2,20]]}", export);

        let dump2: Dump<usize, usize> = serde_json::from_str(&export).unwrap();

        cache.clear();
        assert_eq!(0, cache.len());
        cache.restore(&dump2);
        assert_eq!(2, cache.len());
    }

    #[cfg(feature = "serde")]
    #[test]
    fn test_postcard() {
        extern crate alloc;
        use super::{Cache, Dump};
        use alloc::vec::Vec;
        use postcard::{from_bytes, to_allocvec};

        let mut cache = Cache::<usize, usize>::new(1_000);

        for i in 0..cache.capacity() / 2 {
            cache.insert(i, i * 10);
        }

        let dump1 = cache.dump();

        let export: Vec<u8> = to_allocvec(&dump1).unwrap();

        println!("export len: {}", export.len());

        let dump2: Dump<usize, usize> = from_bytes(&export).unwrap();

        println!("dump1 capacity: {}", dump1.capacity);
        println!("dump2 capacity: {}", dump2.capacity);
        assert_eq!(&dump1, &dump2);
    }
}