1use std::marker::PhantomData;
4use std::sync::Arc;
5
6use aro_core::error::RepoError;
7use aro_core::repository::{BoxFuture, Repository};
8use aro_web::dep::Dep;
9use sqlx::Database;
10
11use crate::error::from_fletch_err;
12
13pub trait FletchDatabase: Database + Send + Sync + 'static {}
17
18#[cfg(feature = "sqlite")]
19impl FletchDatabase for sqlx::Sqlite {}
20
21#[cfg(feature = "postgres")]
22impl FletchDatabase for sqlx::Postgres {}
23
24#[cfg(feature = "mysql")]
25impl FletchDatabase for sqlx::MySql {}
26
27pub trait FletchEntity<DB: FletchDatabase>:
32 aro_core::entity::Entity
33 + fletch_orm::Entity<Id = <Self as aro_core::entity::Entity>::Id>
34 + for<'r> sqlx::FromRow<'r, DB::Row>
35 + Send
36 + Sync
37 + Unpin
38 + Clone
39{
40}
41
42impl<T, DB> FletchEntity<DB> for T
44where
45 DB: FletchDatabase,
46 T: aro_core::entity::Entity
47 + fletch_orm::Entity<Id = <T as aro_core::entity::Entity>::Id>
48 + for<'r> sqlx::FromRow<'r, DB::Row>
49 + Send
50 + Sync
51 + Unpin
52 + Clone,
53{
54}
55
56pub struct FletchRepository<T, DB: FletchDatabase> {
60 pool: fletch_orm::Pool<DB>,
61 _marker: PhantomData<T>,
62}
63
64impl<T, DB> FletchRepository<T, DB>
65where
66 DB: FletchDatabase,
67{
68 pub fn new(pool: fletch_orm::Pool<DB>) -> Self {
70 Self {
71 pool,
72 _marker: PhantomData,
73 }
74 }
75
76 pub fn from_dep(dep: &Dep<fletch_orm::Pool<DB>>) -> Self {
81 Self::new((**dep).clone())
82 }
83
84 pub fn pool(&self) -> &fletch_orm::Pool<DB> {
86 &self.pool
87 }
88}
89
90pub struct FletchTransactionalRepository<T, DB: FletchDatabase> {
95 state: Arc<crate::transaction::FletchTransactionState<DB>>,
96 _marker: PhantomData<T>,
97}
98
99impl<T, DB> FletchTransactionalRepository<T, DB>
100where
101 DB: FletchDatabase,
102{
103 pub(crate) fn new(state: Arc<crate::transaction::FletchTransactionState<DB>>) -> Self {
104 Self {
105 state,
106 _marker: PhantomData,
107 }
108 }
109}
110
111macro_rules! impl_transactional_repository {
120 (mysql, $db:ty, $dialect:expr) => {
121 impl<T> FletchTransactionalRepository<T, $db>
122 where
123 T: FletchEntity<$db>,
124 {
125 pub async fn find_by_id(
127 &self,
128 id: <T as aro_core::entity::Entity>::Id,
129 ) -> Result<T, RepoError> {
130 let mut guard = self.state.tx.lock().await;
131 let tx = guard
132 .as_mut()
133 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
134 fletch_orm::find_by_id($dialect, &mut **tx.inner_mut(), id)
135 .await
136 .map_err(from_fletch_err)
137 }
138
139 pub async fn find_all(&self) -> Result<Vec<T>, RepoError> {
141 let mut guard = self.state.tx.lock().await;
142 let tx = guard
143 .as_mut()
144 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
145 fletch_orm::find_all($dialect, &mut **tx.inner_mut())
146 .await
147 .map_err(from_fletch_err)
148 }
149
150 pub async fn create(&self, entity: T) -> Result<T, RepoError> {
152 let mut guard = self.state.tx.lock().await;
153 let tx = guard
154 .as_mut()
155 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
156 tx.insert(&entity).await.map_err(from_fletch_err)
157 }
158
159 pub async fn update(&self, entity: T) -> Result<T, RepoError> {
161 let mut guard = self.state.tx.lock().await;
162 let tx = guard
163 .as_mut()
164 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
165 tx.update(&entity).await.map_err(from_fletch_err)
166 }
167
168 pub async fn delete(
170 &self,
171 id: <T as aro_core::entity::Entity>::Id,
172 ) -> Result<(), RepoError> {
173 let mut guard = self.state.tx.lock().await;
174 let tx = guard
175 .as_mut()
176 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
177 fletch_orm::delete::<T, $db, _, _>($dialect, &mut **tx.inner_mut(), id)
178 .await
179 .map_err(from_fletch_err)
180 }
181 }
182 };
183
184 (returning, $db:ty, $dialect:expr) => {
185 impl<T> FletchTransactionalRepository<T, $db>
186 where
187 T: FletchEntity<$db>,
188 {
189 pub async fn find_by_id(
191 &self,
192 id: <T as aro_core::entity::Entity>::Id,
193 ) -> Result<T, RepoError> {
194 let mut guard = self.state.tx.lock().await;
195 let tx = guard
196 .as_mut()
197 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
198 fletch_orm::find_by_id($dialect, &mut **tx.inner_mut(), id)
199 .await
200 .map_err(from_fletch_err)
201 }
202
203 pub async fn find_all(&self) -> Result<Vec<T>, RepoError> {
205 let mut guard = self.state.tx.lock().await;
206 let tx = guard
207 .as_mut()
208 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
209 fletch_orm::find_all($dialect, &mut **tx.inner_mut())
210 .await
211 .map_err(from_fletch_err)
212 }
213
214 pub async fn create(&self, entity: T) -> Result<T, RepoError> {
216 let mut guard = self.state.tx.lock().await;
217 let tx = guard
218 .as_mut()
219 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
220 fletch_orm::insert($dialect, &mut **tx.inner_mut(), &entity)
221 .await
222 .map_err(from_fletch_err)
223 }
224
225 pub async fn update(&self, entity: T) -> Result<T, RepoError> {
227 let mut guard = self.state.tx.lock().await;
228 let tx = guard
229 .as_mut()
230 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
231 fletch_orm::update($dialect, &mut **tx.inner_mut(), &entity)
232 .await
233 .map_err(from_fletch_err)
234 }
235
236 pub async fn delete(
238 &self,
239 id: <T as aro_core::entity::Entity>::Id,
240 ) -> Result<(), RepoError> {
241 let mut guard = self.state.tx.lock().await;
242 let tx = guard
243 .as_mut()
244 .ok_or_else(|| RepoError::Unknown("transaction is no longer active".into()))?;
245 fletch_orm::delete::<T, $db, _, _>($dialect, &mut **tx.inner_mut(), id)
246 .await
247 .map_err(from_fletch_err)
248 }
249 }
250 };
251}
252
253#[cfg(feature = "sqlite")]
254impl_transactional_repository!(returning, sqlx::Sqlite, &fletch_orm::SqliteDialect);
255
256#[cfg(feature = "postgres")]
257impl_transactional_repository!(returning, sqlx::Postgres, &fletch_orm::PostgresDialect);
258
259#[cfg(feature = "mysql")]
260impl_transactional_repository!(mysql, sqlx::MySql, &fletch_orm::MySqlDialect);
261
262macro_rules! impl_repository {
263 ($db:ty) => {
264 impl<T> Repository<T> for FletchRepository<T, $db>
265 where
266 T: FletchEntity<$db>,
267 <T as aro_core::entity::Entity>::Id: Into<fletch_orm::Value>,
268 {
269 fn find_by_id(
270 &self,
271 id: <T as aro_core::entity::Entity>::Id,
272 ) -> BoxFuture<'_, Result<T, RepoError>> {
273 Box::pin(
274 async move { self.pool.find_by_id::<T>(id).await.map_err(from_fletch_err) },
275 )
276 }
277
278 fn find_all(&self) -> BoxFuture<'_, Result<Vec<T>, RepoError>> {
279 Box::pin(async move { self.pool.find_all::<T>().await.map_err(from_fletch_err) })
280 }
281
282 fn create(&self, entity: T) -> BoxFuture<'_, Result<T, RepoError>> {
283 Box::pin(async move {
284 self.pool
285 .insert::<T>(&entity)
286 .await
287 .map_err(from_fletch_err)
288 })
289 }
290
291 fn update(&self, entity: T) -> BoxFuture<'_, Result<T, RepoError>> {
292 Box::pin(async move {
293 self.pool
294 .update::<T>(&entity)
295 .await
296 .map_err(from_fletch_err)
297 })
298 }
299
300 fn delete(
301 &self,
302 id: <T as aro_core::entity::Entity>::Id,
303 ) -> BoxFuture<'_, Result<(), RepoError>> {
304 Box::pin(async move { self.pool.delete::<T>(id).await.map_err(from_fletch_err) })
305 }
306 }
307 };
308}
309
310#[cfg(feature = "sqlite")]
311impl_repository!(sqlx::Sqlite);
312
313#[cfg(feature = "postgres")]
314impl_repository!(sqlx::Postgres);
315
316#[cfg(feature = "mysql")]
317impl_repository!(sqlx::MySql);