typesafe_repository 0.2.0

Abstract data persistence in a safe and unified way
Documentation
#[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
}