use crate::errors::{BatchResult, Result};
use std::fmt::Debug;
#[auto_impl::auto_impl(&, &mut, Box, Arc)]
pub trait Repository: 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;
fn create(&self, input: Self::CreateInput) -> Result<Self::Entity>;
fn get(&self, id: Self::Id) -> Result<Option<Self::Entity>>;
fn update(&self, id: Self::Id, input: Self::UpdateInput) -> Result<Self::Entity>;
fn delete(&self, id: Self::Id) -> Result<()>;
fn list(&self, filter: Self::Filter) -> Result<Vec<Self::Entity>>;
fn count(&self, filter: Self::Filter) -> Result<u64>;
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)? {
results.push(entity);
}
}
Ok(results)
}
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) {
Ok(entity) => result.record_success(entity),
Err(err) => result.record_failure(i, None, &err),
}
}
Ok(result)
}
}
#[auto_impl::auto_impl(&, &mut, Box, Arc)]
pub trait Transactional: Send + Sync {
fn begin_transaction(&self) -> Result<()>;
fn commit(&self) -> Result<()>;
fn rollback(&self) -> Result<()>;
fn with_transaction<T, F>(&self, f: F) -> Result<T>
where
F: FnOnce() -> Result<T>,
{
self.begin_transaction()?;
match f() {
Ok(val) => {
self.commit()?;
Ok(val)
}
Err(err) => {
let _ = self.rollback();
Err(err)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::CommerceError;
use std::sync::Arc;
#[derive(Debug)]
struct InMemoryRepo {
items: std::sync::Mutex<Vec<(u64, String)>>,
next_id: std::sync::Mutex<u64>,
}
impl InMemoryRepo {
fn new() -> Self {
Self { items: std::sync::Mutex::new(Vec::new()), next_id: std::sync::Mutex::new(1) }
}
}
#[derive(Debug, Default)]
struct NoFilter;
impl Repository for InMemoryRepo {
type Entity = (u64, String);
type Id = u64;
type CreateInput = String;
type UpdateInput = String;
type Filter = NoFilter;
fn create(&self, input: String) -> Result<(u64, String)> {
let mut id = self.next_id.lock().unwrap();
let current = *id;
*id += 1;
let entity = (current, input);
self.items.lock().unwrap().push(entity.clone());
Ok(entity)
}
fn get(&self, id: u64) -> Result<Option<(u64, String)>> {
Ok(self.items.lock().unwrap().iter().find(|(i, _)| *i == id).cloned())
}
fn update(&self, id: u64, input: String) -> Result<(u64, String)> {
let mut items = self.items.lock().unwrap();
if let Some(item) = items.iter_mut().find(|(i, _)| *i == id) {
item.1 = input;
Ok(item.clone())
} else {
Err(CommerceError::NotFound)
}
}
fn delete(&self, id: u64) -> Result<()> {
let mut items = self.items.lock().unwrap();
items.retain(|(i, _)| *i != id);
Ok(())
}
fn list(&self, _filter: NoFilter) -> Result<Vec<(u64, String)>> {
Ok(self.items.lock().unwrap().clone())
}
fn count(&self, _filter: NoFilter) -> Result<u64> {
Ok(self.items.lock().unwrap().len() as u64)
}
}
#[test]
fn crud_operations() {
let repo = InMemoryRepo::new();
let entity = repo.create("hello".to_string()).unwrap();
assert_eq!(entity.0, 1);
assert_eq!(entity.1, "hello");
let found = repo.get(1).unwrap();
assert_eq!(found, Some((1, "hello".to_string())));
let updated = repo.update(1, "world".to_string()).unwrap();
assert_eq!(updated.1, "world");
assert_eq!(repo.count(NoFilter).unwrap(), 1);
repo.delete(1).unwrap();
assert_eq!(repo.count(NoFilter).unwrap(), 0);
}
#[test]
fn get_batch_default() {
let repo = InMemoryRepo::new();
repo.create("a".to_string()).unwrap();
repo.create("b".to_string()).unwrap();
repo.create("c".to_string()).unwrap();
let batch = repo.get_batch(vec![1, 3]).unwrap();
assert_eq!(batch.len(), 2);
assert_eq!(batch[0].1, "a");
assert_eq!(batch[1].1, "c");
}
#[test]
fn create_batch_default() {
let repo = InMemoryRepo::new();
let result =
repo.create_batch(vec!["x".to_string(), "y".to_string(), "z".to_string()]).unwrap();
assert_eq!(result.success_count, 3);
assert_eq!(result.failure_count, 0);
assert!(result.all_succeeded());
}
fn _takes_arc_repo(
_: Arc<
dyn Repository<
Entity = (),
Id = u64,
CreateInput = (),
UpdateInput = (),
Filter = NoFilter,
>,
>,
) {
}
fn _takes_box_repo(
_: Box<
dyn Repository<
Entity = (),
Id = u64,
CreateInput = (),
UpdateInput = (),
Filter = NoFilter,
>,
>,
) {
}
fn _takes_ref_repo<R: Repository>(_: &R) {}
#[test]
fn arc_wrapped() {
let repo = Arc::new(InMemoryRepo::new());
repo.create("hello".to_string()).unwrap();
assert_eq!(repo.count(NoFilter).unwrap(), 1);
}
#[test]
fn polymorphic_count() {
fn count_all<R: Repository>(repo: &R) -> Result<u64>
where
R::Filter: Default,
{
repo.count(R::Filter::default())
}
let repo = InMemoryRepo::new();
repo.create("one".to_string()).unwrap();
assert_eq!(count_all(&repo).unwrap(), 1);
}
}