rvf 0.1.0

Rust implementation of the ValueFlows vocabulary for distributed economic networks
Documentation
//! Storage abstraction for ValueFlows data
//!
//! This module provides traits and implementations for persisting
//! ValueFlows data, enabling both in-memory and persistent storage.

use crate::error::{Error, Result};
use std::collections::HashMap;
use std::sync::RwLock;

/// A generic synchronous storage trait for ValueFlows entities
pub trait Storage<T>: Send + Sync
where
    T: Clone + Send + Sync,
{
    /// Get an entity by ID
    fn get(&self, id: &str) -> Result<Option<T>>;

    /// Get all entities
    fn get_all(&self) -> Result<Vec<T>>;

    /// Store an entity
    fn put(&self, id: &str, entity: T) -> Result<()>;

    /// Delete an entity by ID
    fn delete(&self, id: &str) -> Result<bool>;

    /// Check if an entity exists
    fn exists(&self, id: &str) -> Result<bool>;

    /// Count entities
    fn count(&self) -> Result<usize>;

    /// Clear all entities
    fn clear(&self) -> Result<()>;
}

/// In-memory storage implementation
#[derive(Debug)]
pub struct InMemoryStorage<T> {
    data: RwLock<HashMap<String, T>>,
}

impl<T> Default for InMemoryStorage<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T> InMemoryStorage<T> {
    /// Create a new empty in-memory storage
    pub fn new() -> Self {
        InMemoryStorage {
            data: RwLock::new(HashMap::new()),
        }
    }

    /// Create from an existing HashMap
    pub fn from_map(map: HashMap<String, T>) -> Self {
        InMemoryStorage {
            data: RwLock::new(map),
        }
    }
}

impl<T> Storage<T> for InMemoryStorage<T>
where
    T: Clone + Send + Sync,
{
    fn get(&self, id: &str) -> Result<Option<T>> {
        let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
        Ok(data.get(id).cloned())
    }

    fn get_all(&self) -> Result<Vec<T>> {
        let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
        Ok(data.values().cloned().collect())
    }

    fn put(&self, id: &str, entity: T) -> Result<()> {
        let mut data = self.data.write().map_err(|e| Error::storage(e.to_string()))?;
        data.insert(id.to_string(), entity);
        Ok(())
    }

    fn delete(&self, id: &str) -> Result<bool> {
        let mut data = self.data.write().map_err(|e| Error::storage(e.to_string()))?;
        Ok(data.remove(id).is_some())
    }

    fn exists(&self, id: &str) -> Result<bool> {
        let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
        Ok(data.contains_key(id))
    }

    fn count(&self) -> Result<usize> {
        let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
        Ok(data.len())
    }

    fn clear(&self) -> Result<()> {
        let mut data = self.data.write().map_err(|e| Error::storage(e.to_string()))?;
        data.clear();
        Ok(())
    }
}

/// A trait for entities that can be stored with an ID
pub trait Identifiable {
    /// Get the ID of this entity
    fn id(&self) -> &str;
}

/// A repository wrapping storage with domain-specific operations
#[derive(Debug)]
pub struct Repository<T, S = InMemoryStorage<T>>
where
    T: Clone + Send + Sync,
    S: Storage<T>,
{
    storage: S,
    _marker: std::marker::PhantomData<T>,
}

impl<T> Repository<T, InMemoryStorage<T>>
where
    T: Clone + Send + Sync,
{
    /// Create a new repository with in-memory storage
    pub fn in_memory() -> Self {
        Repository {
            storage: InMemoryStorage::new(),
            _marker: std::marker::PhantomData,
        }
    }
}

impl<T, S> Repository<T, S>
where
    T: Clone + Send + Sync,
    S: Storage<T>,
{
    /// Create a new repository with the given storage backend
    pub fn new(storage: S) -> Self {
        Repository {
            storage,
            _marker: std::marker::PhantomData,
        }
    }

    /// Get an entity by ID
    pub fn get(&self, id: &str) -> Result<Option<T>> {
        self.storage.get(id)
    }

    /// Get an entity by ID, returning an error if not found
    pub fn get_required(&self, id: &str) -> Result<T> {
        self.storage
            .get(id)?
            .ok_or_else(|| Error::not_found("Entity", id))
    }

    /// Get all entities
    pub fn get_all(&self) -> Result<Vec<T>> {
        self.storage.get_all()
    }

    /// Store an entity
    pub fn put(&self, id: &str, entity: T) -> Result<()> {
        self.storage.put(id, entity)
    }

    /// Delete an entity
    pub fn delete(&self, id: &str) -> Result<bool> {
        self.storage.delete(id)
    }

    /// Check if an entity exists
    pub fn exists(&self, id: &str) -> Result<bool> {
        self.storage.exists(id)
    }

    /// Count entities
    pub fn count(&self) -> Result<usize> {
        self.storage.count()
    }

    /// Clear all entities
    pub fn clear(&self) -> Result<()> {
        self.storage.clear()
    }
}

