aro_fletch/
transaction.rs1use std::future::Future;
4use std::sync::Arc;
5
6use aro_core::error::RepoError;
7use aro_core::repository::{BoxFuture, TransactionManager};
8use aro_web::dep::Dep;
9
10use crate::error::from_fletch_err;
11use crate::repo::{FletchDatabase, FletchEntity, FletchTransactionalRepository};
12
13pub(crate) struct FletchTransactionState<DB: FletchDatabase> {
15 pub(crate) tx: tokio::sync::Mutex<Option<fletch_orm::Transaction<'static, DB>>>,
16}
17
18pub struct FletchTransactionContext<DB: FletchDatabase> {
28 state: Arc<FletchTransactionState<DB>>,
29}
30
31impl<DB: FletchDatabase> Clone for FletchTransactionContext<DB> {
32 fn clone(&self) -> Self {
33 Self {
34 state: Arc::clone(&self.state),
35 }
36 }
37}
38
39impl<DB: FletchDatabase> FletchTransactionContext<DB> {
40 pub fn repo<T>(&self) -> FletchTransactionalRepository<T, DB>
42 where
43 T: FletchEntity<DB>,
44 {
45 FletchTransactionalRepository::new(Arc::clone(&self.state))
46 }
47}
48
49async fn begin_transaction<DB: FletchDatabase>(
50 pool: &fletch_orm::Pool<DB>,
51) -> Result<FletchTransactionContext<DB>, RepoError> {
52 let tx = pool.begin().await.map_err(from_fletch_err)?;
53 Ok(FletchTransactionContext {
54 state: Arc::new(FletchTransactionState {
55 tx: tokio::sync::Mutex::new(Some(tx)),
56 }),
57 })
58}
59
60async fn finish_transaction<DB: FletchDatabase, R: Send>(
61 state: Arc<FletchTransactionState<DB>>,
62 result: Result<R, RepoError>,
63) -> Result<R, RepoError> {
64 let mut guard = state.tx.lock().await;
65 let tx = guard
66 .take()
67 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
68 drop(guard);
69
70 match result {
71 Ok(value) => {
72 tx.commit().await.map_err(from_fletch_err)?;
73 Ok(value)
74 }
75 Err(err) => {
76 if let Err(rollback_err) = tx.rollback().await {
77 return Err(from_fletch_err(rollback_err));
78 }
79 Err(err)
80 }
81 }
82}
83
84pub struct FletchTransactionManager<DB: FletchDatabase> {
91 pool: fletch_orm::Pool<DB>,
92}
93
94impl<DB: FletchDatabase> FletchTransactionManager<DB> {
95 pub fn new(pool: fletch_orm::Pool<DB>) -> Self {
97 Self { pool }
98 }
99
100 pub fn from_dep(dep: &Dep<fletch_orm::Pool<DB>>) -> Self {
102 Self::new((**dep).clone())
103 }
104
105 pub fn pool(&self) -> &fletch_orm::Pool<DB> {
107 &self.pool
108 }
109
110 pub async fn transaction<F, Fut, R>(&self, f: F) -> Result<R, RepoError>
119 where
120 F: FnOnce(FletchTransactionContext<DB>) -> Fut,
121 Fut: Future<Output = Result<R, RepoError>>,
122 R: Send,
123 {
124 let ctx = begin_transaction(&self.pool).await?;
125 let state = Arc::clone(&ctx.state);
126 let result = f(ctx).await;
127 finish_transaction(state, result).await
128 }
129
130 pub fn transaction_boxed<F, Fut, R>(&self, f: F) -> BoxFuture<'_, Result<R, RepoError>>
132 where
133 F: FnOnce(FletchTransactionContext<DB>) -> Fut + Send + 'static,
134 Fut: Future<Output = Result<R, RepoError>> + Send + 'static,
135 R: Send + 'static,
136 {
137 let pool = self.pool.clone();
138 Box::pin(async move {
139 let manager = FletchTransactionManager { pool };
140 manager.transaction(f).await
141 })
142 }
143}
144
145impl<DB: FletchDatabase> TransactionManager for FletchTransactionManager<DB> {
146 type Context = FletchTransactionContext<DB>;
147
148 fn transaction<F, Fut, R>(&self, f: F) -> BoxFuture<'_, Result<R, RepoError>>
149 where
150 F: FnOnce(FletchTransactionContext<DB>) -> Fut + Send + 'static,
151 Fut: Future<Output = Result<R, RepoError>> + Send + 'static,
152 R: Send + 'static,
153 {
154 self.transaction_boxed(f)
155 }
156}