use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct InMemoryStore<T: Clone + Send + Sync> {
items: RwLock<HashMap<String, T>>,
deleted: RwLock<HashMap<String, T>>,
}
impl<T: Clone + Send + Sync> InMemoryStore<T> {
pub fn new() -> Self {
Self {
items: RwLock::new(HashMap::new()),
deleted: RwLock::new(HashMap::new()),
}
}
pub fn new_shared() -> Arc<Self> {
Arc::new(Self::new())
}
pub async fn insert(&self, id: String, item: T) {
self.items.write().await.insert(id, item);
}
pub async fn get(&self, id: &str) -> Option<T> {
self.items.read().await.get(id).cloned()
}
pub async fn get_deleted(&self, id: &str) -> Option<T> {
self.deleted.read().await.get(id).cloned()
}
pub async fn exists(&self, id: &str) -> bool {
self.items.read().await.contains_key(id)
}
pub async fn exists_in_trash(&self, id: &str) -> bool {
self.deleted.read().await.contains_key(id)
}
pub async fn update(&self, id: &str, item: T) -> Option<T> {
let mut items = self.items.write().await;
if items.contains_key(id) {
items.insert(id.to_string(), item.clone());
Some(item)
} else {
None
}
}
pub async fn remove(&self, id: &str) -> Option<T> {
self.items.write().await.remove(id)
}
pub async fn soft_delete(&self, id: &str) -> bool {
let mut items = self.items.write().await;
let mut deleted = self.deleted.write().await;
if let Some(item) = items.remove(id) {
deleted.insert(id.to_string(), item);
true
} else {
false
}
}
pub async fn restore(&self, id: &str) -> Option<T> {
let mut items = self.items.write().await;
let mut deleted = self.deleted.write().await;
if let Some(item) = deleted.remove(id) {
items.insert(id.to_string(), item.clone());
Some(item)
} else {
None
}
}
pub async fn hard_delete(&self, id: &str) -> Option<T> {
self.deleted.write().await.remove(id)
}
pub async fn empty_trash(&self) -> u64 {
let mut deleted = self.deleted.write().await;
let count = deleted.len() as u64;
deleted.clear();
count
}
pub async fn list(&self, page: u32, limit: u32) -> (Vec<T>, u64) {
let items = self.items.read().await;
let total = items.len() as u64;
let skip = ((page.saturating_sub(1)) * limit) as usize;
let items: Vec<T> = items.values().skip(skip).take(limit as usize).cloned().collect();
(items, total)
}
pub async fn list_deleted(&self, page: u32, limit: u32) -> (Vec<T>, u64) {
let deleted = self.deleted.read().await;
let total = deleted.len() as u64;
let skip = ((page.saturating_sub(1)) * limit) as usize;
let items: Vec<T> = deleted.values().skip(skip).take(limit as usize).cloned().collect();
(items, total)
}
pub async fn all(&self) -> Vec<T> {
self.items.read().await.values().cloned().collect()
}
pub async fn all_deleted(&self) -> Vec<T> {
self.deleted.read().await.values().cloned().collect()
}
pub async fn count(&self) -> u64 {
self.items.read().await.len() as u64
}
pub async fn trash_count(&self) -> u64 {
self.deleted.read().await.len() as u64
}
pub async fn clear(&self) {
self.items.write().await.clear();
self.deleted.write().await.clear();
}
pub async fn clear_active(&self) {
self.items.write().await.clear();
}
pub async fn ids(&self) -> Vec<String> {
self.items.read().await.keys().cloned().collect()
}
pub async fn trash_ids(&self) -> Vec<String> {
self.deleted.read().await.keys().cloned().collect()
}
}
impl<T: Clone + Send + Sync> Default for InMemoryStore<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, PartialEq)]
struct TestEntity {
id: String,
name: String,
}
#[tokio::test]
async fn test_insert_and_get() {
let store = InMemoryStore::<TestEntity>::new();
let entity = TestEntity {
id: "1".to_string(),
name: "Test".to_string(),
};
store.insert("1".to_string(), entity.clone()).await;
let found = store.get("1").await;
assert!(found.is_some());
assert_eq!(found.unwrap().name, "Test");
}
#[tokio::test]
async fn test_update() {
let store = InMemoryStore::<TestEntity>::new();
let entity = TestEntity {
id: "1".to_string(),
name: "Original".to_string(),
};
store.insert("1".to_string(), entity).await;
let updated = TestEntity {
id: "1".to_string(),
name: "Updated".to_string(),
};
let result = store.update("1", updated).await;
assert!(result.is_some());
let found = store.get("1").await;
assert_eq!(found.unwrap().name, "Updated");
}
#[tokio::test]
async fn test_soft_delete_and_restore() {
let store = InMemoryStore::<TestEntity>::new();
let entity = TestEntity {
id: "1".to_string(),
name: "Test".to_string(),
};
store.insert("1".to_string(), entity).await;
let deleted = store.soft_delete("1").await;
assert!(deleted);
assert!(store.get("1").await.is_none());
assert!(store.get_deleted("1").await.is_some());
let restored = store.restore("1").await;
assert!(restored.is_some());
assert!(store.get("1").await.is_some());
assert!(store.get_deleted("1").await.is_none());
}
#[tokio::test]
async fn test_list_pagination() {
let store = InMemoryStore::<TestEntity>::new();
for i in 0..10 {
let entity = TestEntity {
id: i.to_string(),
name: format!("Entity {}", i),
};
store.insert(i.to_string(), entity).await;
}
let (items, total) = store.list(1, 3).await;
assert_eq!(items.len(), 3);
assert_eq!(total, 10);
let (items, total) = store.list(4, 3).await;
assert_eq!(items.len(), 1);
assert_eq!(total, 10);
}
#[tokio::test]
async fn test_empty_trash() {
let store = InMemoryStore::<TestEntity>::new();
for i in 0..5 {
let entity = TestEntity {
id: i.to_string(),
name: format!("Entity {}", i),
};
store.insert(i.to_string(), entity).await;
store.soft_delete(&i.to_string()).await;
}
assert_eq!(store.trash_count().await, 5);
let emptied = store.empty_trash().await;
assert_eq!(emptied, 5);
assert_eq!(store.trash_count().await, 0);
}
#[tokio::test]
async fn test_exists() {
let store = InMemoryStore::<TestEntity>::new();
let entity = TestEntity {
id: "1".to_string(),
name: "Test".to_string(),
};
store.insert("1".to_string(), entity).await;
assert!(store.exists("1").await);
assert!(!store.exists("2").await);
store.soft_delete("1").await;
assert!(!store.exists("1").await);
assert!(store.exists_in_trash("1").await);
}
}