use crate::error::{Error, Result};
use std::collections::HashMap;
use std::sync::RwLock;
pub trait Storage<T>: Send + Sync
where
T: Clone + Send + Sync,
{
fn get(&self, id: &str) -> Result<Option<T>>;
fn get_all(&self) -> Result<Vec<T>>;
fn put(&self, id: &str, entity: T) -> Result<()>;
fn delete(&self, id: &str) -> Result<bool>;
fn exists(&self, id: &str) -> Result<bool>;
fn count(&self) -> Result<usize>;
fn clear(&self) -> Result<()>;
}
#[derive(Debug)]
pub struct InMemoryStorage<T> {
data: RwLock<HashMap<String, T>>,
}
impl<T> Default for InMemoryStorage<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> InMemoryStorage<T> {
pub fn new() -> Self {
InMemoryStorage {
data: RwLock::new(HashMap::new()),
}
}
pub fn from_map(map: HashMap<String, T>) -> Self {
InMemoryStorage {
data: RwLock::new(map),
}
}
}
impl<T> Storage<T> for InMemoryStorage<T>
where
T: Clone + Send + Sync,
{
fn get(&self, id: &str) -> Result<Option<T>> {
let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
Ok(data.get(id).cloned())
}
fn get_all(&self) -> Result<Vec<T>> {
let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
Ok(data.values().cloned().collect())
}
fn put(&self, id: &str, entity: T) -> Result<()> {
let mut data = self.data.write().map_err(|e| Error::storage(e.to_string()))?;
data.insert(id.to_string(), entity);
Ok(())
}
fn delete(&self, id: &str) -> Result<bool> {
let mut data = self.data.write().map_err(|e| Error::storage(e.to_string()))?;
Ok(data.remove(id).is_some())
}
fn exists(&self, id: &str) -> Result<bool> {
let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
Ok(data.contains_key(id))
}
fn count(&self) -> Result<usize> {
let data = self.data.read().map_err(|e| Error::storage(e.to_string()))?;
Ok(data.len())
}
fn clear(&self) -> Result<()> {
let mut data = self.data.write().map_err(|e| Error::storage(e.to_string()))?;
data.clear();
Ok(())
}
}
pub trait Identifiable {
fn id(&self) -> &str;
}
#[derive(Debug)]
pub struct Repository<T, S = InMemoryStorage<T>>
where
T: Clone + Send + Sync,
S: Storage<T>,
{
storage: S,
_marker: std::marker::PhantomData<T>,
}
impl<T> Repository<T, InMemoryStorage<T>>
where
T: Clone + Send + Sync,
{
pub fn in_memory() -> Self {
Repository {
storage: InMemoryStorage::new(),
_marker: std::marker::PhantomData,
}
}
}
impl<T, S> Repository<T, S>
where
T: Clone + Send + Sync,
S: Storage<T>,
{
pub fn new(storage: S) -> Self {
Repository {
storage,
_marker: std::marker::PhantomData,
}
}
pub fn get(&self, id: &str) -> Result<Option<T>> {
self.storage.get(id)
}
pub fn get_required(&self, id: &str) -> Result<T> {
self.storage
.get(id)?
.ok_or_else(|| Error::not_found("Entity", id))
}
pub fn get_all(&self) -> Result<Vec<T>> {
self.storage.get_all()
}
pub fn put(&self, id: &str, entity: T) -> Result<()> {
self.storage.put(id, entity)
}
pub fn delete(&self, id: &str) -> Result<bool> {
self.storage.delete(id)
}
pub fn exists(&self, id: &str) -> Result<bool> {
self.storage.exists(id)
}
pub fn count(&self) -> Result<usize> {
self.storage.count()
}
pub fn clear(&self) -> Result<()> {
self.storage.clear()
}
}
#[derive(Debug)]
pub struct AutoIdRepository<T, S = InMemoryStorage<T>>
where
T: Clone + Send + Sync + Identifiable,
S: Storage<T>,
{
storage: S,
_marker: std::marker::PhantomData<T>,
}
impl<T> AutoIdRepository<T, InMemoryStorage<T>>
where
T: Clone + Send + Sync + Identifiable,
{
pub fn in_memory() -> Self {
AutoIdRepository {
storage: InMemoryStorage::new(),
_marker: std::marker::PhantomData,
}
}
}
impl<T, S> AutoIdRepository<T, S>
where
T: Clone + Send + Sync + Identifiable,
S: Storage<T>,
{
pub fn new(storage: S) -> Self {
AutoIdRepository {
storage,
_marker: std::marker::PhantomData,
}
}
pub fn get(&self, id: &str) -> Result<Option<T>> {
self.storage.get(id)
}
pub fn get_required(&self, id: &str) -> Result<T> {
self.storage
.get(id)?
.ok_or_else(|| Error::not_found("Entity", id))
}
pub fn get_all(&self) -> Result<Vec<T>> {
self.storage.get_all()
}
pub fn save(&self, entity: T) -> Result<()> {
let id = entity.id().to_string();
self.storage.put(&id, entity)
}
pub fn delete(&self, id: &str) -> Result<bool> {
self.storage.delete(id)
}
pub fn exists(&self, id: &str) -> Result<bool> {
self.storage.exists(id)
}
pub fn count(&self) -> Result<usize> {
self.storage.count()
}
pub fn clear(&self) -> Result<()> {
self.storage.clear()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
struct TestEntity {
id: String,
name: String,
}
impl Identifiable for TestEntity {
fn id(&self) -> &str {
&self.id
}
}
#[test]
fn test_in_memory_storage_basic_operations() {
let storage = InMemoryStorage::<TestEntity>::new();
let entity = TestEntity {
id: "test-1".to_string(),
name: "Test Entity".to_string(),
};
storage.put("test-1", entity.clone()).unwrap();
let retrieved = storage.get("test-1").unwrap();
assert_eq!(retrieved, Some(entity));
assert!(storage.exists("test-1").unwrap());
assert!(!storage.exists("nonexistent").unwrap());
assert_eq!(storage.count().unwrap(), 1);
assert!(storage.delete("test-1").unwrap());
assert!(!storage.exists("test-1").unwrap());
}
#[test]
fn test_in_memory_storage_get_all() {
let storage = InMemoryStorage::<TestEntity>::new();
storage
.put(
"1",
TestEntity {
id: "1".to_string(),
name: "One".to_string(),
},
)
.unwrap();
storage
.put(
"2",
TestEntity {
id: "2".to_string(),
name: "Two".to_string(),
},
)
.unwrap();
let all = storage.get_all().unwrap();
assert_eq!(all.len(), 2);
}
#[test]
fn test_in_memory_storage_clear() {
let storage = InMemoryStorage::<TestEntity>::new();
storage
.put(
"1",
TestEntity {
id: "1".to_string(),
name: "One".to_string(),
},
)
.unwrap();
storage.clear().unwrap();
assert_eq!(storage.count().unwrap(), 0);
}
#[test]
fn test_repository() {
let repo = Repository::<TestEntity>::in_memory();
let entity = TestEntity {
id: "test-1".to_string(),
name: "Test".to_string(),
};
repo.put("test-1", entity.clone()).unwrap();
let retrieved = repo.get_required("test-1").unwrap();
assert_eq!(retrieved, entity);
let result = repo.get_required("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_auto_id_repository() {
let repo = AutoIdRepository::<TestEntity>::in_memory();
let entity = TestEntity {
id: "auto-1".to_string(),
name: "Auto Test".to_string(),
};
repo.save(entity.clone()).unwrap();
let retrieved = repo.get("auto-1").unwrap();
assert_eq!(retrieved, Some(entity));
}
}