arts-n-crafts 0.1.3

Arts and Crafts was a reaction against the industrialization and excess of the Victorian era, and it sought to celebrate traditional craftsmanship and materials. Arts and Crafts architecture is characterized by a simple and functional design, the use of natural materials, and a focus on craftsmanship.
Documentation
use serde::Serialize;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(Debug, PartialEq)]
pub enum DatabaseError {
    Unreachable,
}

pub struct InMemoryDatabase<T>
where
    T: Serialize + Send + Sync + Clone,
{
    data: Arc<Mutex<HashMap<String, Vec<T>>>>,
    is_offline: bool,
}

impl<T> InMemoryDatabase<T>
where
    T: Serialize + Send + Sync + Clone,
{
    pub fn new() -> Self {
        InMemoryDatabase {
            data: Arc::new(Mutex::new(HashMap::new())),
            is_offline: false,
        }
    }

    pub fn go_offline(&mut self) {
        self.is_offline = true
    }

    pub async fn store(&self, key: &str, value: T) -> Result<(), DatabaseError> {
        if self.is_offline {
            return Err(DatabaseError::Unreachable);
        }
        let mut data = self.data.lock().await;
        data.entry(key.to_owned())
            .or_insert_with(Vec::new)
            .push(value.to_owned());

        Ok(())
    }

    pub async fn query(&self, table_name: &str) -> Result<Vec<T>, DatabaseError> {
        if self.is_offline {
            return Err(DatabaseError::Unreachable);
        }

        let data = self.data.lock().await;
        let result = data.get(table_name).cloned().unwrap_or_default();
        Ok(result)
    }
}

impl<T> Default for InMemoryDatabase<T>
where
    T: Serialize + Send + Sync + Clone,
{
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod in_memory_database_tests {
    use super::*;
    use rstest::{fixture, rstest};
    const TABLE_NAME: &str = "users";

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

    #[fixture]
    fn user() -> Vec<User> {
        vec![User {
            name: "John".to_string(),
        }]
    }

    #[rstest]
    #[tokio::test]
    async fn should_store_the_data(user: Vec<User>) {
        let db = InMemoryDatabase::default();
        let result = db.store(TABLE_NAME, user[0].to_owned()).await;
        assert!(result.is_ok());
    }

    #[rstest]
    #[tokio::test]
    async fn should_fail_storing_if_database_is_offline(user: Vec<User>) {
        let mut db = InMemoryDatabase::new();
        db.go_offline();
        let result = db.store(TABLE_NAME, user[0].to_owned()).await;
        assert!(result.is_err());
        assert_eq!(result.err().unwrap(), DatabaseError::Unreachable);
    }

    #[rstest]
    #[tokio::test]
    async fn should_query_the_data(user: Vec<User>) {
        let db = InMemoryDatabase::new();
        db.store(TABLE_NAME, user[0].to_owned())
            .await
            .expect("unexpected store failed");
        let result = db.query(TABLE_NAME).await;
        assert!(result.is_ok());
    }

    #[rstest]
    #[tokio::test]
    async fn should_fail_querying_if_database_is_offline() {
        let mut db = InMemoryDatabase::<User>::new();
        db.go_offline();
        let result = db.query(TABLE_NAME).await;
        assert!(result.is_err());
        assert_eq!(result.err().unwrap(), DatabaseError::Unreachable);
    }
}