/// A repository that automatically extracts IDs from entities
#[derive(Debug)]
pub struct AutoIdRepository<T, S = InMemoryStorage<T>>
where
    T: Clone + Send + Sync + Identifiable,
    S: Storage<T>,
{
    storage: S,
    _marker: std::marker::PhantomData<T>,
}

impl<T> AutoIdRepository<T, InMemoryStorage<T>>
where
    T: Clone + Send + Sync + Identifiable,
{
    /// Create a new repository with in-memory storage
    pub fn in_memory() -> Self {
        AutoIdRepository {
            storage: InMemoryStorage::new(),
            _marker: std::marker::PhantomData,
        }
    }
}

impl<T, S> AutoIdRepository<T, S>
where
    T: Clone + Send + Sync + Identifiable,
    S: Storage<T>,
{
    /// Create a new repository with the given storage backend
    pub fn new(storage: S) -> Self {
        AutoIdRepository {
            storage,
            _marker: std::marker::PhantomData,
        }
    }

    /// Get an entity by ID
    pub fn get(&self, id: &str) -> Result<Option<T>> {
        self.storage.get(id)
    }

    /// Get an entity by ID, returning an error if not found
    pub fn get_required(&self, id: &str) -> Result<T> {
        self.storage
            .get(id)?
            .ok_or_else(|| Error::not_found("Entity", id))
    }

    /// Get all entities
    pub fn get_all(&self) -> Result<Vec<T>> {
        self.storage.get_all()
    }

    /// Save an entity (uses entity's ID)
    pub fn save(&self, entity: T) -> Result<()> {
        let id = entity.id().to_string();
        self.storage.put(&id, entity)
    }

    /// Delete an entity by ID
    pub fn delete(&self, id: &str) -> Result<bool> {
        self.storage.delete(id)
    }

    /// Check if an entity exists
    pub fn exists(&self, id: &str) -> Result<bool> {
        self.storage.exists(id)
    }

    /// Count entities
    pub fn count(&self) -> Result<usize> {
        self.storage.count()
    }

    /// Clear all entities
    pub fn clear(&self) -> Result<()> {
        self.storage.clear()
    }
}

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

    #[derive(Debug, Clone, PartialEq)]
    struct TestEntity {
        id: String,
        name: String,
    }

    impl Identifiable for TestEntity {
        fn id(&self) -> &str {
            &self.id
        }
    }

    #[test]
    fn test_in_memory_storage_basic_operations() {
        let storage = InMemoryStorage::<TestEntity>::new();

        // Test put and get
        let entity = TestEntity {
            id: "test-1".to_string(),
            name: "Test Entity".to_string(),
        };
        storage.put("test-1", entity.clone()).unwrap();

        let retrieved = storage.get("test-1").unwrap();
        assert_eq!(retrieved, Some(entity));

        // Test exists
        assert!(storage.exists("test-1").unwrap());
        assert!(!storage.exists("nonexistent").unwrap());

        // Test count
        assert_eq!(storage.count().unwrap(), 1);

        // Test delete
        assert!(storage.delete("test-1").unwrap());
        assert!(!storage.exists("test-1").unwrap());
    }

    #[test]
    fn test_in_memory_storage_get_all() {
        let storage = InMemoryStorage::<TestEntity>::new();

        storage
            .put(
                "1",
                TestEntity {
                    id: "1".to_string(),
                    name: "One".to_string(),
                },
            )
            .unwrap();
        storage
            .put(
                "2",
                TestEntity {
                    id: "2".to_string(),
                    name: "Two".to_string(),
                },
            )
            .unwrap();

        let all = storage.get_all().unwrap();
        assert_eq!(all.len(), 2);
    }

    #[test]
    fn test_in_memory_storage_clear() {
        let storage = InMemoryStorage::<TestEntity>::new();

        storage
            .put(
                "1",
                TestEntity {
                    id: "1".to_string(),
                    name: "One".to_string(),
                },
            )
            .unwrap();

        storage.clear().unwrap();
        assert_eq!(storage.count().unwrap(), 0);
    }

    #[test]
    fn test_repository() {
        let repo = Repository::<TestEntity>::in_memory();

        let entity = TestEntity {
            id: "test-1".to_string(),
            name: "Test".to_string(),
        };

        repo.put("test-1", entity.clone()).unwrap();

        let retrieved = repo.get_required("test-1").unwrap();
        assert_eq!(retrieved, entity);

        // Test not found error
        let result = repo.get_required("nonexistent");
        assert!(result.is_err());
    }

    #[test]
    fn test_auto_id_repository() {
        let repo = AutoIdRepository::<TestEntity>::in_memory();

        let entity = TestEntity {
            id: "auto-1".to_string(),
            name: "Auto Test".to_string(),
        };

        repo.save(entity.clone()).unwrap();

        let retrieved = repo.get("auto-1").unwrap();
        assert_eq!(retrieved, Some(entity));
    }
}