use crate::errors::{BatchResult, Result};
use std::fmt::Debug;
#[async_trait::async_trait]
#[auto_impl::auto_impl(&, &mut, Box, Arc)]
pub trait AsyncRepository: Send + Sync + Debug {
type Entity: Send + Sync;
type Id: Send + Sync + Clone;
type CreateInput: Send + Sync;
type UpdateInput: Send + Sync;
type Filter: Send + Sync + Default;
async fn create(&self, input: Self::CreateInput) -> Result<Self::Entity>;
async fn get(&self, id: Self::Id) -> Result<Option<Self::Entity>>;
async fn update(&self, id: Self::Id, input: Self::UpdateInput) -> Result<Self::Entity>;
async fn delete(&self, id: Self::Id) -> Result<()>;
async fn list(&self, filter: Self::Filter) -> Result<Vec<Self::Entity>>;
async fn count(&self, filter: Self::Filter) -> Result<u64>;
async fn get_batch(&self, ids: Vec<Self::Id>) -> Result<Vec<Self::Entity>> {
let mut results = Vec::with_capacity(ids.len());
for id in ids {
if let Some(entity) = self.get(id).await? {
results.push(entity);
}
}
Ok(results)
}
async fn create_batch(
&self,
inputs: Vec<Self::CreateInput>,
) -> Result<BatchResult<Self::Entity>> {
let mut result = BatchResult::with_capacity(inputs.len());
for (i, input) in inputs.into_iter().enumerate() {
match self.create(input).await {
Ok(entity) => result.record_success(entity),
Err(err) => result.record_failure(i, None, &err),
}
}
Ok(result)
}
}
#[async_trait::async_trait]
#[auto_impl::auto_impl(&, &mut, Box, Arc)]
pub trait AsyncTransactional: Send + Sync {
async fn begin_transaction(&self) -> Result<()>;
async fn commit(&self) -> Result<()>;
async fn rollback(&self) -> Result<()>;
async fn with_transaction<T, F, Fut>(&self, f: F) -> Result<T>
where
T: Send,
F: FnOnce() -> Fut + Send,
Fut: std::future::Future<Output = Result<T>> + Send,
{
self.begin_transaction().await?;
match f().await {
Ok(val) => {
self.commit().await?;
Ok(val)
}
Err(err) => {
let _ = self.rollback().await;
Err(err)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::CommerceError;
use std::sync::Arc;
#[derive(Debug)]
struct AsyncInMemoryRepo {
items: tokio::sync::Mutex<Vec<(u64, String)>>,
next_id: tokio::sync::Mutex<u64>,
}
impl AsyncInMemoryRepo {
fn new() -> Self {
Self { items: tokio::sync::Mutex::new(Vec::new()), next_id: tokio::sync::Mutex::new(1) }
}
}
#[derive(Debug, Default)]
struct NoFilter;
#[async_trait::async_trait]
impl AsyncRepository for AsyncInMemoryRepo {
type Entity = (u64, String);
type Id = u64;
type CreateInput = String;
type UpdateInput = String;
type Filter = NoFilter;
async fn create(&self, input: String) -> Result<(u64, String)> {
let mut id = self.next_id.lock().await;
let current = *id;
*id += 1;
let entity = (current, input);
self.items.lock().await.push(entity.clone());
Ok(entity)
}
async fn get(&self, id: u64) -> Result<Option<(u64, String)>> {
Ok(self.items.lock().await.iter().find(|(i, _)| *i == id).cloned())
}
async fn update(&self, id: u64, input: String) -> Result<(u64, String)> {
let mut items = self.items.lock().await;
if let Some(item) = items.iter_mut().find(|(i, _)| *i == id) {
item.1 = input;
Ok(item.clone())
} else {
Err(CommerceError::NotFound)
}
}
async fn delete(&self, id: u64) -> Result<()> {
let mut items = self.items.lock().await;
items.retain(|(i, _)| *i != id);
Ok(())
}
async fn list(&self, _filter: NoFilter) -> Result<Vec<(u64, String)>> {
Ok(self.items.lock().await.clone())
}
async fn count(&self, _filter: NoFilter) -> Result<u64> {
Ok(self.items.lock().await.len() as u64)
}
}
#[tokio::test]
async fn async_crud_operations() {
let repo = AsyncInMemoryRepo::new();
let entity = repo.create("hello".to_string()).await.unwrap();
assert_eq!(entity.0, 1);
assert_eq!(entity.1, "hello");
let found = repo.get(1).await.unwrap();
assert_eq!(found, Some((1, "hello".to_string())));
let updated = repo.update(1, "world".to_string()).await.unwrap();
assert_eq!(updated.1, "world");
assert_eq!(repo.count(NoFilter).await.unwrap(), 1);
repo.delete(1).await.unwrap();
assert_eq!(repo.count(NoFilter).await.unwrap(), 0);
}
#[tokio::test]
async fn async_get_batch_default() {
let repo = AsyncInMemoryRepo::new();
repo.create("a".to_string()).await.unwrap();
repo.create("b".to_string()).await.unwrap();
repo.create("c".to_string()).await.unwrap();
let batch = repo.get_batch(vec![1, 3]).await.unwrap();
assert_eq!(batch.len(), 2);
assert_eq!(batch[0].1, "a");
assert_eq!(batch[1].1, "c");
}
#[tokio::test]
async fn async_create_batch_default() {
let repo = AsyncInMemoryRepo::new();
let result = repo
.create_batch(vec!["x".to_string(), "y".to_string(), "z".to_string()])
.await
.unwrap();
assert_eq!(result.success_count, 3);
assert_eq!(result.failure_count, 0);
assert!(result.all_succeeded());
}
#[tokio::test]
async fn async_arc_wrapped() {
let repo = Arc::new(AsyncInMemoryRepo::new());
repo.create("hello".to_string()).await.unwrap();
assert_eq!(repo.count(NoFilter).await.unwrap(), 1);
}
async fn async_count_all<R: AsyncRepository>(repo: &R) -> Result<u64>
where
R::Filter: Default,
{
repo.count(R::Filter::default()).await
}
#[tokio::test]
async fn async_polymorphic_count() {
let repo = AsyncInMemoryRepo::new();
repo.create("one".to_string()).await.unwrap();
assert_eq!(async_count_all(&repo).await.unwrap(), 1);
}
}