aurora 0.0.3

A lightweight and extensible key-value storage library in Rust.
Documentation
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::hash::Hash;
use std::marker::PhantomData;

/// Core trait for storage backends, allowing different implementations
pub trait Storage<K, V> {
    fn get(&self, key: &K) -> Option<V>;
    fn insert(&mut self, key: K, value: V) -> Option<V>;
    fn remove(&mut self, key: &K) -> Option<V>;
    fn contains_key(&self, key: &K) -> bool;
    fn clear(&mut self);
    fn len(&self) -> usize;
    fn is_empty(&self) -> bool;
}

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

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

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

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

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

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

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

    fn contains_key(&self, key: &K) -> bool {
        self.data.contains_key(key)
    }

    fn clear(&mut self) {
        self.data.clear()
    }

    fn len(&self) -> usize {
        self.data.len()
    }

    fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

/// Thread-safe database wrapper
pub struct Database<K, V, S>
where
    S: Storage<K, V>,
{
    storage: Arc<RwLock<S>>,
    _phantom: PhantomData<(K, V)>,
}

impl<K, V, S> Database<K, V, S>
where
    S: Storage<K, V>,
    K: Eq + Hash + Clone,
    V: Clone,
{
    pub fn new(storage: S) -> Self {
        Self {
            storage: Arc::new(RwLock::new(storage)),
            _phantom: PhantomData,
        }
    }

    pub fn get(&self, key: &K) -> Option<V> {
        self.storage.read().unwrap().get(key)
    }

    pub fn insert(&self, key: K, value: V) -> Option<V> {
        self.storage.write().unwrap().insert(key, value)
    }

    pub fn remove(&self, key: &K) -> Option<V> {
        self.storage.write().unwrap().remove(key)
    }

    pub fn contains_key(&self, key: &K) -> bool {
        self.storage.read().unwrap().contains_key(key)
    }

    pub fn clear(&self) {
        self.storage.write().unwrap().clear()
    }

    pub fn len(&self) -> usize {
        self.storage.read().unwrap().len()
    }

    pub fn is_empty(&self) -> bool {
        self.storage.read().unwrap().is_empty()
    }
}

impl<K, V, S> Clone for Database<K, V, S>
where
    S: Storage<K, V>,
{
    fn clone(&self) -> Self {
        Self {
            storage: Arc::clone(&self.storage),
            _phantom: PhantomData,
        }
    }
}

/// Convenience type alias for HashMap-based database
pub type HashMapDatabase<K, V> = Database<K, V, HashMapStorage<K, V>>;

impl<K, V> HashMapDatabase<K, V>
where
    K: Eq + Hash + Clone,
    V: Clone,
{
    pub fn create() -> Self {
        Self::new(HashMapStorage::new())
    }

    pub fn with_capacity(capacity: usize) -> Self {
        Self::new(HashMapStorage::with_capacity(capacity))
    }
}

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

    #[test]
    fn test_basic_operations() {
        let db = HashMapDatabase::<String, i32>::create();

        // 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 db = HashMapDatabase::<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 db = HashMapDatabase::<i32, i32>::create();
        let 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));
    }
}