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}