this/storage/
in_memory.rs

1//! In-memory implementation of LinkService for testing and development
2
3use crate::core::{link::LinkEntity, LinkService};
4use anyhow::{anyhow, Result};
5use async_trait::async_trait;
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8use uuid::Uuid;
9
10/// In-memory link service implementation
11///
12/// Useful for testing and development. Uses RwLock for thread-safe access.
13#[derive(Clone)]
14pub struct InMemoryLinkService {
15    links: Arc<RwLock<HashMap<Uuid, LinkEntity>>>,
16}
17
18impl InMemoryLinkService {
19    /// Create a new in-memory link service
20    pub fn new() -> Self {
21        Self {
22            links: Arc::new(RwLock::new(HashMap::new())),
23        }
24    }
25}
26
27impl Default for InMemoryLinkService {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33#[async_trait]
34impl LinkService for InMemoryLinkService {
35    async fn create(&self, link: LinkEntity) -> Result<LinkEntity> {
36        let mut links = self
37            .links
38            .write()
39            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
40
41        links.insert(link.id, link.clone());
42
43        Ok(link)
44    }
45
46    async fn get(&self, id: &Uuid) -> Result<Option<LinkEntity>> {
47        let links = self
48            .links
49            .read()
50            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
51
52        Ok(links.get(id).cloned())
53    }
54
55    async fn list(&self) -> Result<Vec<LinkEntity>> {
56        let links = self
57            .links
58            .read()
59            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
60
61        Ok(links.values().cloned().collect())
62    }
63
64    async fn find_by_source(
65        &self,
66        source_id: &Uuid,
67        link_type: Option<&str>,
68        target_type: Option<&str>,
69    ) -> Result<Vec<LinkEntity>> {
70        let links = self
71            .links
72            .read()
73            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
74
75        Ok(links
76            .values()
77            .filter(|link| {
78                &link.source_id == source_id
79                    && link_type.is_none_or(|lt| link.link_type == lt)
80                    && target_type.is_none_or(|_tt| true) // TODO: Add target type to Link if needed
81            })
82            .cloned()
83            .collect())
84    }
85
86    async fn find_by_target(
87        &self,
88        target_id: &Uuid,
89        link_type: Option<&str>,
90        source_type: Option<&str>,
91    ) -> Result<Vec<LinkEntity>> {
92        let links = self
93            .links
94            .read()
95            .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
96
97        Ok(links
98            .values()
99            .filter(|link| {
100                &link.target_id == target_id
101                    && link_type.is_none_or(|lt| link.link_type == lt)
102                    && source_type.is_none_or(|_st| true) // TODO: Add source type to Link if needed
103            })
104            .cloned()
105            .collect())
106    }
107
108    async fn update(&self, id: &Uuid, updated_link: LinkEntity) -> Result<LinkEntity> {
109        let mut links = self
110            .links
111            .write()
112            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
113
114        links.get_mut(id).ok_or_else(|| anyhow!("Link not found"))?;
115
116        links.insert(*id, updated_link.clone());
117
118        Ok(updated_link)
119    }
120
121    async fn delete(&self, id: &Uuid) -> Result<()> {
122        let mut links = self
123            .links
124            .write()
125            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
126
127        links.remove(id);
128
129        Ok(())
130    }
131
132    async fn delete_by_entity(&self, entity_id: &Uuid) -> Result<()> {
133        let mut links = self
134            .links
135            .write()
136            .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
137
138        links.retain(|_, link| &link.source_id != entity_id && &link.target_id != entity_id);
139
140        Ok(())
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[tokio::test]
149    async fn test_create_link() {
150        let service = InMemoryLinkService::new();
151        let user_id = Uuid::new_v4();
152        let car_id = Uuid::new_v4();
153
154        let link = LinkEntity::new("owner", user_id, car_id, None);
155
156        let created = service.create(link.clone()).await.unwrap();
157
158        assert_eq!(created.link_type, "owner");
159        assert_eq!(created.source_id, user_id);
160        assert_eq!(created.target_id, car_id);
161    }
162
163    #[tokio::test]
164    async fn test_get_link() {
165        let service = InMemoryLinkService::new();
166        let link = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
167
168        service.create(link.clone()).await.unwrap();
169
170        let retrieved = service.get(&link.id).await.unwrap();
171        assert!(retrieved.is_some());
172        assert_eq!(retrieved.unwrap().id, link.id);
173    }
174
175    #[tokio::test]
176    async fn test_list_links() {
177        let service = InMemoryLinkService::new();
178
179        let link1 = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
180        let link2 = LinkEntity::new("driver", Uuid::new_v4(), Uuid::new_v4(), None);
181
182        service.create(link1).await.unwrap();
183        service.create(link2).await.unwrap();
184
185        let links = service.list().await.unwrap();
186        assert_eq!(links.len(), 2);
187    }
188
189    #[tokio::test]
190    async fn test_find_by_source() {
191        let service = InMemoryLinkService::new();
192        let user_id = Uuid::new_v4();
193        let car1_id = Uuid::new_v4();
194        let car2_id = Uuid::new_v4();
195
196        // User owns car1
197        service
198            .create(LinkEntity::new("owner", user_id, car1_id, None))
199            .await
200            .unwrap();
201
202        // User drives car2
203        service
204            .create(LinkEntity::new("driver", user_id, car2_id, None))
205            .await
206            .unwrap();
207
208        // Find all links from user
209        let links = service.find_by_source(&user_id, None, None).await.unwrap();
210        assert_eq!(links.len(), 2);
211
212        // Find only owner links
213        let owner_links = service
214            .find_by_source(&user_id, Some("owner"), None)
215            .await
216            .unwrap();
217        assert_eq!(owner_links.len(), 1);
218        assert_eq!(owner_links[0].link_type, "owner");
219    }
220
221    #[tokio::test]
222    async fn test_find_by_target() {
223        let service = InMemoryLinkService::new();
224        let user1_id = Uuid::new_v4();
225        let user2_id = Uuid::new_v4();
226        let car_id = Uuid::new_v4();
227
228        // User1 owns car
229        service
230            .create(LinkEntity::new("owner", user1_id, car_id, None))
231            .await
232            .unwrap();
233
234        // User2 drives car
235        service
236            .create(LinkEntity::new("driver", user2_id, car_id, None))
237            .await
238            .unwrap();
239
240        // Find all links to car
241        let links = service.find_by_target(&car_id, None, None).await.unwrap();
242        assert_eq!(links.len(), 2);
243
244        // Find only driver links
245        let driver_links = service
246            .find_by_target(&car_id, Some("driver"), None)
247            .await
248            .unwrap();
249        assert_eq!(driver_links.len(), 1);
250        assert_eq!(driver_links[0].link_type, "driver");
251    }
252
253    #[tokio::test]
254    async fn test_update_link() {
255        let service = InMemoryLinkService::new();
256        let user_id = Uuid::new_v4();
257        let company_id = Uuid::new_v4();
258
259        let mut link = LinkEntity::new(
260            "worker",
261            user_id,
262            company_id,
263            Some(serde_json::json!({"role": "Developer"})),
264        );
265
266        service.create(link.clone()).await.unwrap();
267
268        // Update metadata
269        link.metadata = Some(serde_json::json!({"role": "Senior Developer"}));
270        link.touch();
271
272        let updated = service.update(&link.id, link.clone()).await.unwrap();
273        assert_eq!(
274            updated.metadata,
275            Some(serde_json::json!({"role": "Senior Developer"}))
276        );
277    }
278
279    #[tokio::test]
280    async fn test_delete_link() {
281        let service = InMemoryLinkService::new();
282        let link = LinkEntity::new("owner", Uuid::new_v4(), Uuid::new_v4(), None);
283
284        service.create(link.clone()).await.unwrap();
285
286        let retrieved = service.get(&link.id).await.unwrap();
287        assert!(retrieved.is_some());
288
289        service.delete(&link.id).await.unwrap();
290
291        let retrieved = service.get(&link.id).await.unwrap();
292        assert!(retrieved.is_none());
293    }
294
295    #[tokio::test]
296    async fn test_delete_by_entity() {
297        let service = InMemoryLinkService::new();
298        let user_id = Uuid::new_v4();
299        let car1_id = Uuid::new_v4();
300        let car2_id = Uuid::new_v4();
301
302        service
303            .create(LinkEntity::new("owner", user_id, car1_id, None))
304            .await
305            .unwrap();
306        service
307            .create(LinkEntity::new("driver", user_id, car2_id, None))
308            .await
309            .unwrap();
310        service
311            .create(LinkEntity::new("owner", Uuid::new_v4(), car1_id, None))
312            .await
313            .unwrap();
314
315        let links = service.list().await.unwrap();
316        assert_eq!(links.len(), 3);
317
318        // Delete all links involving user_id
319        service.delete_by_entity(&user_id).await.unwrap();
320
321        let remaining = service.list().await.unwrap();
322        assert_eq!(remaining.len(), 1);
323        assert_ne!(remaining[0].source_id, user_id);
324        assert_ne!(remaining[0].target_id, user_id);
325    }
326}