use async_trait::async_trait;
use std::fmt::Debug;
use std::marker::PhantomData;
use crate::testdb::{DatabaseBackend, DatabaseConfig};
pub mod boxed;
mod setup;
mod with_database;
mod with_transaction;
#[cfg(test)]
mod tests;
pub use boxed::{BoxedDatabaseEntryPoint, with_boxed_database, with_boxed_database_config};
pub use setup::{SetupHandler, setup};
pub use with_database::DatabaseHandler;
pub use with_transaction::{
DatabaseTransactionHandler, TransactionFnHandler, with_db_transaction, with_transaction,
};
pub struct AndThenHandler<DB, A, B, F>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
A: TransactionHandler<DB> + Send + Sync,
B: TransactionHandler<DB, Error = A::Error> + Send + Sync,
F: FnOnce(A::Item) -> B + Send + Sync + 'static,
{
first: A,
next_fn: F,
_phantom: PhantomData<(DB, B)>,
}
#[async_trait]
impl<DB, A, B, F> TransactionHandler<DB> for AndThenHandler<DB, A, B, F>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
A: TransactionHandler<DB> + Send + Sync,
B: TransactionHandler<DB, Error = A::Error> + Send + Sync,
F: FnOnce(A::Item) -> B + Send + Sync + 'static,
{
type Item = B::Item;
type Error = A::Error;
async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
let result = self.first.execute(ctx).await?;
let next = (self.next_fn)(result);
next.execute(ctx).await
}
}
pub use boxed::{
with_boxed_database as with_database, with_boxed_database_config as with_database_config,
};
#[async_trait]
pub trait TransactionHandler<DB>: Send + Sync
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
type Item;
type Error: From<DB::Error> + Send + Sync;
async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error>;
async fn execute_standalone(self, backend: DB) -> Result<Self::Item, Self::Error>
where
Self: Sized,
{
let config = DatabaseConfig::default();
let db_instance = crate::testdb::TestDatabaseInstance::new(backend, config).await?;
let mut ctx = crate::TestContext::new(db_instance);
self.execute(&mut ctx).await
}
fn and_then<F, B>(self, f: F) -> AndThenHandler<DB, Self, B, F>
where
Self: Sized,
B: TransactionHandler<DB, Error = Self::Error> + Send + Sync,
F: FnOnce(Self::Item) -> B + Send + Sync + 'static,
{
AndThenHandler {
first: self,
next_fn: f,
_phantom: PhantomData,
}
}
fn setup<S, Fut, E>(
self,
setup_fn: S,
) -> impl TransactionHandler<DB, Item = (), Error = Self::Error>
where
Self: Sized,
E: From<DB::Error> + From<Self::Error> + Send + Sync,
Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
S: FnOnce(&mut <DB::Pool as crate::DatabasePool>::Connection) -> Fut
+ Send
+ Sync
+ 'static,
{
self.and_then(move |_| {
let handler = setup(setup_fn);
SetupHandlerWrapper::<DB, S, Self::Error>::new(handler)
})
}
fn with_transaction<F, Fut, E>(
self,
transaction_fn: F,
) -> impl TransactionHandler<DB, Item = (), Error = Self::Error>
where
Self: Sized,
E: From<DB::Error> + From<Self::Error> + Send + Sync,
Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
{
self.and_then(move |_| {
let handler = with_transaction(transaction_fn);
TransactionFnHandlerWrapper::<DB, F, Self::Error>::new(handler)
})
}
fn with_db_transaction<F, Fut, E>(
self,
db: crate::TestDatabaseInstance<DB>,
transaction_fn: F,
) -> impl TransactionHandler<DB, Item = crate::TestContext<DB>, Error = Self::Error>
where
Self: Sized,
E: From<DB::Error> + From<Self::Error> + Send + Sync,
Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
{
self.and_then(move |_| {
let handler = with_db_transaction(db, transaction_fn);
DbTransactionHandlerWrapper::<DB, F, Self::Error>::new(handler)
})
}
async fn run_with_database(self, backend: DB) -> Result<crate::TestContext<DB>, Self::Error>
where
Self: Sized,
{
let config = DatabaseConfig::default();
let db_instance = crate::testdb::TestDatabaseInstance::new(backend, config).await?;
let mut ctx = crate::TestContext::new(db_instance);
self.execute(&mut ctx).await?;
Ok(ctx)
}
}
pub trait IntoTransactionHandler<DB>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
{
type Handler: TransactionHandler<DB, Item = Self::Item, Error = Self::Error>;
type Item;
type Error: From<DB::Error> + Send + Sync;
fn into_transaction_handler(self) -> Self::Handler;
}
pub async fn run_with_database<DB, H>(
backend: DB,
handler: H,
) -> Result<crate::TestContext<DB>, H::Error>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
H: TransactionHandler<DB>,
{
handler.run_with_database(backend).await
}
pub struct SetupHandlerWrapper<DB, S, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
S: Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
inner: SetupHandler<DB, S>,
_error: PhantomData<E>,
}
impl<DB, S, E> SetupHandlerWrapper<DB, S, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
S: Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
pub fn new(inner: SetupHandler<DB, S>) -> Self {
Self {
inner,
_error: PhantomData,
}
}
}
#[async_trait]
impl<DB, S, Fut, E> TransactionHandler<DB> for SetupHandlerWrapper<DB, S, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
S: FnOnce(&mut <DB::Pool as crate::DatabasePool>::Connection) -> Fut + Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
type Item = ();
type Error = E;
async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
Ok(self.inner.execute(ctx).await?)
}
}
pub struct TransactionFnHandlerWrapper<DB, F, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
F: Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
inner: TransactionFnHandler<DB, F>,
_error: PhantomData<E>,
}
impl<DB, F, E> TransactionFnHandlerWrapper<DB, F, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
F: Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
pub fn new(inner: TransactionFnHandler<DB, F>) -> Self {
Self {
inner,
_error: PhantomData,
}
}
}
#[async_trait]
impl<DB, F, Fut, E> TransactionHandler<DB> for TransactionFnHandlerWrapper<DB, F, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
type Item = ();
type Error = E;
async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
Ok(self.inner.execute(ctx).await?)
}
}
pub struct DbTransactionHandlerWrapper<DB, F, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
F: Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
inner: DatabaseTransactionHandler<DB, F>,
_error: PhantomData<E>,
}
impl<DB, F, E> DbTransactionHandlerWrapper<DB, F, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
F: Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
pub fn new(inner: DatabaseTransactionHandler<DB, F>) -> Self {
Self {
inner,
_error: PhantomData,
}
}
}
#[async_trait]
impl<DB, F, Fut, E> TransactionHandler<DB> for DbTransactionHandlerWrapper<DB, F, E>
where
DB: DatabaseBackend + Send + Sync + Debug + 'static,
Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
E: From<DB::Error> + Send + Sync,
{
type Item = crate::TestContext<DB>;
type Error = E;
async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
Ok(self.inner.execute(ctx).await?)
}
}