#[macro_export]
macro_rules! repo {
(struct $repo:ident<$v:ident> {
$($f:ident: $t:ty$(,)?)*
}
$(struct $type:ident: $op:ident {
$($op_i:ident: $op_type:ty$(,)?)*
}
impl Operation {
$($o:item)*
}
)*
) => {
pub struct $repo {
$($f : $t)*,
}
impl Repository<$v> for $repo {}
$(
#[derive(Clone)]
pub struct $type {
r: Arc<$repo>,
$($op_i: $op_type,)*
}
impl operation::$op<$repo, $v> for $type {
fn new(r: Arc<$repo>, $($op_i: $op_type,)*) -> Self {
Self {
r,
$($op_i,)*
}
}
}
#[async_trait]
impl operation::Operation for $type {
type Error = <$repo as crate::Repository<$v>>::Error;
$($o)*
}
impl repository::$op<$v> for $repo {
type $op = $type;
}
)*
}
}
use crate::{
inmemory::InMemoryRepository,
macros::Id,
repo,
transactional::{operation, repository, Repository, RepositoryWrapper, TransactionRunner},
Add, Get, Identity, IdentityOf, List, Remove,
};
use async_trait::async_trait;
use std::error::Error;
use std::sync::Arc;
#[derive(Id, Clone, Debug, PartialEq, Eq)]
pub struct Foo {
id: String,
}
pub struct FooRepo {
cache: InMemoryRepository<Foo, Box<dyn Error + Send + Sync>>,
}
impl crate::Repository<Foo> for FooRepo {
type Error = Box<dyn Error + Send + Sync>;
}
#[async_trait]
impl Add<Foo> for FooRepo {
async fn add(&self, f: Foo) -> Result<(), Self::Error> {
self.cache.add(f).await
}
}
#[async_trait]
impl Get<Foo> for FooRepo {
async fn get_one(&self, k: &IdentityOf<Foo>) -> Result<Option<Foo>, Self::Error> {
self.cache.get_one(k).await
}
}
#[async_trait]
impl List<Foo> for FooRepo {
async fn list(&self) -> Result<Vec<Foo>, Self::Error> {
self.cache.list().await
}
}
#[async_trait]
impl Remove<Foo> for FooRepo {
async fn remove(&self, k: &IdentityOf<Foo>) -> Result<Option<Foo>, Self::Error> {
self.cache.remove(k).await
}
}
impl Repository<Foo> for FooRepo {}
impl repository::Add<Foo> for FooRepo {
type Add = operation::default::AddOperation<FooRepo, Foo>;
}
impl repository::List<Foo> for FooRepo {
type List = operation::default::ListOperation<FooRepo, Foo>;
}
impl repository::Get<Foo> for FooRepo {
type Get = operation::default::GetOperation<FooRepo, Foo>;
}
#[derive(Id, Clone, Debug, PartialEq, Eq)]
pub struct Bar {
id: usize,
}
struct BarRepo {
cache: InMemoryRepository<Bar, Box<dyn Error + Send + Sync>>,
}
impl crate::Repository<Bar> for BarRepo {
type Error = Box<dyn Error + Send + Sync>;
}
impl Repository<Bar> for BarRepo {}
#[async_trait]
impl Get<Bar> for BarRepo {
async fn get_one(&self, k: &IdentityOf<Bar>) -> Result<Option<Bar>, Self::Error> {
self.cache.get_one(k).await
}
}
#[async_trait]
impl Add<Bar> for BarRepo {
async fn add(&self, v: Bar) -> Result<(), Self::Error> {
self.cache.add(v).await
}
}
#[async_trait]
impl List<Bar> for BarRepo {
async fn list(&self) -> Result<Vec<Bar>, Self::Error> {
self.cache.list().await
}
}
#[async_trait]
impl Remove<Bar> for BarRepo {
async fn remove(&self, k: &IdentityOf<Bar>) -> Result<Option<Bar>, Self::Error> {
self.cache.remove(k).await
}
}
impl repository::Add<Bar> for BarRepo {
type Add = operation::default::AddOperation<BarRepo, Bar>;
}
impl repository::Get<Bar> for BarRepo {
type Get = operation::default::GetOperation<BarRepo, Bar>;
}
impl repository::List<Bar> for BarRepo {
type List = operation::default::ListOperation<BarRepo, Bar>;
}
#[tokio::test]
async fn multi_type() {
let foo_cache = Arc::new(FooRepo {
cache: InMemoryRepository::new(),
});
let bar_cache = Arc::new(BarRepo {
cache: InMemoryRepository::new(),
});
let repo = RepositoryWrapper::new(foo_cache.clone());
let foo = Foo {
id: "foo".to_string(),
};
let bar = Bar { id: 0 };
let r = repo
.add(foo.clone())
.change_type(bar_cache.clone())
.add(bar.clone())
.change_type(foo_cache.clone())
.list()
.change_type(bar_cache.clone())
.list()
.commit_transaction()
.await
.unwrap();
assert_eq!(r.get::<Vec<Foo>, _>(), &vec![foo.clone()]);
assert_eq!(r.get::<Vec<Bar>, _>(), &vec![bar.clone()]);
}
#[tokio::test]
async fn signle_type() {
let repo = RepositoryWrapper::new(Arc::new(FooRepo {
cache: InMemoryRepository::new(),
}));
let foo = Foo {
id: "bar".to_string(),
};
let result = repo
.add(foo.clone())
.get("bar".to_string())
.list()
.commit_transaction()
.await
.unwrap();
let (r, result) = result.pop();
assert_eq!(r, ());
let (r, result) = result.pop();
assert_eq!(r, ());
let (r, result) = result.pop();
assert_eq!(r, Some(foo.clone()));
let (r, _result) = result.pop();
assert_eq!(r, vec![foo.clone()]);
}
use derive_more::{Display, Error};
use once_cell::sync::OnceCell;
#[derive(Error, Display, Debug, PartialEq)]
pub struct TestError();
static ROLLBACK: OnceCell<()> = OnceCell::new();
#[allow(dead_code)]
mod rollback_repo {
use super::*;
repo! {
struct RollbackRepo<Foo> {
a: (),
}
struct RollbackFooGet: Get {
k: IdentityOf<Foo>,
}
impl Operation {
type Output = ();
async fn perform(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
Err(Box::new(TestError()))
}
async fn rollback(&mut self) {
ROLLBACK.set(()).unwrap();
}
}
}
impl RollbackRepo {
pub fn new() -> Self {
Self { a: () }
}
}
}
impl crate::Repository<Foo> for RollbackRepo {
type Error = Box<dyn Error + Send + Sync>;
}
use rollback_repo::*;
#[tokio::test]
async fn test_rollback() {
let r = Arc::new(RollbackRepo::new());
let repo = RepositoryWrapper::new(r);
let result = repo.get("bar".to_string()).commit_transaction().await;
matches!(result, Err(_));
assert_eq!(Some(&()), ROLLBACK.get());
}
#[tokio::test]
async fn as_repository() {
let repo = RepositoryWrapper::new(Arc::new(FooRepo {
cache: InMemoryRepository::new(),
}));
async fn test_as_repository<R>(r: R)
where
R: crate::Repository<Foo> + Add<Foo> + List<Foo>,
<R as crate::Repository<Foo>>::Error: std::fmt::Debug,
{
let foo = Foo {
id: "test".to_string(),
};
r.add(foo.clone()).await.unwrap();
assert_eq!(vec![foo], r.list().await.unwrap());
}
test_as_repository(repo).await
}