testkit_core/
utils.rs

1use std::future::Future;
2use std::pin::Pin;
3
4/// Internal helper function for the boxed_async macro
5pub fn box_future<F, O>(future: F) -> Pin<Box<dyn Future<Output = O> + Send>>
6where
7    F: Future<Output = O> + Send + 'static,
8{
9    Box::pin(future)
10}
11
12/// Macro to automatically box an async block for use with the boxed database API
13///
14/// This macro makes the boxed database API more ergonomic by hiding the need to
15/// manually use Box::pin() around async blocks.
16#[macro_export]
17macro_rules! boxed_async {
18    (async $block:block) => {
19        Box::pin(async $block)
20    };
21    (async move $block:block) => {{
22        Box::pin(async move $block)
23    }};
24}
25
26/// Macro to implement the TransactionHandler trait for a type
27///
28/// This macro makes it easier to implement the TransactionHandler trait for types
29/// that work with databases. It automatically handles the Box::pin wrapping for async functions.
30#[macro_export]
31macro_rules! impl_transaction_handler {
32    ($type:ty, $db:ty, $item:ty, $error:ty) => {
33        #[async_trait::async_trait]
34        impl $crate::handlers::TransactionHandler<$db> for $type {
35            type Item = $item;
36            type Error = $error;
37
38            async fn execute(
39                self,
40                ctx: &mut $crate::TestContext<$db>,
41            ) -> Result<Self::Item, Self::Error> {
42                self.execute_impl(ctx).await
43            }
44        }
45    };
46}
47
48/// Boxes an async closure to handle lifetime issues
49///
50/// This function takes a closure that returns a Future and wraps it in a
51/// Pin<Box<dyn Future>> to solve lifetime problems. This is particularly useful
52/// for async closures that capture variables from their environment.
53pub fn boxed_future<T, F, Fut, E>(
54    f: F,
55) -> impl FnOnce(T) -> Pin<Box<dyn Future<Output = Result<(), E>> + Send>>
56where
57    F: FnOnce(T) -> Fut + Send + 'static,
58    Fut: Future<Output = Result<(), E>> + Send + 'static,
59    T: Send + 'static,
60    E: Send + 'static,
61{
62    move |t| {
63        let future = f(t);
64        Box::pin(future) as Pin<Box<dyn Future<Output = Result<(), E>> + Send>>
65    }
66}
67
68/// A more ergonomic macro for database operations that hides all the boxing complexity
69///
70/// This macro provides a clean syntax for database operations without requiring
71/// the user to manually use Box::new(), Box::pin(), or boxed_async!
72///
73/// # Example:
74///
75/// ```rust,no_run,ignore
76/// use testkit_core::db_test;
77///
78/// #[tokio::test]
79/// async fn test_database() {
80///     let backend = MockBackend::new();
81///     
82///     // Using the macro for a clean API
83///     let ctx = db_test!(backend)
84///         .setup_async(|conn| async {
85///             // Setup operations...
86///             Ok(())
87///         })
88///         .transaction(|conn| async {
89///             // Transaction operations...
90///             Ok(())
91///         })
92///         .run()
93///         .await
94///         .expect("Test failed");
95/// }
96/// ```
97#[macro_export]
98macro_rules! db_test {
99    ($backend:expr) => {
100        $crate::with_boxed_database($backend)
101    };
102    ($backend:expr, $config:expr) => {
103        $crate::with_boxed_database_config($backend, $config)
104    };
105}
106
107/// A macro to simplify setting up a database
108///
109/// This macro provides a cleaner API for the setup phase without requiring
110/// manual boxing.
111#[macro_export]
112#[rustfmt::skip]
113macro_rules! setup {
114    ($backend:expr, |$conn:ident| $body:expr) => {
115        $crate::with_boxed_database($backend)
116            .setup(|$conn| $crate::boxed_async!($body))
117    };
118}
119
120/// A macro to simplify running a transaction
121///
122/// This macro provides a cleaner API for the transaction phase without requiring
123/// manual boxing.
124#[macro_export]
125#[rustfmt::skip]
126macro_rules! transaction {
127    ($backend:expr, |$conn:ident| $body:expr) => {
128        $crate::with_boxed_database($backend)
129            .with_transaction(|$conn| $crate::boxed_async!($body))
130    };
131}
132
133/// A macro to simplify both setup and transaction phases
134///
135/// This macro provides a cleaner API for both setup and transaction phases without
136/// requiring manual boxing.
137#[macro_export]
138#[rustfmt::skip]
139macro_rules! setup_and_transaction {
140    ($backend:expr, 
141     setup: |$setup_conn:ident| $setup_body:expr,
142     transaction: |$tx_conn:ident| $tx_body:expr) => {
143        $crate::with_boxed_database($backend)
144            .setup(|$setup_conn| $crate::boxed_async!($setup_body))
145            .with_transaction(|$tx_conn| $crate::boxed_async!($tx_body))
146    };
147}