aurora 0.0.5

A lightweight and extensible data storage library in Rust.
Documentation
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, Mutex};

use crate::Storage;

/// HashMap-based storage implementation
pub struct InMemoryStorage<K, V> {
    data: Arc<Mutex<HashMap<K, V>>>,
}

impl<K, V> InMemoryStorage<K, V>
where
    K: Eq + Hash,
    V: Clone,
{
    pub fn create() -> Self {
        Self {
            data: Arc::new(Mutex::new(HashMap::new())),
        }
    }

    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            data: Arc::new(Mutex::new(HashMap::with_capacity(capacity))),
        }
    }

    pub fn clone(&self) -> Self {
        Self {
            data: self.data.clone(),
        }
    }
}

impl<K, V> Default for InMemoryStorage<K, V>
where
    K: Eq + Hash,
    V: Clone,
{
    fn default() -> Self {
        Self::create()
    }
}

impl<K, V> Storage<K, V> for InMemoryStorage<K, V>
where
    K: Eq + Hash + Clone,
    V: Clone,
{
    fn get(&self, key: &K) -> Option<V> {
        self.data.lock().expect("Mutex poisoned").get(key).cloned()
    }

    fn insert(&mut self, key: K, value: V) -> Option<V> {
        self.data.lock().expect("Mutex poisoned").insert(key, value)
    }

    fn remove(&mut self, key: &K) -> Option<V> {
        self.data.lock().expect("Mutex poisoned").remove(key)
    }

    fn contains_key(&self, key: &K) -> bool {
        self.data.lock().expect("Mutex poisoned").contains_key(key)
    }

    fn clear(&mut self) {
        self.data.lock().expect("Mutex poisoned").clear()
    }

    fn len(&self) -> usize {
        self.data.lock().expect("Mutex poisoned").len()
    }

    fn is_empty(&self) -> bool {
        self.data.lock().expect("Mutex poisoned").is_empty()
    }

    fn keys(&self) -> Vec<K> {
        self.data
            .lock()
            .expect("Mutex poisoned")
            .keys()
            .cloned()
            .collect()
    }

    fn search_by_value<F>(&self, predicate: F) -> Vec<K>
    where
        F: Fn(&V) -> bool,
    {
        self.data
            .lock()
            .expect("Mutex poisoned")
            .iter()
            .filter_map(|(k, v)| {
                if predicate(v) {
                    Some(k.clone())
                } else {
                    None
                }
            })
            .collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_operations() {
        let mut db = InMemoryStorage::<String, i32>::default();

        // Insert
        assert_eq!(db.insert("key1".to_string(), 100), None);
        assert_eq!(db.len(), 1);

        // Get
        assert_eq!(db.get(&"key1".to_string()), Some(100));

        // Update
        assert_eq!(db.insert("key1".to_string(), 200), Some(100));
        assert_eq!(db.get(&"key1".to_string()), Some(200));

        // Contains
        assert!(db.contains_key(&"key1".to_string()));
        assert!(!db.contains_key(&"key2".to_string()));

        // Remove
        assert_eq!(db.remove(&"key1".to_string()), Some(200));
        assert_eq!(db.len(), 0);
        assert!(db.is_empty());
    }

    #[test]
    fn test_multiple_entries() {
        let mut db = InMemoryStorage::<i32, String>::create();

        db.insert(1, "one".to_string());
        db.insert(2, "two".to_string());
        db.insert(3, "three".to_string());

        assert_eq!(db.len(), 3);
        assert_eq!(db.get(&2), Some("two".to_string()));

        db.clear();
        assert!(db.is_empty());
    }

    #[test]
    fn test_thread_safety() {
        use std::thread;

        let mut db = InMemoryStorage::<i32, i32>::create();
        let mut db_clone = db.clone();

        let handle = thread::spawn(move || {
            db_clone.insert(1, 100);
            db_clone.insert(2, 200);
        });

        db.insert(3, 300);
        handle.join().unwrap();

        assert_eq!(db.len(), 3);
        assert_eq!(db.get(&1), Some(100));
        assert_eq!(db.get(&2), Some(200));
        assert_eq!(db.get(&3), Some(300));
    }

    #[test]
    fn test_keys_iterator() {
        let mut storage = InMemoryStorage::<i32, &str>::create();

        storage.insert(10, "ten");
        storage.insert(20, "twenty");
        storage.insert(30, "thirty");

        let mut keys = storage.keys();
        keys.sort_unstable();
        assert_eq!(keys, vec![10, 20, 30]);

        storage.remove(&20);
        let keys_after_remove = storage.keys();
        assert_eq!(keys_after_remove.len(), 2);
        assert!(keys_after_remove.contains(&10));
        assert!(keys_after_remove.contains(&30));
        assert!(!keys_after_remove.contains(&20));
    }

    #[test]
    fn test_search_by_value() {
        let mut storage = InMemoryStorage::<String, i32>::create();

        storage.insert("Alice".to_string(), 25);
        storage.insert("Bob".to_string(), 30);
        storage.insert("Charlie".to_string(), 25);
        storage.insert("Diana".to_string(), 35);
        storage.insert("Eve".to_string(), 30);

        let mut age_25 = storage.search_by_value(|&age| age == 25);
        age_25.sort();

        assert_eq!(age_25, vec!["Alice".to_string(), "Charlie".to_string()]);

        let mut age_30_plus = storage.search_by_value(|&age| age >= 30);
        age_30_plus.sort();

        assert_eq!(
            age_30_plus,
            vec![
                "Bob".to_string(),
                "Diana".to_string(),
                "Eve".to_string()
            ]
        );

        let age_100 = storage.search_by_value(|&age| age == 100);
        assert!(age_100.is_empty());

        let mut even_ages = storage.search_by_value(|&age| age % 2 == 0);
        even_ages.sort();

        assert_eq!(
            even_ages,
            vec!["Bob".to_string(), "Eve".to_string()]
        );
    }
}