1use std::fmt::Debug;
2use std::future::Future;
3use std::pin::Pin;
4
5use crate::DatabasePool;
6use crate::TestContext;
7use crate::handlers::TransactionHandler;
8use crate::testdb::DatabaseBackend;
9use crate::testdb::DatabaseConfig;
10use async_trait::async_trait;
11
12pub type BoxedSetupFn<DB> = Box<
15 dyn for<'a> FnOnce(
16 &'a mut <<DB as DatabaseBackend>::Pool as crate::DatabasePool>::Connection,
17 ) -> Pin<
18 Box<dyn Future<Output = Result<(), <DB as DatabaseBackend>::Error>> + Send + 'a>,
19 > + Send
20 + Sync,
21>;
22
23pub type BoxedTransactionFn<DB> = Box<
25 dyn for<'a> FnOnce(
26 &'a mut <DB as DatabaseBackend>::Connection,
27 ) -> Pin<
28 Box<dyn Future<Output = Result<(), <DB as DatabaseBackend>::Error>> + Send + 'a>,
29 > + Send
30 + Sync,
31>;
32
33pub struct BoxedDatabaseEntryPoint<DB>
39where
40 DB: DatabaseBackend + Send + Sync + Debug + 'static,
41{
42 backend: DB,
43}
44
45pub struct BoxedSetupHandler<DB>
47where
48 DB: DatabaseBackend + Send + Sync + Debug + 'static,
49{
50 backend: DB,
51 setup_fn: BoxedSetupFn<DB>,
52}
53
54pub struct BoxedTransactionHandler<DB>
56where
57 DB: DatabaseBackend + Send + Sync + Debug + 'static,
58{
59 backend: DB,
60 setup_fn: BoxedSetupFn<DB>,
61 transaction_fn: BoxedTransactionFn<DB>,
62}
63
64pub struct BoxedTransactionOnlyHandler<DB>
66where
67 DB: DatabaseBackend + Send + Sync + Debug + 'static,
68{
69 backend: DB,
70 transaction_fn: BoxedTransactionFn<DB>,
71}
72
73impl<DB> BoxedDatabaseEntryPoint<DB>
74where
75 DB: DatabaseBackend + Send + Sync + Debug + 'static,
76{
77 pub fn new(backend: DB) -> Self {
79 Self { backend }
80 }
81
82 pub fn setup<F>(self, setup_fn: F) -> BoxedSetupHandler<DB>
88 where
89 F: for<'a> FnOnce(
90 &'a mut <DB::Pool as crate::DatabasePool>::Connection,
91 )
92 -> Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + 'a>>
93 + Send
94 + Sync
95 + 'static,
96 {
97 BoxedSetupHandler {
98 backend: self.backend,
99 setup_fn: Box::new(setup_fn),
100 }
101 }
102
103 pub fn with_transaction<F>(self, transaction_fn: F) -> BoxedTransactionOnlyHandler<DB>
105 where
106 F: for<'a> FnOnce(
107 &'a mut <DB as DatabaseBackend>::Connection,
108 )
109 -> Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + 'a>>
110 + Send
111 + Sync
112 + 'static,
113 {
114 BoxedTransactionOnlyHandler {
115 backend: self.backend,
116 transaction_fn: Box::new(transaction_fn),
117 }
118 }
119
120 pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
122 let db_instance =
124 crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
125 .await?;
126
127 Ok(crate::TestContext::new(db_instance))
129 }
130
131 pub fn setup_async<F, Fut>(self, setup_fn: F) -> BoxedSetupHandler<DB>
135 where
136 F: FnOnce(&mut <DB::Pool as DatabasePool>::Connection) -> Fut + Send + Sync + 'static,
137 Fut: Future<Output = Result<(), DB::Error>> + Send + 'static,
138 {
139 self.setup(move |conn| {
140 Box::pin(setup_fn(conn))
141 as Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + '_>>
142 })
143 }
144
145 pub fn transaction<F, Fut>(self, transaction_fn: F) -> BoxedTransactionOnlyHandler<DB>
149 where
150 F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
151 Fut: Future<Output = Result<(), DB::Error>> + Send + 'static,
152 {
153 self.with_transaction(move |conn| {
154 Box::pin(transaction_fn(conn))
155 as Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + '_>>
156 })
157 }
158
159 pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
163 self.execute().await
164 }
165}
166
167#[async_trait]
168impl<DB> TransactionHandler<DB> for BoxedDatabaseEntryPoint<DB>
169where
170 DB: DatabaseBackend + Send + Sync + Debug + 'static,
171{
172 type Item = TestContext<DB>;
173 type Error = DB::Error;
174
175 async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
176 let db_instance =
178 crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
179 .await?;
180
181 Ok(crate::TestContext::new(db_instance))
183 }
184}
185
186impl<DB> BoxedTransactionOnlyHandler<DB>
187where
188 DB: DatabaseBackend + Send + Sync + Debug + 'static,
189{
190 pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
192 let db_instance =
194 crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
195 .await?;
196
197 let ctx = crate::TestContext::new(db_instance.clone());
199
200 let mut conn = ctx.db.pool.acquire().await?;
202
203 (self.transaction_fn)(&mut conn).await?;
205
206 ctx.db.pool.release(conn).await?;
208
209 Ok(ctx)
211 }
212
213 pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
217 self.execute().await
218 }
219}
220
221#[async_trait]
222impl<DB> TransactionHandler<DB> for BoxedTransactionOnlyHandler<DB>
223where
224 DB: DatabaseBackend + Send + Sync + Debug + 'static,
225{
226 type Item = TestContext<DB>;
227 type Error = DB::Error;
228
229 async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
230 self.execute().await
231 }
232}
233
234impl<DB> BoxedSetupHandler<DB>
235where
236 DB: DatabaseBackend + Send + Sync + Debug + 'static,
237{
238 pub fn with_transaction<F>(self, transaction_fn: F) -> BoxedTransactionHandler<DB>
244 where
245 F: for<'a> FnOnce(
246 &'a mut <DB as DatabaseBackend>::Connection,
247 )
248 -> Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + 'a>>
249 + Send
250 + Sync
251 + 'static,
252 {
253 BoxedTransactionHandler {
254 backend: self.backend,
255 setup_fn: self.setup_fn,
256 transaction_fn: Box::new(transaction_fn),
257 }
258 }
259
260 pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
262 let db_instance =
264 crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
265 .await?;
266
267 let ctx = crate::TestContext::new(db_instance);
269
270 let mut conn = ctx.db.pool.acquire().await?;
272
273 (self.setup_fn)(&mut conn).await?;
275
276 ctx.db.pool.release(conn).await?;
278
279 Ok(ctx)
281 }
282
283 pub fn transaction<F, Fut>(self, transaction_fn: F) -> BoxedTransactionHandler<DB>
287 where
288 F: FnOnce(&mut <DB as DatabaseBackend>::Connection) -> Fut + Send + Sync + 'static,
289 Fut: Future<Output = Result<(), DB::Error>> + Send + 'static,
290 {
291 self.with_transaction(move |conn| {
292 Box::pin(transaction_fn(conn))
293 as Pin<Box<dyn Future<Output = Result<(), DB::Error>> + Send + '_>>
294 })
295 }
296
297 pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
301 self.execute().await
302 }
303}
304
305#[async_trait]
306impl<DB> TransactionHandler<DB> for BoxedSetupHandler<DB>
307where
308 DB: DatabaseBackend + Send + Sync + Debug + 'static,
309{
310 type Item = TestContext<DB>;
311 type Error = DB::Error;
312
313 async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
314 self.execute().await
315 }
316}
317
318impl<DB> BoxedTransactionHandler<DB>
319where
320 DB: DatabaseBackend + Send + Sync + Debug + 'static,
321{
322 pub async fn execute(self) -> Result<crate::TestContext<DB>, DB::Error> {
324 let db_instance =
326 crate::testdb::TestDatabaseInstance::new(self.backend, DatabaseConfig::default())
327 .await?;
328
329 let ctx = crate::TestContext::new(db_instance);
331
332 let mut conn = ctx.db.pool.acquire().await?;
334
335 (self.setup_fn)(&mut conn).await?;
337
338 ctx.db.pool.release(conn).await?;
340
341 let mut conn = ctx.db.pool.acquire().await?;
343
344 (self.transaction_fn)(&mut conn).await?;
346
347 ctx.db.pool.release(conn).await?;
349
350 Ok(ctx)
352 }
353
354 pub async fn run(self) -> Result<crate::TestContext<DB>, DB::Error> {
358 self.execute().await
359 }
360}
361
362#[async_trait]
363impl<DB> TransactionHandler<DB> for BoxedTransactionHandler<DB>
364where
365 DB: DatabaseBackend + Send + Sync + Debug + 'static,
366{
367 type Item = TestContext<DB>;
368 type Error = DB::Error;
369
370 async fn execute(self, _ctx: &mut TestContext<DB>) -> Result<Self::Item, Self::Error> {
371 self.execute().await
372 }
373}
374
375pub fn with_boxed_database<DB>(backend: DB) -> BoxedDatabaseEntryPoint<DB>
380where
381 DB: DatabaseBackend + Send + Sync + Debug + 'static,
382{
383 BoxedDatabaseEntryPoint::new(backend)
384}
385
386pub fn with_boxed_database_config<DB>(
391 backend: DB,
392 _config: DatabaseConfig,
393) -> BoxedDatabaseEntryPoint<DB>
394where
395 DB: DatabaseBackend + Send + Sync + Debug + 'static,
396{
397 BoxedDatabaseEntryPoint::new(backend)
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::testdb::TestDatabaseConnection;
404
405 #[derive(Debug, Clone)]
406 struct MockError(String);
407
408 impl std::fmt::Display for MockError {
409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410 write!(f, "Mock error: {}", self.0)
411 }
412 }
413
414 impl std::error::Error for MockError {}
415
416 impl From<String> for MockError {
417 fn from(s: String) -> Self {
418 MockError(s)
419 }
420 }
421
422 #[derive(Debug, Clone)]
423 struct MockConnection;
424
425 impl TestDatabaseConnection for MockConnection {
426 fn connection_string(&self) -> String {
427 "mock://test".to_string()
428 }
429 }
430
431 #[derive(Debug, Clone)]
432 struct MockPool;
433
434 #[async_trait]
435 impl crate::DatabasePool for MockPool {
436 type Connection = MockConnection;
437 type Error = MockError;
438
439 async fn acquire(&self) -> Result<Self::Connection, Self::Error> {
440 Ok(MockConnection)
441 }
442
443 async fn release(&self, _conn: Self::Connection) -> Result<(), Self::Error> {
444 Ok(())
445 }
446
447 fn connection_string(&self) -> String {
448 "mock://test".to_string()
449 }
450 }
451
452 #[derive(Debug, Clone)]
453 struct MockBackend;
454
455 impl MockBackend {
456 fn new() -> Self {
457 MockBackend
458 }
459 }
460
461 #[async_trait]
462 impl crate::DatabaseBackend for MockBackend {
463 type Connection = MockConnection;
464 type Pool = MockPool;
465 type Error = MockError;
466
467 async fn new(_config: crate::DatabaseConfig) -> Result<Self, Self::Error> {
468 Ok(Self)
469 }
470
471 async fn connect(
472 &self,
473 _name: &crate::DatabaseName,
474 ) -> Result<Self::Connection, Self::Error> {
475 Ok(MockConnection)
476 }
477
478 async fn connect_with_string(
479 &self,
480 _connection_string: &str,
481 ) -> Result<Self::Connection, Self::Error> {
482 Ok(MockConnection)
483 }
484
485 async fn create_pool(
486 &self,
487 _name: &crate::DatabaseName,
488 _config: &crate::DatabaseConfig,
489 ) -> Result<Self::Pool, Self::Error> {
490 Ok(MockPool)
491 }
492
493 async fn create_database(
494 &self,
495 _pool: &Self::Pool,
496 _name: &crate::DatabaseName,
497 ) -> Result<(), Self::Error> {
498 Ok(())
499 }
500
501 fn drop_database(&self, _name: &crate::DatabaseName) -> Result<(), Self::Error> {
502 Ok(())
503 }
504
505 fn connection_string(&self, _name: &crate::DatabaseName) -> String {
506 "mock://test".to_string()
507 }
508 }
509
510 #[tokio::test]
511 async fn test_boxed_database() {
512 let backend = MockBackend::new();
513
514 let ctx = with_boxed_database(backend)
516 .setup(|_conn| {
517 crate::boxed_async!(async move {
518 Ok(())
520 })
521 })
522 .with_transaction(|_conn| {
523 crate::boxed_async!(async move {
524 Ok(())
526 })
527 })
528 .execute()
529 .await;
530
531 assert!(ctx.is_ok());
532 }
533}