datasources 0.1.5

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use crate::command::Command;
use crate::connection::Connection;
use crate::datasource_error::DatasourceError;
use crate::isolation_level::IsolationLevel;
use crate::result::Result;
use crate::transaction::Transaction;
use async_trait::async_trait;
use std::sync::{Arc, Mutex};

use super::mock_command::MockCommand;
use super::mock_transaction::MockTransaction;

#[derive(Default)]
struct CallState {
    open_calls: usize,
    close_calls: usize,
    start_calls: usize,
    open_error: Option<String>,
    close_error: Option<String>,
    start_error: Option<String>,
    opened: bool,
    last_isolation: IsolationLevel,
}

pub struct MockConnection {
    state: Mutex<CallState>,
    transaction: Arc<dyn Transaction>,
    mock_transaction: Option<Arc<MockTransaction>>,
    command: Arc<dyn Command>,
    provider: String,
}

impl MockConnection {
    pub fn new() -> Arc<Self> {
        Self::with_command(Arc::new(MockCommand::new()))
    }

    pub fn with_command(command: Arc<dyn Command>) -> Arc<Self> {
        Self::with_command_provider(command, "mock")
    }

    pub fn with_command_provider(
        command: Arc<dyn Command>,
        provider: impl Into<String>,
    ) -> Arc<Self> {
        let mock_transaction = Arc::new(MockTransaction::new_unbound());
        Arc::new(Self {
            state: Mutex::new(CallState::default()),
            transaction: Arc::clone(&mock_transaction) as Arc<dyn Transaction>,
            mock_transaction: Some(mock_transaction),
            command,
            provider: provider.into(),
        })
    }

    pub fn with_transaction(transaction: Arc<dyn Transaction>) -> Arc<Self> {
        Arc::new(Self {
            state: Mutex::new(CallState::default()),
            transaction,
            mock_transaction: None,
            command: Arc::new(MockCommand::new()),
            provider: "mock".into(),
        })
    }

    pub fn set_open_error(&self, message: impl Into<String>) {
        self.state.lock().expect("mock").open_error = Some(message.into());
    }

    pub fn set_close_error(&self, message: impl Into<String>) {
        self.state.lock().expect("mock").close_error = Some(message.into());
    }

    pub fn set_start_error(&self, message: impl Into<String>) {
        self.state.lock().expect("mock").start_error = Some(message.into());
    }

    pub fn open_calls(&self) -> usize {
        self.state.lock().expect("mock").open_calls
    }

    pub fn start_calls(&self) -> usize {
        self.state.lock().expect("mock").start_calls
    }

    pub fn is_open(&self) -> bool {
        self.state.lock().expect("mock").opened
    }

    pub fn last_isolation(&self) -> IsolationLevel {
        self.state.lock().expect("mock").last_isolation
    }
}

#[async_trait]
impl Connection for MockConnection {
    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn provider(&self) -> &str {
        &self.provider
    }

    fn is_open(&self) -> bool {
        self.state.lock().expect("mock").opened
    }

    fn create_command(&self) -> Arc<dyn Command> {
        self.command.clone()
    }

    async fn open(&self) -> Result<()> {
        let mut state = self.state.lock().expect("mock");
        if state.opened {
            return Err(DatasourceError::message("Connection is already open"));
        }
        state.open_calls += 1;
        if let Some(error) = &state.open_error {
            return Err(DatasourceError::message(error.clone()));
        }
        state.opened = true;
        Ok(())
    }

    async fn close(&self) -> Result<()> {
        let mut state = self.state.lock().expect("mock");
        state.close_calls += 1;
        if let Some(error) = &state.close_error {
            return Err(DatasourceError::message(error.clone()));
        }
        state.opened = false;
        Ok(())
    }

    async fn start_transaction(
        &self,
        isolation: Option<IsolationLevel>,
    ) -> Result<Arc<dyn Transaction>> {
        let mut state = self.state.lock().expect("mock");
        state.start_calls += 1;
        if let Some(error) = &state.start_error {
            return Err(DatasourceError::message(error.clone()));
        }
        let isolation = isolation.unwrap_or(IsolationLevel::ReadCommitted);
        state.last_isolation = isolation;
        if let Some(transaction) = &self.mock_transaction {
            transaction.set_isolation(isolation);
        }
        Ok(self.transaction.clone())
    }
}