testkit_core/handlers/
mod.rs

1use async_trait::async_trait;
2use std::fmt::Debug;
3use std::marker::PhantomData;
4
5use crate::testdb::{DatabaseBackend, DatabaseConfig};
6
7pub mod boxed;
8mod setup;
9mod with_database;
10mod with_transaction;
11
12#[cfg(test)]
13mod tests;
14
15// Re-export core components (simplified version)
16pub use boxed::{BoxedDatabaseEntryPoint, with_boxed_database, with_boxed_database_config};
17pub use setup::{SetupHandler, setup};
18pub use with_database::DatabaseHandler;
19pub use with_transaction::{
20    DatabaseTransactionHandler, TransactionFnHandler, with_db_transaction, with_transaction,
21};
22
23// Add minimal implementation of AndThenHandler to fix linter errors
24/// Handler that executes two handlers in sequence
25pub struct AndThenHandler<DB, A, B, F>
26where
27    DB: DatabaseBackend + Send + Sync + Debug + 'static,
28    A: TransactionHandler<DB> + Send + Sync,
29    B: TransactionHandler<DB, Error = A::Error> + Send + Sync,
30    F: FnOnce(A::Item) -> B + Send + Sync + 'static,
31{
32    first: A,
33    next_fn: F,
34    _phantom: PhantomData<(DB, B)>,
35}
36
37#[async_trait]
38impl<DB, A, B, F> TransactionHandler<DB> for AndThenHandler<DB, A, B, F>
39where
40    DB: DatabaseBackend + Send + Sync + Debug + 'static,
41    A: TransactionHandler<DB> + Send + Sync,
42    B: TransactionHandler<DB, Error = A::Error> + Send + Sync,
43    F: FnOnce(A::Item) -> B + Send + Sync + 'static,
44{
45    type Item = B::Item;
46    type Error = A::Error;
47
48    async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
49        let result = self.first.execute(ctx).await?;
50        let next = (self.next_fn)(result);
51        next.execute(ctx).await
52    }
53}
54
55/// Database handlers for the testkit crate
56///
57/// This module provides handlers for database operations with the boxed API,
58/// which automatically handles boxing of closures to solve lifetime issues when
59/// capturing variables from the environment.
60///
61/// # Example:
62/// ```rust,no_run,ignore
63/// use testkit_core::*;
64///
65/// async fn test() -> Result<(), Box<dyn std::error::Error>> {
66///     let backend = testkit_core::testdb::tests::MockBackend::new();
67///     
68///     // This local variable would cause lifetime issues with a non-boxed API
69///     let table_name = "users".to_string();
70///     
71///     let ctx = with_database(backend)
72///        .setup(|conn| boxed_async!(async move {
73///            // Can capture local variables without lifetime issues
74///            let query = format!("CREATE TABLE {}", table_name);
75///            // setup code
76///            Ok(())
77///        }))
78///        .with_transaction(|tx| boxed_async!(async move {
79///            // Transaction code with local variables
80///            Ok(())
81///        }))
82///        .execute()
83///        .await?;
84///     Ok(())
85/// }
86/// ```
87// Re-export boxed API as the primary API
88pub use boxed::{
89    with_boxed_database as with_database, with_boxed_database_config as with_database_config,
90};
91
92/// A trait for handlers that can work with transactions
93#[async_trait]
94pub trait TransactionHandler<DB>: Send + Sync
95where
96    DB: DatabaseBackend + Send + Sync + Debug + 'static,
97{
98    /// The result type of this handler
99    type Item;
100    /// The error type
101    type Error: From<DB::Error> + Send + Sync;
102
103    /// Execute the handler with the given context
104    async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error>;
105
106    /// Execute this handler with a new context
107    ///
108    /// This is a convenience method that creates a new context using the provided database
109    /// backend and then executes the handler with it.
110    async fn execute_standalone(self, backend: DB) -> Result<Self::Item, Self::Error>
111    where
112        Self: Sized,
113    {
114        // Create a context with the provided database
115        let config = DatabaseConfig::default();
116        let db_instance = crate::testdb::TestDatabaseInstance::new(backend, config).await?;
117        let mut ctx = crate::TestContext::new(db_instance);
118
119        // Execute with the new context
120        self.execute(&mut ctx).await
121    }
122
123    /// Chain two handlers together, where the second handler may depend on the result of the first
124    fn and_then<F, B>(self, f: F) -> AndThenHandler<DB, Self, B, F>
125    where
126        Self: Sized,
127        B: TransactionHandler<DB, Error = Self::Error> + Send + Sync,
128        F: FnOnce(Self::Item) -> B + Send + Sync + 'static,
129    {
130        AndThenHandler {
131            first: self,
132            next_fn: f,
133            _phantom: PhantomData,
134        }
135    }
136
137    /// Add a setup operation to this handler
138    fn setup<S, Fut, E>(
139        self,
140        setup_fn: S,
141    ) -> impl TransactionHandler<DB, Item = (), Error = Self::Error>
142    where
143        Self: Sized,
144        E: From<DB::Error> + From<Self::Error> + Send + Sync,
145        Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
146        S: FnOnce(&mut <DB::Pool as crate::DatabasePool>::Connection) -> Fut
147            + Send
148            + Sync
149            + 'static,
150    {
151        self.and_then(move |_| {
152            let handler = setup(setup_fn);
153            SetupHandlerWrapper::<DB, S, Self::Error>::new(handler)
154        })
155    }
156
157    /// Add a transaction operation to this handler
158    fn with_transaction<F, Fut, E>(
159        self,
160        transaction_fn: F,
161    ) -> impl TransactionHandler<DB, Item = (), Error = Self::Error>
162    where
163        Self: Sized,
164        E: From<DB::Error> + From<Self::Error> + Send + Sync,
165        Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
166        F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
167    {
168        self.and_then(move |_| {
169            let handler = with_transaction(transaction_fn);
170            TransactionFnHandlerWrapper::<DB, F, Self::Error>::new(handler)
171        })
172    }
173
174    /// Create a database transaction handler from this handler
175    fn with_db_transaction<F, Fut, E>(
176        self,
177        db: crate::TestDatabaseInstance<DB>,
178        transaction_fn: F,
179    ) -> impl TransactionHandler<DB, Item = crate::TestContext<DB>, Error = Self::Error>
180    where
181        Self: Sized,
182        E: From<DB::Error> + From<Self::Error> + Send + Sync,
183        Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
184        F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
185    {
186        self.and_then(move |_| {
187            let handler = with_db_transaction(db, transaction_fn);
188            DbTransactionHandlerWrapper::<DB, F, Self::Error>::new(handler)
189        })
190    }
191
192    /// Run this handler with a new database instance
193    async fn run_with_database(self, backend: DB) -> Result<crate::TestContext<DB>, Self::Error>
194    where
195        Self: Sized,
196    {
197        let config = DatabaseConfig::default();
198        let db_instance = crate::testdb::TestDatabaseInstance::new(backend, config).await?;
199        let mut ctx = crate::TestContext::new(db_instance);
200
201        self.execute(&mut ctx).await?;
202
203        Ok(ctx)
204    }
205}
206
207/// A trait for converting types into transaction handlers
208pub trait IntoTransactionHandler<DB>
209where
210    DB: DatabaseBackend + Send + Sync + Debug + 'static,
211{
212    /// The handler type
213    type Handler: TransactionHandler<DB, Item = Self::Item, Error = Self::Error>;
214    /// The result type
215    type Item;
216    /// The error type
217    type Error: From<DB::Error> + Send + Sync;
218
219    /// Convert this type into a transaction handler
220    fn into_transaction_handler(self) -> Self::Handler;
221}
222
223/// Helper function to run a handler with a new database
224pub async fn run_with_database<DB, H>(
225    backend: DB,
226    handler: H,
227) -> Result<crate::TestContext<DB>, H::Error>
228where
229    DB: DatabaseBackend + Send + Sync + Debug + 'static,
230    H: TransactionHandler<DB>,
231{
232    handler.run_with_database(backend).await
233}
234
235/// Helper wrapper for SetupHandler
236pub struct SetupHandlerWrapper<DB, S, E>
237where
238    DB: DatabaseBackend + Send + Sync + Debug + 'static,
239    S: Send + Sync + 'static,
240    E: From<DB::Error> + Send + Sync,
241{
242    inner: SetupHandler<DB, S>,
243    _error: PhantomData<E>,
244}
245
246impl<DB, S, E> SetupHandlerWrapper<DB, S, E>
247where
248    DB: DatabaseBackend + Send + Sync + Debug + 'static,
249    S: Send + Sync + 'static,
250    E: From<DB::Error> + Send + Sync,
251{
252    pub fn new(inner: SetupHandler<DB, S>) -> Self {
253        Self {
254            inner,
255            _error: PhantomData,
256        }
257    }
258}
259
260#[async_trait]
261impl<DB, S, Fut, E> TransactionHandler<DB> for SetupHandlerWrapper<DB, S, E>
262where
263    DB: DatabaseBackend + Send + Sync + Debug + 'static,
264    Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
265    S: FnOnce(&mut <DB::Pool as crate::DatabasePool>::Connection) -> Fut + Send + Sync + 'static,
266    E: From<DB::Error> + Send + Sync,
267{
268    type Item = ();
269    type Error = E;
270
271    async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
272        Ok(self.inner.execute(ctx).await?)
273    }
274}
275
276/// Helper wrapper for TransactionFnHandler
277pub struct TransactionFnHandlerWrapper<DB, F, E>
278where
279    DB: DatabaseBackend + Send + Sync + Debug + 'static,
280    F: Send + Sync + 'static,
281    E: From<DB::Error> + Send + Sync,
282{
283    inner: TransactionFnHandler<DB, F>,
284    _error: PhantomData<E>,
285}
286
287impl<DB, F, E> TransactionFnHandlerWrapper<DB, F, E>
288where
289    DB: DatabaseBackend + Send + Sync + Debug + 'static,
290    F: Send + Sync + 'static,
291    E: From<DB::Error> + Send + Sync,
292{
293    pub fn new(inner: TransactionFnHandler<DB, F>) -> Self {
294        Self {
295            inner,
296            _error: PhantomData,
297        }
298    }
299}
300
301#[async_trait]
302impl<DB, F, Fut, E> TransactionHandler<DB> for TransactionFnHandlerWrapper<DB, F, E>
303where
304    DB: DatabaseBackend + Send + Sync + Debug + 'static,
305    Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
306    F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
307    E: From<DB::Error> + Send + Sync,
308{
309    type Item = ();
310    type Error = E;
311
312    async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
313        Ok(self.inner.execute(ctx).await?)
314    }
315}
316
317/// Helper wrapper for DatabaseTransactionHandler
318pub struct DbTransactionHandlerWrapper<DB, F, E>
319where
320    DB: DatabaseBackend + Send + Sync + Debug + 'static,
321    F: Send + Sync + 'static,
322    E: From<DB::Error> + Send + Sync,
323{
324    inner: DatabaseTransactionHandler<DB, F>,
325    _error: PhantomData<E>,
326}
327
328impl<DB, F, E> DbTransactionHandlerWrapper<DB, F, E>
329where
330    DB: DatabaseBackend + Send + Sync + Debug + 'static,
331    F: Send + Sync + 'static,
332    E: From<DB::Error> + Send + Sync,
333{
334    pub fn new(inner: DatabaseTransactionHandler<DB, F>) -> Self {
335        Self {
336            inner,
337            _error: PhantomData,
338        }
339    }
340}
341
342#[async_trait]
343impl<DB, F, Fut, E> TransactionHandler<DB> for DbTransactionHandlerWrapper<DB, F, E>
344where
345    DB: DatabaseBackend + Send + Sync + Debug + 'static,
346    Fut: std::future::Future<Output = Result<(), DB::Error>> + Send + 'static,
347    F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
348    E: From<DB::Error> + Send + Sync,
349{
350    type Item = crate::TestContext<DB>;
351    type Error = E;
352
353    async fn execute(self, ctx: &mut crate::TestContext<DB>) -> Result<Self::Item, Self::Error> {
354        Ok(self.inner.execute(ctx).await?)
355    }
356}