1use crate::core::{EntityReference, Link, LinkService};
4use anyhow::{anyhow, Result};
5use async_trait::async_trait;
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8use uuid::Uuid;
9
10#[derive(Clone)]
14pub struct InMemoryLinkService {
15 links: Arc<RwLock<HashMap<Uuid, Link>>>,
16}
17
18impl InMemoryLinkService {
19 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(
36 &self,
37 tenant_id: &Uuid,
38 link_type: &str,
39 source: EntityReference,
40 target: EntityReference,
41 metadata: Option<serde_json::Value>,
42 ) -> Result<Link> {
43 let link = Link::new(*tenant_id, link_type, source, target, metadata);
44
45 let mut links = self
46 .links
47 .write()
48 .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
49
50 links.insert(link.id, link.clone());
51
52 Ok(link)
53 }
54
55 async fn get(&self, tenant_id: &Uuid, id: &Uuid) -> Result<Option<Link>> {
56 let links = self
57 .links
58 .read()
59 .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
60
61 Ok(links
62 .get(id)
63 .filter(|link| &link.tenant_id == tenant_id)
64 .cloned())
65 }
66
67 async fn list(&self, tenant_id: &Uuid) -> Result<Vec<Link>> {
68 let links = self
69 .links
70 .read()
71 .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
72
73 Ok(links
74 .values()
75 .filter(|link| &link.tenant_id == tenant_id)
76 .cloned()
77 .collect())
78 }
79
80 async fn find_by_source(
81 &self,
82 tenant_id: &Uuid,
83 source_id: &Uuid,
84 source_type: &str,
85 link_type: Option<&str>,
86 target_type: Option<&str>,
87 ) -> Result<Vec<Link>> {
88 let links = self
89 .links
90 .read()
91 .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
92
93 Ok(links
94 .values()
95 .filter(|link| {
96 &link.tenant_id == tenant_id
97 && &link.source.id == source_id
98 && link.source.entity_type == source_type
99 && link_type.is_none_or(|lt| link.link_type == lt)
100 && target_type.is_none_or(|tt| link.target.entity_type == tt)
101 })
102 .cloned()
103 .collect())
104 }
105
106 async fn find_by_target(
107 &self,
108 tenant_id: &Uuid,
109 target_id: &Uuid,
110 target_type: &str,
111 link_type: Option<&str>,
112 source_type: Option<&str>,
113 ) -> Result<Vec<Link>> {
114 let links = self
115 .links
116 .read()
117 .map_err(|e| anyhow!("Failed to acquire read lock: {}", e))?;
118
119 Ok(links
120 .values()
121 .filter(|link| {
122 &link.tenant_id == tenant_id
123 && &link.target.id == target_id
124 && link.target.entity_type == target_type
125 && link_type.is_none_or(|lt| link.link_type == lt)
126 && source_type.is_none_or(|st| link.source.entity_type == st)
127 })
128 .cloned()
129 .collect())
130 }
131
132 async fn update(
133 &self,
134 tenant_id: &Uuid,
135 id: &Uuid,
136 metadata: Option<serde_json::Value>,
137 ) -> Result<Link> {
138 let mut links = self
139 .links
140 .write()
141 .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
142
143 let link = links.get_mut(id).ok_or_else(|| anyhow!("Link not found"))?;
144
145 if &link.tenant_id != tenant_id {
147 return Err(anyhow!("Link not found or access denied"));
148 }
149
150 link.metadata = metadata;
152 link.updated_at = chrono::Utc::now();
153
154 Ok(link.clone())
155 }
156
157 async fn delete(&self, tenant_id: &Uuid, id: &Uuid) -> Result<()> {
158 let mut links = self
159 .links
160 .write()
161 .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
162
163 if let Some(link) = links.get(id) {
164 if &link.tenant_id != tenant_id {
165 return Err(anyhow!("Link not found or access denied"));
166 }
167 links.remove(id);
168 }
169
170 Ok(())
171 }
172
173 async fn delete_by_entity(
174 &self,
175 tenant_id: &Uuid,
176 entity_id: &Uuid,
177 entity_type: &str,
178 ) -> Result<()> {
179 let mut links = self
180 .links
181 .write()
182 .map_err(|e| anyhow!("Failed to acquire write lock: {}", e))?;
183
184 links.retain(|_, link| {
185 &link.tenant_id != tenant_id
186 || (&link.source.id != entity_id || link.source.entity_type != entity_type)
187 && (&link.target.id != entity_id || link.target.entity_type != entity_type)
188 });
189
190 Ok(())
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[tokio::test]
199 async fn test_create_link() {
200 let service = InMemoryLinkService::new();
201 let tenant_id = Uuid::new_v4();
202 let user_id = Uuid::new_v4();
203 let car_id = Uuid::new_v4();
204
205 let link = service
206 .create(
207 &tenant_id,
208 "owner",
209 EntityReference::new(user_id, "user"),
210 EntityReference::new(car_id, "car"),
211 None,
212 )
213 .await
214 .unwrap();
215
216 assert_eq!(link.tenant_id, tenant_id);
217 assert_eq!(link.link_type, "owner");
218 assert_eq!(link.source.id, user_id);
219 assert_eq!(link.target.id, car_id);
220 }
221
222 #[tokio::test]
223 async fn test_find_by_source() {
224 let service = InMemoryLinkService::new();
225 let tenant_id = Uuid::new_v4();
226 let user_id = Uuid::new_v4();
227 let car1_id = Uuid::new_v4();
228 let car2_id = Uuid::new_v4();
229
230 service
232 .create(
233 &tenant_id,
234 "owner",
235 EntityReference::new(user_id, "user"),
236 EntityReference::new(car1_id, "car"),
237 None,
238 )
239 .await
240 .unwrap();
241
242 service
244 .create(
245 &tenant_id,
246 "driver",
247 EntityReference::new(user_id, "user"),
248 EntityReference::new(car2_id, "car"),
249 None,
250 )
251 .await
252 .unwrap();
253
254 let links = service
256 .find_by_source(&tenant_id, &user_id, "user", None, None)
257 .await
258 .unwrap();
259
260 assert_eq!(links.len(), 2);
261
262 let owner_links = service
264 .find_by_source(&tenant_id, &user_id, "user", Some("owner"), None)
265 .await
266 .unwrap();
267
268 assert_eq!(owner_links.len(), 1);
269 assert_eq!(owner_links[0].link_type, "owner");
270 }
271
272 #[tokio::test]
273 async fn test_update_link() {
274 let service = InMemoryLinkService::new();
275 let tenant_id = Uuid::new_v4();
276 let user_id = Uuid::new_v4();
277 let company_id = Uuid::new_v4();
278
279 let initial_metadata = serde_json::json!({
281 "role": "Developer",
282 "start_date": "2024-01-01"
283 });
284
285 let link = service
286 .create(
287 &tenant_id,
288 "worker",
289 EntityReference::new(user_id, "user"),
290 EntityReference::new(company_id, "company"),
291 Some(initial_metadata.clone()),
292 )
293 .await
294 .unwrap();
295
296 assert_eq!(link.metadata, Some(initial_metadata));
297
298 let updated_metadata = serde_json::json!({
300 "role": "Senior Developer",
301 "start_date": "2024-01-01",
302 "promotion_date": "2024-06-01"
303 });
304
305 let updated_link = service
306 .update(&tenant_id, &link.id, Some(updated_metadata.clone()))
307 .await
308 .unwrap();
309
310 assert_eq!(updated_link.metadata, Some(updated_metadata.clone()));
311 assert_ne!(updated_link.updated_at, link.updated_at);
312
313 let fetched = service.get(&tenant_id, &link.id).await.unwrap();
315 assert!(fetched.is_some());
316 assert_eq!(fetched.unwrap().metadata, Some(updated_metadata));
317 }
318
319 #[tokio::test]
320 async fn test_update_link_removes_metadata() {
321 let service = InMemoryLinkService::new();
322 let tenant_id = Uuid::new_v4();
323 let user_id = Uuid::new_v4();
324 let car_id = Uuid::new_v4();
325
326 let link = service
328 .create(
329 &tenant_id,
330 "owner",
331 EntityReference::new(user_id, "user"),
332 EntityReference::new(car_id, "car"),
333 Some(serde_json::json!({"purchase_date": "2024-01-01"})),
334 )
335 .await
336 .unwrap();
337
338 assert!(link.metadata.is_some());
339
340 let updated = service.update(&tenant_id, &link.id, None).await.unwrap();
342
343 assert!(updated.metadata.is_none());
344 }
345
346 #[tokio::test]
347 async fn test_tenant_isolation() {
348 let service = InMemoryLinkService::new();
349 let tenant1_id = Uuid::new_v4();
350 let tenant2_id = Uuid::new_v4();
351 let user_id = Uuid::new_v4();
352 let car_id = Uuid::new_v4();
353
354 let link = service
356 .create(
357 &tenant1_id,
358 "owner",
359 EntityReference::new(user_id, "user"),
360 EntityReference::new(car_id, "car"),
361 None,
362 )
363 .await
364 .unwrap();
365
366 let result = service.get(&tenant1_id, &link.id).await.unwrap();
368 assert!(result.is_some());
369
370 let result = service.get(&tenant2_id, &link.id).await.unwrap();
372 assert!(result.is_none());
373 }
374
375 #[tokio::test]
376 async fn test_get_link_by_id() {
377 let service = InMemoryLinkService::new();
378 let tenant_id = Uuid::new_v4();
379 let user_id = Uuid::new_v4();
380 let company_id = Uuid::new_v4();
381
382 let link = service
384 .create(
385 &tenant_id,
386 "worker",
387 EntityReference::new(user_id, "user"),
388 EntityReference::new(company_id, "company"),
389 Some(serde_json::json!({ "role": "Developer" })),
390 )
391 .await
392 .unwrap();
393
394 let retrieved = service.get(&tenant_id, &link.id).await.unwrap();
396 assert!(retrieved.is_some());
397
398 let retrieved_link = retrieved.unwrap();
399 assert_eq!(retrieved_link.id, link.id);
400 assert_eq!(retrieved_link.link_type, "worker");
401 assert_eq!(retrieved_link.source.id, user_id);
402 assert_eq!(retrieved_link.target.id, company_id);
403 assert_eq!(
404 retrieved_link.metadata,
405 Some(serde_json::json!({ "role": "Developer" }))
406 );
407 }
408
409 #[tokio::test]
410 async fn test_get_nonexistent_link() {
411 let service = InMemoryLinkService::new();
412 let tenant_id = Uuid::new_v4();
413 let fake_id = Uuid::new_v4();
414
415 let result = service.get(&tenant_id, &fake_id).await.unwrap();
417 assert!(result.is_none());
418 }
419}