use crate::entity::CacheEntity;
use crate::error::Result;
#[allow(async_fn_in_trait)]
pub trait DataRepository<T: CacheEntity>: Send + Sync {
async fn fetch_by_id(&self, id: &T::Key) -> Result<Option<T>>;
async fn fetch_by_ids(&self, ids: &[T::Key]) -> Result<Vec<Option<T>>> {
let mut results = Vec::with_capacity(ids.len());
for id in ids {
results.push(self.fetch_by_id(id).await?);
}
Ok(results)
}
async fn count(&self) -> Result<u64> {
Err(crate::error::Error::NotImplemented(
"count not implemented".to_string(),
))
}
async fn fetch_all(&self) -> Result<Vec<T>> {
Err(crate::error::Error::NotImplemented(
"fetch_all not implemented for this repository".to_string(),
))
}
}
use std::collections::HashMap;
pub struct InMemoryRepository<T: CacheEntity> {
data: HashMap<String, T>,
}
impl<T: CacheEntity> InMemoryRepository<T> {
pub fn new() -> Self {
InMemoryRepository {
data: HashMap::new(),
}
}
pub fn insert(&mut self, id: T::Key, value: T) {
self.data.insert(id.to_string(), value);
}
pub fn clear(&mut self) {
self.data.clear();
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl<T: CacheEntity> Default for InMemoryRepository<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: CacheEntity> DataRepository<T> for InMemoryRepository<T> {
async fn fetch_by_id(&self, id: &T::Key) -> Result<Option<T>> {
Ok(self.data.get(&id.to_string()).cloned())
}
async fn fetch_by_ids(&self, ids: &[T::Key]) -> Result<Vec<Option<T>>> {
Ok(ids
.iter()
.map(|id| self.data.get(&id.to_string()).cloned())
.collect())
}
async fn count(&self) -> Result<u64> {
Ok(self.data.len() as u64)
}
async fn fetch_all(&self) -> Result<Vec<T>> {
Ok(self.data.values().cloned().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize)]
struct TestEntity {
id: String,
value: String,
}
impl CacheEntity for TestEntity {
type Key = String;
fn cache_key(&self) -> Self::Key {
self.id.clone()
}
fn cache_prefix() -> &'static str {
"test"
}
}
#[tokio::test]
async fn test_in_memory_repository() {
let mut repo = InMemoryRepository::new();
let entity = TestEntity {
id: "1".to_string(),
value: "data".to_string(),
};
repo.insert("1".to_string(), entity.clone());
let fetched = repo
.fetch_by_id(&"1".to_string())
.await
.expect("Failed to fetch");
assert!(fetched.is_some());
assert_eq!(fetched.expect("Entity not found").value, "data");
}
#[tokio::test]
async fn test_in_memory_repository_miss() {
let repo: InMemoryRepository<TestEntity> = InMemoryRepository::new();
let fetched = repo
.fetch_by_id(&"nonexistent".to_string())
.await
.expect("Failed to fetch");
assert!(fetched.is_none());
}
#[tokio::test]
async fn test_in_memory_repository_batch() {
let mut repo = InMemoryRepository::new();
repo.insert(
"1".to_string(),
TestEntity {
id: "1".to_string(),
value: "a".to_string(),
},
);
repo.insert(
"2".to_string(),
TestEntity {
id: "2".to_string(),
value: "b".to_string(),
},
);
let ids = vec!["1".to_string(), "2".to_string(), "3".to_string()];
let results = repo
.fetch_by_ids(&ids)
.await
.expect("Failed to fetch batch");
assert_eq!(results.len(), 3);
assert!(results[0].is_some());
assert!(results[1].is_some());
assert!(results[2].is_none());
}
#[tokio::test]
async fn test_in_memory_repository_count() {
let mut repo = InMemoryRepository::new();
repo.insert(
"1".to_string(),
TestEntity {
id: "1".to_string(),
value: "a".to_string(),
},
);
assert_eq!(repo.count().await.expect("Failed to count"), 1);
}
}