backbone_core/persistence/
memory.rs1use async_trait::async_trait;
10use chrono::Utc;
11use std::collections::HashMap;
12use std::sync::RwLock;
13
14use super::traits::{CrudRepository, PersistentEntity, RepositoryError, SearchableRepository};
15
16pub struct InMemoryRepository<E>
40where
41 E: PersistentEntity,
42{
43 store: RwLock<HashMap<String, E>>,
45 trash: RwLock<HashMap<String, E>>,
47}
48
49impl<E> InMemoryRepository<E>
50where
51 E: PersistentEntity,
52{
53 pub fn new() -> Self {
55 Self {
56 store: RwLock::new(HashMap::new()),
57 trash: RwLock::new(HashMap::new()),
58 }
59 }
60
61 pub fn with_data(entities: Vec<E>) -> Self {
63 let store: HashMap<String, E> = entities
64 .into_iter()
65 .map(|e| (e.entity_id(), e))
66 .collect();
67
68 Self {
69 store: RwLock::new(store),
70 trash: RwLock::new(HashMap::new()),
71 }
72 }
73
74 pub fn len(&self) -> usize {
76 self.store.read().unwrap().len()
77 }
78
79 pub fn is_empty(&self) -> bool {
81 self.store.read().unwrap().is_empty()
82 }
83
84 pub fn trash_len(&self) -> usize {
86 self.trash.read().unwrap().len()
87 }
88
89 pub fn clear(&self) {
91 self.store.write().unwrap().clear();
92 self.trash.write().unwrap().clear();
93 }
94}
95
96impl<E> Default for InMemoryRepository<E>
97where
98 E: PersistentEntity,
99{
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105#[async_trait]
106impl<E> CrudRepository<E> for InMemoryRepository<E>
107where
108 E: PersistentEntity,
109{
110 async fn create(&self, mut entity: E) -> Result<E, RepositoryError> {
111 let mut store = self.store.write().unwrap();
112
113 if entity.entity_id().is_empty() {
115 entity.set_entity_id(E::generate_id());
116 }
117
118 if store.contains_key(&entity.entity_id()) {
120 return Err(RepositoryError::AlreadyExists(format!(
121 "Entity with ID {} already exists",
122 entity.entity_id()
123 )));
124 }
125
126 let now = Utc::now();
128 if entity.created_at().is_none() {
129 entity.set_created_at(now);
130 }
131 entity.set_updated_at(now);
132
133 let id = entity.entity_id();
134 store.insert(id, entity.clone());
135 Ok(entity)
136 }
137
138 async fn find_by_id(&self, id: &str) -> Result<Option<E>, RepositoryError> {
139 let store = self.store.read().unwrap();
140 Ok(store.get(id).cloned())
141 }
142
143 async fn find_by_id_including_deleted(&self, id: &str) -> Result<Option<E>, RepositoryError> {
144 {
146 let store = self.store.read().unwrap();
147 if let Some(entity) = store.get(id) {
148 return Ok(Some(entity.clone()));
149 }
150 }
151
152 let trash = self.trash.read().unwrap();
154 Ok(trash.get(id).cloned())
155 }
156
157 async fn update(&self, mut entity: E) -> Result<E, RepositoryError> {
158 let mut store = self.store.write().unwrap();
159
160 if !store.contains_key(&entity.entity_id()) {
161 return Err(RepositoryError::NotFound);
162 }
163
164 entity.set_updated_at(Utc::now());
165 let id = entity.entity_id();
166 store.insert(id, entity.clone());
167 Ok(entity)
168 }
169
170 async fn soft_delete(&self, id: &str) -> Result<bool, RepositoryError> {
171 let mut store = self.store.write().unwrap();
172 let mut trash = self.trash.write().unwrap();
173
174 if let Some(mut entity) = store.remove(id) {
175 entity.mark_deleted();
176 trash.insert(id.to_string(), entity);
177 return Ok(true);
178 }
179
180 Ok(false)
181 }
182
183 async fn restore(&self, id: &str) -> Result<Option<E>, RepositoryError> {
184 let mut store = self.store.write().unwrap();
185 let mut trash = self.trash.write().unwrap();
186
187 if let Some(mut entity) = trash.remove(id) {
188 entity.restore();
189 store.insert(id.to_string(), entity.clone());
190 return Ok(Some(entity));
191 }
192
193 Ok(None)
194 }
195
196 async fn hard_delete(&self, id: &str) -> Result<bool, RepositoryError> {
197 let mut store = self.store.write().unwrap();
198 let mut trash = self.trash.write().unwrap();
199
200 let removed_active = store.remove(id).is_some();
202 let removed_trash = trash.remove(id).is_some();
203
204 Ok(removed_active || removed_trash)
205 }
206
207 async fn list(&self, page: u32, limit: u32) -> Result<(Vec<E>, u64), RepositoryError> {
208 let store = self.store.read().unwrap();
209 let total = store.len() as u64;
210
211 let offset = ((page.saturating_sub(1)) * limit) as usize;
212 let entities: Vec<E> = store
213 .values()
214 .skip(offset)
215 .take(limit as usize)
216 .cloned()
217 .collect();
218
219 Ok((entities, total))
220 }
221
222 async fn list_deleted(&self, page: u32, limit: u32) -> Result<(Vec<E>, u64), RepositoryError> {
223 let trash = self.trash.read().unwrap();
224 let total = trash.len() as u64;
225
226 let offset = ((page.saturating_sub(1)) * limit) as usize;
227 let entities: Vec<E> = trash
228 .values()
229 .skip(offset)
230 .take(limit as usize)
231 .cloned()
232 .collect();
233
234 Ok((entities, total))
235 }
236
237 async fn count(&self) -> Result<u64, RepositoryError> {
238 Ok(self.store.read().unwrap().len() as u64)
239 }
240
241 async fn count_deleted(&self) -> Result<u64, RepositoryError> {
242 Ok(self.trash.read().unwrap().len() as u64)
243 }
244
245 async fn bulk_create(&self, entities: Vec<E>) -> Result<Vec<E>, RepositoryError> {
246 let mut results = Vec::with_capacity(entities.len());
247 for entity in entities {
248 results.push(self.create(entity).await?);
249 }
250 Ok(results)
251 }
252
253 async fn empty_trash(&self) -> Result<u64, RepositoryError> {
254 let mut trash = self.trash.write().unwrap();
255 let count = trash.len() as u64;
256 trash.clear();
257 Ok(count)
258 }
259}
260
261#[async_trait]
262impl<E> SearchableRepository<E> for InMemoryRepository<E>
263where
264 E: PersistentEntity,
265{
266 async fn search(
267 &self,
268 filters: HashMap<String, String>,
269 page: u32,
270 limit: u32,
271 ) -> Result<(Vec<E>, u64), RepositoryError> {
272 let store = self.store.read().unwrap();
274
275 let filtered: Vec<E> = store
276 .values()
277 .filter(|entity| {
278 if let Ok(json) = serde_json::to_value(entity) {
280 filters.iter().all(|(key, value)| {
281 if let Some(field_value) = json.get(key) {
282 match field_value {
283 serde_json::Value::String(s) => {
284 s.to_lowercase().contains(&value.to_lowercase())
285 }
286 serde_json::Value::Number(n) => n.to_string() == *value,
287 serde_json::Value::Bool(b) => b.to_string() == *value,
288 _ => false,
289 }
290 } else {
291 false
292 }
293 })
294 } else {
295 false
296 }
297 })
298 .cloned()
299 .collect();
300
301 let total = filtered.len() as u64;
302 let offset = ((page.saturating_sub(1)) * limit) as usize;
303 let paginated: Vec<E> = filtered
304 .into_iter()
305 .skip(offset)
306 .take(limit as usize)
307 .collect();
308
309 Ok((paginated, total))
310 }
311
312 async fn find_by_field(&self, field: &str, value: &str) -> Result<Option<E>, RepositoryError> {
313 let store = self.store.read().unwrap();
314
315 for entity in store.values() {
316 if let Ok(json) = serde_json::to_value(entity) {
317 if let Some(field_value) = json.get(field) {
318 let matches = match field_value {
319 serde_json::Value::String(s) => s == value,
320 serde_json::Value::Number(n) => n.to_string() == value,
321 serde_json::Value::Bool(b) => b.to_string() == value,
322 _ => false,
323 };
324 if matches {
325 return Ok(Some(entity.clone()));
326 }
327 }
328 }
329 }
330
331 Ok(None)
332 }
333
334 async fn find_all_by_field(
335 &self,
336 field: &str,
337 value: &str,
338 page: u32,
339 limit: u32,
340 ) -> Result<(Vec<E>, u64), RepositoryError> {
341 let mut filters = HashMap::new();
342 filters.insert(field.to_string(), value.to_string());
343 self.search(filters, page, limit).await
344 }
345}
346
347#[cfg(test)]
352mod tests {
353 use super::*;
354 use chrono::Utc;
355 use serde::{Deserialize, Serialize};
356
357 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
358 struct TestEntity {
359 id: String,
360 name: String,
361 value: i32,
362 created_at: Option<chrono::DateTime<Utc>>,
363 updated_at: Option<chrono::DateTime<Utc>>,
364 deleted_at: Option<chrono::DateTime<Utc>>,
365 }
366
367 impl TestEntity {
368 fn new(name: &str, value: i32) -> Self {
369 Self {
370 id: String::new(),
371 name: name.to_string(),
372 value,
373 created_at: None,
374 updated_at: None,
375 deleted_at: None,
376 }
377 }
378 }
379
380 impl PersistentEntity for TestEntity {
381 fn entity_id(&self) -> String {
382 self.id.clone()
383 }
384
385 fn set_entity_id(&mut self, id: String) {
386 self.id = id;
387 }
388
389 fn created_at(&self) -> Option<chrono::DateTime<Utc>> {
390 self.created_at
391 }
392
393 fn set_created_at(&mut self, ts: chrono::DateTime<Utc>) {
394 self.created_at = Some(ts);
395 }
396
397 fn updated_at(&self) -> Option<chrono::DateTime<Utc>> {
398 self.updated_at
399 }
400
401 fn set_updated_at(&mut self, ts: chrono::DateTime<Utc>) {
402 self.updated_at = Some(ts);
403 }
404
405 fn deleted_at(&self) -> Option<chrono::DateTime<Utc>> {
406 self.deleted_at
407 }
408
409 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<Utc>>) {
410 self.deleted_at = ts;
411 }
412 }
413
414 #[tokio::test]
415 async fn test_create_and_find() {
416 let repo = InMemoryRepository::<TestEntity>::new();
417 let entity = TestEntity::new("test", 42);
418
419 let created = repo.create(entity).await.unwrap();
420 assert!(!created.id.is_empty());
421 assert_eq!(created.name, "test");
422 assert!(created.created_at.is_some());
423
424 let found = repo.find_by_id(&created.id).await.unwrap();
425 assert!(found.is_some());
426 assert_eq!(found.unwrap().name, "test");
427 }
428
429 #[tokio::test]
430 async fn test_soft_delete_and_restore() {
431 let repo = InMemoryRepository::<TestEntity>::new();
432 let entity = TestEntity::new("test", 42);
433
434 let created = repo.create(entity).await.unwrap();
435 let id = created.id.clone();
436
437 assert!(repo.soft_delete(&id).await.unwrap());
439 assert!(repo.find_by_id(&id).await.unwrap().is_none());
440 assert!(repo.find_by_id_including_deleted(&id).await.unwrap().is_some());
441
442 let restored = repo.restore(&id).await.unwrap();
444 assert!(restored.is_some());
445 assert!(repo.find_by_id(&id).await.unwrap().is_some());
446 }
447
448 #[tokio::test]
449 async fn test_bulk_create() {
450 let repo = InMemoryRepository::<TestEntity>::new();
451 let entities = vec![
452 TestEntity::new("one", 1),
453 TestEntity::new("two", 2),
454 TestEntity::new("three", 3),
455 ];
456
457 let created = repo.bulk_create(entities).await.unwrap();
458 assert_eq!(created.len(), 3);
459 assert_eq!(repo.count().await.unwrap(), 3);
460 }
461
462 #[tokio::test]
463 async fn test_pagination() {
464 let repo = InMemoryRepository::<TestEntity>::new();
465
466 for i in 0..10 {
468 repo.create(TestEntity::new(&format!("entity_{}", i), i))
469 .await
470 .unwrap();
471 }
472
473 let (page1, total) = repo.list(1, 3).await.unwrap();
475 assert_eq!(total, 10);
476 assert_eq!(page1.len(), 3);
477
478 let (page2, _) = repo.list(2, 3).await.unwrap();
479 assert_eq!(page2.len(), 3);
480 }
481
482 #[tokio::test]
483 async fn test_search() {
484 let repo = InMemoryRepository::<TestEntity>::new();
485
486 repo.create(TestEntity::new("apple", 1)).await.unwrap();
487 repo.create(TestEntity::new("banana", 2)).await.unwrap();
488 repo.create(TestEntity::new("apple pie", 3)).await.unwrap();
489
490 let mut filters = HashMap::new();
491 filters.insert("name".to_string(), "apple".to_string());
492
493 let (results, total) = repo.search(filters, 1, 10).await.unwrap();
494 assert_eq!(total, 2);
495 assert_eq!(results.len(), 2);
496 }
497}