Skip to main content

this/storage/
in_memory.rs

1//! In-memory implementations of DataService and LinkService for testing and development
2
3use crate::core::field::FieldValue;
4use crate::core::{Data, DataService, LinkService, link::LinkEntity};
5use anyhow::{Result, anyhow};
6use async_trait::async_trait;
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9use uuid::Uuid;
10
11/// In-memory implementation of DataService
12///
13/// Provides a generic in-memory store for any entity type implementing `Data`.
14/// Useful for testing, development, and prototyping without external dependencies.
15///
16/// Uses `Arc<RwLock<HashMap>>` for thread-safe concurrent access.
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use this::storage::InMemoryDataService;
22///
23/// let service = InMemoryDataService::<MyEntity>::new();
24/// let entity = service.create(my_entity).await?;
25/// let found = service.get(&entity.id()).await?;
26/// ```
27pub struct InMemoryDataService<T: Data> {
28    data: Arc<RwLock<HashMap<Uuid, T>>>,
29}
30
31impl<T: Data> InMemoryDataService<T> {
32    /// Create a new empty in-memory data service
33    pub fn new() -> Self {
34        Self {
35            data: Arc::new(RwLock::new(HashMap::new())),
36        }
37    }
38}
39
40impl<T: Data> Clone for InMemoryDataService<T> {
41    fn clone(&self) -> Self {
42        Self {
43            data: Arc::clone(&self.data),
44        }
45    }
46}
47
48impl<T: Data> Default for InMemoryDataService<T> {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54#[async_trait]
55impl<T: Data> DataService<T> for InMemoryDataService<T> {
56    async fn create(&self, entity: T) -> Result<T> {
57        let mut data = self
58            .data
59            .write()
60            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
61
62        data.insert(entity.id(), entity.clone());
63
64        Ok(entity)
65    }
66
67    async fn get(&self, id: &Uuid) -> Result<Option<T>> {
68        let data = self
69            .data
70            .read()
71            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
72
73        Ok(data.get(id).cloned())
74    }
75
76    async fn list(&self) -> Result<Vec<T>> {
77        let data = self
78            .data
79            .read()
80            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
81
82        Ok(data.values().cloned().collect())
83    }
84
85    async fn update(&self, id: &Uuid, entity: T) -> Result<T> {
86        let mut data = self
87            .data
88            .write()
89            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
90
91        data.get(id)
92            .ok_or_else(|| anyhow!("Entity not found: {}", id))?;
93
94        data.insert(*id, entity.clone());
95
96        Ok(entity)
97    }
98
99    async fn delete(&self, id: &Uuid) -> Result<()> {
100        let mut data = self
101            .data
102            .write()
103            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
104
105        data.remove(id);
106
107        Ok(())
108    }
109
110    async fn search(&self, field: &str, value: &str) -> Result<Vec<T>> {
111        let data = self
112            .data
113            .read()
114            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
115
116        Ok(data
117            .values()
118            .filter(|entity| {
119                entity.field_value(field).is_some_and(|fv| match &fv {
120                    FieldValue::String(s) => s == value,
121                    FieldValue::Integer(i) => i.to_string() == value,
122                    FieldValue::Float(f) => f.to_string() == value,
123                    FieldValue::Boolean(b) => b.to_string() == value,
124                    FieldValue::Uuid(u) => u.to_string() == value,
125                    FieldValue::DateTime(dt) => dt.to_rfc3339() == value,
126                    FieldValue::Null => false,
127                })
128            })
129            .cloned()
130            .collect())
131    }
132}
133
134// ---------------------------------------------------------------------------
135// LinkService
136// ---------------------------------------------------------------------------
137
138/// In-memory link service implementation
139///
140/// Useful for testing and development. Uses RwLock for thread-safe access.
141#[derive(Clone)]
142pub struct InMemoryLinkService {
143    links: Arc<RwLock<HashMap<Uuid, LinkEntity>>>,
144}
145
146impl InMemoryLinkService {
147    /// Create a new in-memory link service
148    pub fn new() -> Self {
149        Self {
150            links: Arc::new(RwLock::new(HashMap::new())),
151        }
152    }
153}
154
155impl Default for InMemoryLinkService {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161#[async_trait]
162impl LinkService for InMemoryLinkService {
163    async fn create(&self, link: LinkEntity) -> Result<LinkEntity> {
164        let mut links = self
165            .links
166            .write()
167            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
168
169        links.insert(link.id, link.clone());
170
171        Ok(link)
172    }
173
174    async fn get(&self, id: &Uuid) -> Result<Option<LinkEntity>> {
175        let links = self
176            .links
177            .read()
178            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
179
180        Ok(links.get(id).cloned())
181    }
182
183    async fn list(&self) -> Result<Vec<LinkEntity>> {
184        let links = self
185            .links
186            .read()
187            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
188
189        Ok(links.values().cloned().collect())
190    }
191
192    async fn find_by_source(
193        &self,
194        source_id: &Uuid,
195        link_type: Option<&str>,
196        target_type: Option<&str>,
197    ) -> Result<Vec<LinkEntity>> {
198        let links = self
199            .links
200            .read()
201            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
202
203        Ok(links
204            .values()
205            .filter(|link| {
206                &link.source_id == source_id
207                    && link_type.is_none_or(|lt| link.link_type == lt)
208                    && target_type.is_none_or(|_tt| true) // TODO: Add target type to Link if needed
209            })
210            .cloned()
211            .collect())
212    }
213
214    async fn find_by_target(
215        &self,
216        target_id: &Uuid,
217        link_type: Option<&str>,
218        source_type: Option<&str>,
219    ) -> Result<Vec<LinkEntity>> {
220        let links = self
221            .links
222            .read()
223            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
224
225        Ok(links
226            .values()
227            .filter(|link| {
228                &link.target_id == target_id
229                    && link_type.is_none_or(|lt| link.link_type == lt)
230                    && source_type.is_none_or(|_st| true) // TODO: Add source type to Link if needed
231            })
232            .cloned()
233            .collect())
234    }
235
236    async fn update(&self, id: &Uuid, updated_link: LinkEntity) -> Result<LinkEntity> {
237        let mut links = self
238            .links
239            .write()
240            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
241
242        links.get_mut(id).ok_or_else(|| anyhow!("Link not found"))?;
243
244        links.insert(*id, updated_link.clone());
245
246        Ok(updated_link)
247    }
248
249    async fn delete(&self, id: &Uuid) -> Result<()> {
250        let mut links = self
251            .links
252            .write()
253            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
254
255        links.remove(id);
256
257        Ok(())
258    }
259
260    async fn delete_by_entity(&self, entity_id: &Uuid) -> Result<()> {
261        let mut links = self
262            .links
263            .write()
264            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
265
266        links.retain(|_, link| &link.source_id != entity_id && &link.target_id != entity_id);
267
268        Ok(())
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use crate::core::entity::Entity;
276    use crate::core::field::FieldValue;
277    use chrono::{DateTime, Utc};
278
279    // -----------------------------------------------------------------------
280    // Test entity for InMemoryDataService tests
281    // -----------------------------------------------------------------------
282
283    #[derive(Clone, Debug, PartialEq)]
284    struct TestDataEntity {
285        id: Uuid,
286        entity_name: String,
287        status: String,
288        created_at: DateTime<Utc>,
289        updated_at: DateTime<Utc>,
290    }
291
292    impl TestDataEntity {
293        fn new(name: &str) -> Self {
294            let now = Utc::now();
295            Self {
296                id: Uuid::new_v4(),
297                entity_name: name.to_string(),
298                status: "active".to_string(),
299                created_at: now,
300                updated_at: now,
301            }
302        }
303    }
304
305    impl Entity for TestDataEntity {
306        type Service = ();
307
308        fn resource_name() -> &'static str {
309            "test_data_entities"
310        }
311
312        fn resource_name_singular() -> &'static str {
313            "test_data_entity"
314        }
315
316        fn service_from_host(
317            _: &std::sync::Arc<dyn std::any::Any + Send + Sync>,
318        ) -> anyhow::Result<std::sync::Arc<Self::Service>> {
319            Ok(std::sync::Arc::new(()))
320        }
321
322        fn id(&self) -> Uuid {
323            self.id
324        }
325
326        fn entity_type(&self) -> &str {
327            "test_data"
328        }
329
330        fn created_at(&self) -> DateTime<Utc> {
331            self.created_at
332        }
333
334        fn updated_at(&self) -> DateTime<Utc> {
335            self.updated_at
336        }
337
338        fn deleted_at(&self) -> Option<DateTime<Utc>> {
339            None
340        }
341
342        fn status(&self) -> &str {
343            &self.status
344        }
345    }
346
347    impl crate::core::Data for TestDataEntity {
348        fn name(&self) -> &str {
349            &self.entity_name
350        }
351
352        fn indexed_fields() -> &'static [&'static str] {
353            &["entity_name", "status"]
354        }
355
356        fn field_value(&self, field: &str) -> Option<FieldValue> {
357            match field {
358                "entity_name" => Some(FieldValue::String(self.entity_name.clone())),
359                "status" => Some(FieldValue::String(self.status.clone())),
360                _ => None,
361            }
362        }
363    }
364
365    // -----------------------------------------------------------------------
366    // InMemoryDataService CRUD tests
367    // -----------------------------------------------------------------------
368
369    #[tokio::test]
370    async fn test_data_create_entity() {
371        let service = InMemoryDataService::<TestDataEntity>::new();
372        let entity = TestDataEntity::new("Alice");
373
374        let created = service.create(entity.clone()).await.unwrap();
375        assert_eq!(created.id, entity.id);
376        assert_eq!(created.entity_name, "Alice");
377    }
378
379    #[tokio::test]
380    async fn test_data_get_entity() {
381        let service = InMemoryDataService::<TestDataEntity>::new();
382        let entity = TestDataEntity::new("Bob");
383
384        service.create(entity.clone()).await.unwrap();
385
386        let retrieved = service.get(&entity.id).await.unwrap();
387        assert!(retrieved.is_some());
388        assert_eq!(retrieved.unwrap().entity_name, "Bob");
389    }
390
391    #[tokio::test]
392    async fn test_data_get_nonexistent() {
393        let service = InMemoryDataService::<TestDataEntity>::new();
394
395        let retrieved = service.get(&Uuid::new_v4()).await.unwrap();
396        assert!(retrieved.is_none());
397    }
398
399    #[tokio::test]
400    async fn test_data_list_entities() {
401        let service = InMemoryDataService::<TestDataEntity>::new();
402
403        service.create(TestDataEntity::new("Alice")).await.unwrap();
404        service.create(TestDataEntity::new("Bob")).await.unwrap();
405        service
406            .create(TestDataEntity::new("Charlie"))
407            .await
408            .unwrap();
409
410        let all = service.list().await.unwrap();
411        assert_eq!(all.len(), 3);
412    }
413
414    #[tokio::test]
415    async fn test_data_list_empty() {
416        let service = InMemoryDataService::<TestDataEntity>::new();
417
418        let all = service.list().await.unwrap();
419        assert!(all.is_empty());
420    }
421
422    #[tokio::test]
423    async fn test_data_update_entity() {
424        let service = InMemoryDataService::<TestDataEntity>::new();
425        let mut entity = TestDataEntity::new("Alice");
426
427        service.create(entity.clone()).await.unwrap();
428
429        entity.entity_name = "Alice Updated".to_string();
430        let updated = service.update(&entity.id, entity.clone()).await.unwrap();
431
432        assert_eq!(updated.entity_name, "Alice Updated");
433
434        // Verify persisted
435        let retrieved = service.get(&entity.id).await.unwrap().unwrap();
436        assert_eq!(retrieved.entity_name, "Alice Updated");
437    }
438
439    #[tokio::test]
440    async fn test_data_update_nonexistent() {
441        let service = InMemoryDataService::<TestDataEntity>::new();
442        let entity = TestDataEntity::new("Ghost");
443        let id = entity.id;
444
445        let result = service.update(&id, entity).await;
446        assert!(result.is_err());
447        assert!(result.unwrap_err().to_string().contains("not found"));
448    }
449
450    #[tokio::test]
451    async fn test_data_delete_entity() {
452        let service = InMemoryDataService::<TestDataEntity>::new();
453        let entity = TestDataEntity::new("Alice");
454
455        service.create(entity.clone()).await.unwrap();
456        assert!(service.get(&entity.id).await.unwrap().is_some());
457
458        service.delete(&entity.id).await.unwrap();
459        assert!(service.get(&entity.id).await.unwrap().is_none());
460    }
461
462    #[tokio::test]
463    async fn test_data_delete_nonexistent() {
464        let service = InMemoryDataService::<TestDataEntity>::new();
465
466        // Deleting a nonexistent entity should succeed silently
467        let result = service.delete(&Uuid::new_v4()).await;
468        assert!(result.is_ok());
469    }
470
471    // -----------------------------------------------------------------------
472    // InMemoryDataService search tests
473    // -----------------------------------------------------------------------
474
475    #[tokio::test]
476    async fn test_data_search_by_indexed_field() {
477        let service = InMemoryDataService::<TestDataEntity>::new();
478
479        service.create(TestDataEntity::new("Alice")).await.unwrap();
480        service.create(TestDataEntity::new("Bob")).await.unwrap();
481        service.create(TestDataEntity::new("Alice")).await.unwrap();
482
483        let results = service.search("entity_name", "Alice").await.unwrap();
484        assert_eq!(results.len(), 2);
485        assert!(results.iter().all(|e| e.entity_name == "Alice"));
486    }
487
488    #[tokio::test]
489    async fn test_data_search_no_results() {
490        let service = InMemoryDataService::<TestDataEntity>::new();
491
492        service.create(TestDataEntity::new("Alice")).await.unwrap();
493
494        let results = service.search("entity_name", "Zara").await.unwrap();
495        assert!(results.is_empty());
496    }
497
498    #[tokio::test]
499    async fn test_data_search_by_status() {
500        let service = InMemoryDataService::<TestDataEntity>::new();
501
502        let mut inactive = TestDataEntity::new("Inactive");
503        inactive.status = "inactive".to_string();
504
505        service.create(TestDataEntity::new("Active")).await.unwrap();
506        service.create(inactive).await.unwrap();
507
508        let results = service.search("status", "inactive").await.unwrap();
509        assert_eq!(results.len(), 1);
510        assert_eq!(results[0].entity_name, "Inactive");
511    }
512
513    #[tokio::test]
514    async fn test_data_search_unknown_field() {
515        let service = InMemoryDataService::<TestDataEntity>::new();
516
517        service.create(TestDataEntity::new("Alice")).await.unwrap();
518
519        // Unknown field returns no results (field_value returns None)
520        let results = service
521            .search("nonexistent_field", "anything")
522            .await
523            .unwrap();
524        assert!(results.is_empty());
525    }
526
527    #[tokio::test]
528    async fn test_data_clone_shares_state() {
529        let service = InMemoryDataService::<TestDataEntity>::new();
530        let cloned = service.clone();
531
532        service.create(TestDataEntity::new("Alice")).await.unwrap();
533
534        // Clone shares the same backing store
535        let all = cloned.list().await.unwrap();
536        assert_eq!(all.len(), 1);
537    }
538
539    // -----------------------------------------------------------------------
540    // InMemoryLinkService tests (existing)
541    // -----------------------------------------------------------------------
542
543    #[tokio::test]
544    async fn test_create_link() {
545        let service = InMemoryLinkService::new();
546        let user_id = Uuid::new_v4();
547        let car_id = Uuid::new_v4();
548
549        let link = LinkEntity::new("owner", user_id, car_id, None);
550
551        let created = service.create(link.clone()).await.unwrap();
552
553        assert_eq!(created.link_type, "owner");
554        assert_eq!(created.source_id, user_id);
555        assert_eq!(created.target_id, car_id);
556    }
557
558    #[tokio::test]
559    async fn test_get_link() {
560        let service = InMemoryLinkService::new();
561        let link = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
562
563        service.create(link.clone()).await.unwrap();
564
565        let retrieved = service.get(&link.id).await.unwrap();
566        assert!(retrieved.is_some());
567        assert_eq!(retrieved.unwrap().id, link.id);
568    }
569
570    #[tokio::test]
571    async fn test_list_links() {
572        let service = InMemoryLinkService::new();
573
574        let link1 = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
575        let link2 = LinkEntity::new("driver", Uuid::new_v4(), Uuid::new_v4(), None);
576
577        service.create(link1).await.unwrap();
578        service.create(link2).await.unwrap();
579
580        let links = service.list().await.unwrap();
581        assert_eq!(links.len(), 2);
582    }
583
584    #[tokio::test]
585    async fn test_find_by_source() {
586        let service = InMemoryLinkService::new();
587        let user_id = Uuid::new_v4();
588        let car1_id = Uuid::new_v4();
589        let car2_id = Uuid::new_v4();
590
591        // User owns car1
592        service
593            .create(LinkEntity::new("owner", user_id, car1_id, None))
594            .await
595            .unwrap();
596
597        // User drives car2
598        service
599            .create(LinkEntity::new("driver", user_id, car2_id, None))
600            .await
601            .unwrap();
602
603        // Find all links from user
604        let links = service.find_by_source(&user_id, None, None).await.unwrap();
605        assert_eq!(links.len(), 2);
606
607        // Find only owner links
608        let owner_links = service
609            .find_by_source(&user_id, Some("owner"), None)
610            .await
611            .unwrap();
612        assert_eq!(owner_links.len(), 1);
613        assert_eq!(owner_links[0].link_type, "owner");
614    }
615
616    #[tokio::test]
617    async fn test_find_by_target() {
618        let service = InMemoryLinkService::new();
619        let user1_id = Uuid::new_v4();
620        let user2_id = Uuid::new_v4();
621        let car_id = Uuid::new_v4();
622
623        // User1 owns car
624        service
625            .create(LinkEntity::new("owner", user1_id, car_id, None))
626            .await
627            .unwrap();
628
629        // User2 drives car
630        service
631            .create(LinkEntity::new("driver", user2_id, car_id, None))
632            .await
633            .unwrap();
634
635        // Find all links to car
636        let links = service.find_by_target(&car_id, None, None).await.unwrap();
637        assert_eq!(links.len(), 2);
638
639        // Find only driver links
640        let driver_links = service
641            .find_by_target(&car_id, Some("driver"), None)
642            .await
643            .unwrap();
644        assert_eq!(driver_links.len(), 1);
645        assert_eq!(driver_links[0].link_type, "driver");
646    }
647
648    #[tokio::test]
649    async fn test_update_link() {
650        let service = InMemoryLinkService::new();
651        let user_id = Uuid::new_v4();
652        let company_id = Uuid::new_v4();
653
654        let mut link = LinkEntity::new(
655            "worker",
656            user_id,
657            company_id,
658            Some(serde_json::json!({"role": "Developer"})),
659        );
660
661        service.create(link.clone()).await.unwrap();
662
663        // Update metadata
664        link.metadata = Some(serde_json::json!({"role": "Senior Developer"}));
665        link.touch();
666
667        let updated = service.update(&link.id, link.clone()).await.unwrap();
668        assert_eq!(
669            updated.metadata,
670            Some(serde_json::json!({"role": "Senior Developer"}))
671        );
672    }
673
674    #[tokio::test]
675    async fn test_delete_link() {
676        let service = InMemoryLinkService::new();
677        let link = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
678
679        service.create(link.clone()).await.unwrap();
680
681        let retrieved = service.get(&link.id).await.unwrap();
682        assert!(retrieved.is_some());
683
684        service.delete(&link.id).await.unwrap();
685
686        let retrieved = service.get(&link.id).await.unwrap();
687        assert!(retrieved.is_none());
688    }
689
690    #[tokio::test]
691    async fn test_delete_by_entity() {
692        let service = InMemoryLinkService::new();
693        let user_id = Uuid::new_v4();
694        let car1_id = Uuid::new_v4();
695        let car2_id = Uuid::new_v4();
696
697        service
698            .create(LinkEntity::new("owner", user_id, car1_id, None))
699            .await
700            .unwrap();
701        service
702            .create(LinkEntity::new("driver", user_id, car2_id, None))
703            .await
704            .unwrap();
705        service
706            .create(LinkEntity::new("owner", Uuid::new_v4(), car1_id, None))
707            .await
708            .unwrap();
709
710        let links = service.list().await.unwrap();
711        assert_eq!(links.len(), 3);
712
713        // Delete all links involving user_id
714        service.delete_by_entity(&user_id).await.unwrap();
715
716        let remaining = service.list().await.unwrap();
717        assert_eq!(remaining.len(), 1);
718        assert_ne!(remaining[0].source_id, user_id);
719        assert_ne!(remaining[0].target_id, user_id);
720    }
721}