use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
use crate::DatabasePool;
use crate::TestContext;
use crate::handlers::TransactionHandler;
use crate::testdb::DatabaseBackend;
use crate::testdb::DatabaseConfig;
use async_trait::async_trait;
pub type BoxedSetupFn<DB> = Box<
dyn for<'a> FnOnce(
&'a mut <<DB as DatabaseBackend>::Pool as crate::DatabasePool>::Connection,
) -> Pin<
Box<dyn Future<Output = Result<(), <DB as DatabaseBackend>::Error>> + Send + 'a>,
> + Send
+ Sync,
>;
pub type BoxedTransactionFn<DB> = Box<
dyn for<'a> FnOnce(
&'a mut <DB as DatabaseBackend>::Connection,
) -> Pin<
Box<dyn Future<Output = Result<(), <DB as DatabaseBackend>::Error>> + Send + 'a>,
> + Send
+ Sync,
>;
pub struct BoxedDatabaseEntryPoint<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
backend: DB,
}
pub struct BoxedSetupHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
backend: DB,
setup_fn: BoxedSetupFn<DB>,
}
pub struct BoxedTransactionHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
backend: DB,
setup_fn: BoxedSetupFn<DB>,
transaction_fn: BoxedTransactionFn<DB>,
}
pub struct BoxedTransactionOnlyHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
backend: DB,
transaction_fn: BoxedTransactionFn<DB>,
}
impl<DB> BoxedDatabaseEntryPoint<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
pub fn new(backend: DB) -> Self {
Self { backend }
}
pub fn setup<F>(self, setup_fn: F) -> BoxedSetupHandler<DB>
where
F: for<'a> FnOnce(
&'a mut <DB::Pool as crate::DatabasePool>::Connection,
)
-> Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + 'a>>
+ Send
+ Sync
+ 'static,
{
BoxedSetupHandler {
backend: self.backend,
setup_fn: Box::new(setup_fn),
}
}
pub fn with_transaction<F>(self, transaction_fn: F) -> BoxedTransactionOnlyHandler<DB>
where
F: for<'a> FnOnce(
&'a mut <DB as DatabaseBackend>::Connection,
)
-> Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + 'a>>
+ Send
+ Sync
+ 'static,
{
BoxedTransactionOnlyHandler {
backend: self.backend,
transaction_fn: Box::new(transaction_fn),
}
}
pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
let db_instance =
crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
.await?;
Ok(crate::TestContext::new(db_instance))
}
pub fn setup_async<F, Fut>(self, setup_fn: F) -> BoxedSetupHandler<DB>
where
F: FnOnce(&mut <DB::Pool as DatabasePool>::Connection) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), DB::Error>> + Send + 'static,
{
self.setup(move |conn| {
Box::pin(setup_fn(conn))
as Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + '_>>
})
}
pub fn transaction<F, Fut>(self, transaction_fn: F) -> BoxedTransactionOnlyHandler<DB>
where
F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), DB::Error>> + Send + 'static,
{
self.with_transaction(move |conn| {
Box::pin(transaction_fn(conn))
as Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + '_>>
})
}
pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
self.execute().await
}
}
#[async_trait]
impl<DB> TransactionHandler<DB> for BoxedDatabaseEntryPoint<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
type Item = TestContext<DB>;
type Error = DB::Error;
async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
let db_instance =
crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
.await?;
Ok(crate::TestContext::new(db_instance))
}
}
impl<DB> BoxedTransactionOnlyHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
let db_instance =
crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
.await?;
let ctx = crate::TestContext::new(db_instance.clone());
let mut conn = ctx.db.pool.acquire().await?;
(self.transaction_fn)(&mut conn).await?;
ctx.db.pool.release(conn).await?;
Ok(ctx)
}
pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
self.execute().await
}
}
#[async_trait]
impl<DB> TransactionHandler<DB> for BoxedTransactionOnlyHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
type Item = TestContext<DB>;
type Error = DB::Error;
async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
self.execute().await
}
}
impl<DB> BoxedSetupHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
pub fn with_transaction<F>(self, transaction_fn: F) -> BoxedTransactionHandler<DB>
where
F: for<'a> FnOnce(
&'a mut <DB as DatabaseBackend>::Connection,
)
-> Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + 'a>>
+ Send
+ Sync
+ 'static,
{
BoxedTransactionHandler {
backend: self.backend,
setup_fn: self.setup_fn,
transaction_fn: Box::new(transaction_fn),
}
}
pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
let db_instance =
crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
.await?;
let ctx = crate::TestContext::new(db_instance);
let mut conn = ctx.db.pool.acquire().await?;
(self.setup_fn)(&mut conn).await?;
ctx.db.pool.release(conn).await?;
Ok(ctx)
}
pub fn transaction<F, Fut>(self, transaction_fn: F) -> BoxedTransactionHandler<DB>
where
F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), DB::Error>> + Send + 'static,
{
self.with_transaction(move |conn| {
Box::pin(transaction_fn(conn))
as Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + '_>>
})
}
pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
self.execute().await
}
}
#[async_trait]
impl<DB> TransactionHandler<DB> for BoxedSetupHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
type Item = TestContext<DB>;
type Error = DB::Error;
async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
self.execute().await
}
}
impl<DB> BoxedTransactionHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
let db_instance =
crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
.await?;
let ctx = crate::TestContext::new(db_instance);
let mut conn = ctx.db.pool.acquire().await?;
(self.setup_fn)(&mut conn).await?;
ctx.db.pool.release(conn).await?;
let mut conn = ctx.db.pool.acquire().await?;
(self.transaction_fn)(&mut conn).await?;
ctx.db.pool.release(conn).await?;
Ok(ctx)
}
pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
self.execute().await
}
}
#[async_trait]
impl<DB> TransactionHandler<DB> for BoxedTransactionHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
type Item = TestContext<DB>;
type Error = DB::Error;
async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
self.execute().await
}
}
pub fn with_boxed_database<DB>(backend: DB) -> BoxedDatabaseEntryPoint<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
BoxedDatabaseEntryPoint::new(backend)
}
pub fn with_boxed_database_config<DB>(
backend: DB,
_config: DatabaseConfig,
) -> BoxedDatabaseEntryPoint<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
BoxedDatabaseEntryPoint::new(backend)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testdb::TestDatabaseConnection;
#[derive(Debug, Clone)]
struct MockError(String);
impl std::fmt::Display for MockError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Mock error: {}", self.0)
}
}
impl std::error::Error for MockError {}
impl From<String> for MockError {
fn from(s: String) -> Self {
MockError(s)
}
}
#[derive(Debug, Clone)]
struct MockConnection;
impl TestDatabaseConnection for MockConnection {
fn connection_string(&self) -> String {
"mock://test".to_string()
}
}
#[derive(Debug, Clone)]
struct MockPool;
#[async_trait]
impl crate::DatabasePool for MockPool {
type Connection = MockConnection;
type Error = MockError;
async fn acquire(&self) -> Result<Self::Connection, Self::Error> {
Ok(MockConnection)
}
async fn release(&self, _conn: Self::Connection) -> Result<(), Self::Error> {
Ok(())
}
fn connection_string(&self) -> String {
"mock://test".to_string()
}
}
#[derive(Debug, Clone)]
struct MockBackend;
impl MockBackend {
fn new() -> Self {
MockBackend
}
}
#[async_trait]
impl crate::DatabaseBackend for MockBackend {
type Connection = MockConnection;
type Pool = MockPool;
type Error = MockError;
async fn new(_config: crate::DatabaseConfig) -> Result<Self, Self::Error> {
Ok(Self)
}
async fn connect(
&self,
_name: &crate::DatabaseName,
) -> Result<Self::Connection, Self::Error> {
Ok(MockConnection)
}
async fn connect_with_string(
&self,
_connection_string: &str,
) -> Result<Self::Connection, Self::Error> {
Ok(MockConnection)
}
async fn create_pool(
&self,
_name: &crate::DatabaseName,
_config: &crate::DatabaseConfig,
) -> Result<Self::Pool, Self::Error> {
Ok(MockPool)
}
async fn create_database(
&self,
_pool: &Self::Pool,
_name: &crate::DatabaseName,
) -> Result<(), Self::Error> {
Ok(())
}
fn drop_database(&self, _name: &crate::DatabaseName) -> Result<(), Self::Error> {
Ok(())
}
fn connection_string(&self, _name: &crate::DatabaseName) -> String {
"mock://test".to_string()
}
}
#[tokio::test]
async fn test_boxed_database() {
let backend = MockBackend::new();
let ctx = with_boxed_database(backend)
.setup(|_conn| {
crate::boxed_async!(async move {
Ok(())
})
})
.with_transaction(|_conn| {
crate::boxed_async!(async move {
Ok(())
})
})
.execute()
.await;
assert!(ctx.is_ok());
}
}