scim_server/storage/
in_memory.rs

1//! In-memory storage implementation for SCIM resources.
2//!
3//! This module provides a thread-safe in-memory implementation of the `StorageProvider`
4//! trait using HashMap and RwLock for concurrent access. It's designed for testing,
5//! development, and scenarios where persistence is not required.
6//!
7//! # Features
8//!
9//! * Thread-safe concurrent access with async RwLock
10//! * Automatic tenant isolation through hierarchical key structure
11//! * Efficient querying with attribute-based searches
12//! * Consistent ordering for list operations
13//! * No external dependencies beyond standard library
14//!
15//! # Performance Characteristics
16//!
17//! * PUT/GET/DELETE: O(1) average case
18//! * LIST with pagination: O(n) where n is total resources in prefix
19//! * FIND_BY_ATTRIBUTE: O(n) with JSON parsing overhead
20//! * EXISTS/COUNT: O(1) and O(n) respectively
21//!
22//! # Example Usage
23//!
24//! ```rust
25//! use scim_server::storage::{InMemoryStorage, StorageProvider, StorageKey};
26//! use serde_json::json;
27//!
28//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
29//! let storage = InMemoryStorage::new();
30//!
31//! // Store a user
32//! let key = StorageKey::new("tenant1", "User", "user123");
33//! let user_data = json!({
34//!     "id": "user123",
35//!     "userName": "john.doe",
36//!     "displayName": "John Doe",
37//!     "emails": [{"value": "john@example.com", "primary": true}]
38//! });
39//!
40//! let stored = storage.put(key.clone(), user_data).await?;
41//! println!("Stored: {}", stored);
42//!
43//! // Retrieve the user
44//! let retrieved = storage.get(key.clone()).await?;
45//! assert!(retrieved.is_some());
46//!
47//! // Search by email
48//! let prefix = StorageKey::prefix("tenant1", "User");
49//! let found = storage.find_by_attribute(prefix, "emails.0.value", "john@example.com").await?;
50//! assert_eq!(found.len(), 1);
51//!
52//! // Delete the user
53//! let was_deleted = storage.delete(key).await?;
54//! assert!(was_deleted);
55//! # Ok(())
56//! # }
57//! ```
58
59use crate::storage::{StorageError, StorageKey, StoragePrefix, StorageProvider, StorageStats};
60use serde_json::Value;
61use std::collections::HashMap;
62use std::sync::Arc;
63use tokio::sync::RwLock;
64
65/// Thread-safe in-memory storage implementation.
66///
67/// Uses a nested HashMap structure for efficient storage and retrieval:
68/// `tenant_id` → `resource_type` → `resource_id` → `data`
69///
70/// All operations are async and thread-safe using tokio's RwLock.
71#[derive(Clone)]
72pub struct InMemoryStorage {
73    // Structure: tenant_id -> resource_type -> resource_id -> data
74    data: Arc<RwLock<HashMap<String, HashMap<String, HashMap<String, Value>>>>>,
75}
76
77impl InMemoryStorage {
78    /// Create a new empty in-memory storage instance.
79    pub fn new() -> Self {
80        Self {
81            data: Arc::new(RwLock::new(HashMap::new())),
82        }
83    }
84
85    /// Extract a nested attribute value from JSON data using dot notation.
86    fn extract_attribute_value(data: &Value, attribute_path: &str) -> Option<String> {
87        let parts: Vec<&str> = attribute_path.split('.').collect();
88        let mut current = data;
89
90        for part in parts {
91            if let Ok(index) = part.parse::<usize>() {
92                // Array index
93                current = current.get(index)?;
94            } else {
95                // Object key
96                current = current.get(part)?;
97            }
98        }
99
100        // Convert the final value to string for comparison
101        match current {
102            Value::String(s) => Some(s.clone()),
103            Value::Number(n) => Some(n.to_string()),
104            Value::Bool(b) => Some(b.to_string()),
105            _ => current.as_str().map(|s| s.to_string()),
106        }
107    }
108}
109
110impl Default for InMemoryStorage {
111    fn default() -> Self {
112        Self::new()
113    }
114}
115
116impl StorageProvider for InMemoryStorage {
117    type Error = StorageError;
118
119    async fn put(&self, key: StorageKey, data: Value) -> Result<Value, Self::Error> {
120        let mut data_guard = self.data.write().await;
121
122        // Ensure the nested structure exists
123        let tenant_data = data_guard
124            .entry(key.tenant_id().to_string())
125            .or_insert_with(HashMap::new);
126
127        let type_data = tenant_data
128            .entry(key.resource_type().to_string())
129            .or_insert_with(HashMap::new);
130
131        // Store the data
132        type_data.insert(key.resource_id().to_string(), data.clone());
133
134        // Return the stored data (in this implementation, it's unchanged)
135        Ok(data)
136    }
137
138    async fn get(&self, key: StorageKey) -> Result<Option<Value>, Self::Error> {
139        let data_guard = self.data.read().await;
140
141        let result = data_guard
142            .get(key.tenant_id())
143            .and_then(|tenant_data| tenant_data.get(key.resource_type()))
144            .and_then(|type_data| type_data.get(key.resource_id()))
145            .cloned();
146
147        Ok(result)
148    }
149
150    async fn delete(&self, key: StorageKey) -> Result<bool, Self::Error> {
151        let mut data_guard = self.data.write().await;
152
153        let existed = if let Some(tenant_data) = data_guard.get_mut(key.tenant_id()) {
154            if let Some(type_data) = tenant_data.get_mut(key.resource_type()) {
155                type_data.remove(key.resource_id()).is_some()
156            } else {
157                false
158            }
159        } else {
160            false
161        };
162
163        Ok(existed)
164    }
165
166    async fn list(
167        &self,
168        prefix: StoragePrefix,
169        offset: usize,
170        limit: usize,
171    ) -> Result<Vec<(StorageKey, Value)>, Self::Error> {
172        if limit == 0 {
173            return Ok(Vec::new());
174        }
175
176        let data_guard = self.data.read().await;
177
178        let type_data = match data_guard
179            .get(prefix.tenant_id())
180            .and_then(|tenant_data| tenant_data.get(prefix.resource_type()))
181        {
182            Some(data) => data,
183            None => return Ok(Vec::new()),
184        };
185
186        // Collect and sort keys for consistent ordering
187        let mut keys: Vec<_> = type_data.keys().collect();
188        keys.sort();
189
190        // Apply pagination
191        let results: Vec<(StorageKey, Value)> = keys
192            .into_iter()
193            .skip(offset)
194            .take(limit)
195            .filter_map(|resource_id| {
196                type_data.get(resource_id).map(|data| {
197                    (
198                        StorageKey::new(prefix.tenant_id(), prefix.resource_type(), resource_id),
199                        data.clone(),
200                    )
201                })
202            })
203            .collect();
204
205        Ok(results)
206    }
207
208    async fn find_by_attribute(
209        &self,
210        prefix: StoragePrefix,
211        attribute: &str,
212        value: &str,
213    ) -> Result<Vec<(StorageKey, Value)>, Self::Error> {
214        let data_guard = self.data.read().await;
215
216        let type_data = match data_guard
217            .get(prefix.tenant_id())
218            .and_then(|tenant_data| tenant_data.get(prefix.resource_type()))
219        {
220            Some(data) => data,
221            None => return Ok(Vec::new()),
222        };
223
224        let mut results = Vec::new();
225
226        for (resource_id, resource_data) in type_data {
227            if let Some(attr_value) = Self::extract_attribute_value(resource_data, attribute) {
228                if attr_value == value {
229                    results.push((
230                        StorageKey::new(prefix.tenant_id(), prefix.resource_type(), resource_id),
231                        resource_data.clone(),
232                    ));
233                }
234            }
235        }
236
237        // Sort results by resource ID for consistency
238        results.sort_by(|a, b| a.0.resource_id().cmp(b.0.resource_id()));
239
240        Ok(results)
241    }
242
243    async fn exists(&self, key: StorageKey) -> Result<bool, Self::Error> {
244        let data_guard = self.data.read().await;
245
246        let exists = data_guard
247            .get(key.tenant_id())
248            .and_then(|tenant_data| tenant_data.get(key.resource_type()))
249            .and_then(|type_data| type_data.get(key.resource_id()))
250            .is_some();
251
252        Ok(exists)
253    }
254
255    async fn count(&self, prefix: StoragePrefix) -> Result<usize, Self::Error> {
256        let data_guard = self.data.read().await;
257
258        let count = data_guard
259            .get(prefix.tenant_id())
260            .and_then(|tenant_data| tenant_data.get(prefix.resource_type()))
261            .map(|type_data| type_data.len())
262            .unwrap_or(0);
263
264        Ok(count)
265    }
266
267    async fn list_tenants(&self) -> Result<Vec<String>, Self::Error> {
268        let data_guard = self.data.read().await;
269        Ok(data_guard.keys().cloned().collect())
270    }
271
272    async fn list_resource_types(&self, tenant_id: &str) -> Result<Vec<String>, Self::Error> {
273        let data_guard = self.data.read().await;
274        Ok(data_guard
275            .get(tenant_id)
276            .map(|tenant_data| tenant_data.keys().cloned().collect())
277            .unwrap_or_default())
278    }
279
280    async fn list_all_resource_types(&self) -> Result<Vec<String>, Self::Error> {
281        let data_guard = self.data.read().await;
282        let mut resource_types = std::collections::HashSet::new();
283
284        for tenant_data in data_guard.values() {
285            for resource_type in tenant_data.keys() {
286                resource_types.insert(resource_type.clone());
287            }
288        }
289
290        Ok(resource_types.into_iter().collect())
291    }
292
293    async fn clear(&self) -> Result<(), Self::Error> {
294        let mut data_guard = self.data.write().await;
295        data_guard.clear();
296        Ok(())
297    }
298
299    async fn stats(&self) -> Result<StorageStats, Self::Error> {
300        let data_guard = self.data.read().await;
301        let mut tenant_count = 0;
302        let mut resource_type_count = 0;
303        let mut total_resources = 0;
304
305        for (_, tenant_data) in data_guard.iter() {
306            tenant_count += 1;
307            for (_, type_data) in tenant_data.iter() {
308                resource_type_count += 1;
309                total_resources += type_data.len();
310            }
311        }
312
313        Ok(StorageStats {
314            tenant_count,
315            resource_type_count,
316            total_resources,
317        })
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324    use serde_json::json;
325
326    #[tokio::test]
327    async fn test_put_and_get() {
328        let storage = InMemoryStorage::new();
329        let key = StorageKey::new("tenant1", "User", "123");
330        let data = json!({"id": "123", "name": "test"});
331
332        // Put data
333        let stored = storage.put(key.clone(), data.clone()).await.unwrap();
334        assert_eq!(stored, data);
335
336        // Get data
337        let retrieved = storage.get(key).await.unwrap();
338        assert_eq!(retrieved, Some(data));
339    }
340
341    #[tokio::test]
342    async fn test_get_nonexistent() {
343        let storage = InMemoryStorage::new();
344        let key = StorageKey::new("tenant1", "User", "999");
345
346        let result = storage.get(key).await.unwrap();
347        assert!(result.is_none());
348    }
349
350    #[tokio::test]
351    async fn test_delete() {
352        let storage = InMemoryStorage::new();
353        let key = StorageKey::new("tenant1", "User", "123");
354        let data = json!({"id": "123", "name": "test"});
355
356        // Put data first
357        storage.put(key.clone(), data).await.unwrap();
358
359        // Delete should return true
360        let deleted = storage.delete(key.clone()).await.unwrap();
361        assert!(deleted);
362
363        // Get should return None
364        let retrieved = storage.get(key.clone()).await.unwrap();
365        assert!(retrieved.is_none());
366
367        // Delete again should return false
368        let deleted_again = storage.delete(key).await.unwrap();
369        assert!(!deleted_again);
370    }
371
372    #[tokio::test]
373    async fn test_exists() {
374        let storage = InMemoryStorage::new();
375        let key = StorageKey::new("tenant1", "User", "123");
376        let data = json!({"id": "123", "name": "test"});
377
378        // Should not exist initially
379        assert!(!storage.exists(key.clone()).await.unwrap());
380
381        // Put data
382        storage.put(key.clone(), data).await.unwrap();
383
384        // Should exist now
385        assert!(storage.exists(key.clone()).await.unwrap());
386
387        // Delete data
388        storage.delete(key.clone()).await.unwrap();
389
390        // Should not exist anymore
391        assert!(!storage.exists(key).await.unwrap());
392    }
393
394    #[tokio::test]
395    async fn test_list_with_pagination() {
396        let storage = InMemoryStorage::new();
397        let prefix = StorageKey::prefix("tenant1", "User");
398
399        // Store multiple resources
400        for i in 1..=5 {
401            let key = StorageKey::new("tenant1", "User", &format!("{}", i));
402            let data = json!({"id": i, "name": format!("user{}", i)});
403            storage.put(key, data).await.unwrap();
404        }
405
406        // Test pagination
407        let page1 = storage.list(prefix.clone(), 0, 2).await.unwrap();
408        assert_eq!(page1.len(), 2);
409        assert_eq!(page1[0].0.resource_id(), "1");
410        assert_eq!(page1[1].0.resource_id(), "2");
411
412        let page2 = storage.list(prefix.clone(), 2, 2).await.unwrap();
413        assert_eq!(page2.len(), 2);
414        assert_eq!(page2[0].0.resource_id(), "3");
415        assert_eq!(page2[1].0.resource_id(), "4");
416
417        let page3 = storage.list(prefix, 4, 2).await.unwrap();
418        assert_eq!(page3.len(), 1);
419        assert_eq!(page3[0].0.resource_id(), "5");
420    }
421
422    #[tokio::test]
423    async fn test_find_by_attribute() {
424        let storage = InMemoryStorage::new();
425        let prefix = StorageKey::prefix("tenant1", "User");
426
427        // Store users with different userNames
428        let user1 = json!({
429            "id": "1",
430            "userName": "john.doe",
431            "emails": [{"value": "john@example.com", "primary": true}]
432        });
433        let user2 = json!({
434            "id": "2",
435            "userName": "jane.doe",
436            "emails": [{"value": "jane@example.com", "primary": true}]
437        });
438
439        storage
440            .put(StorageKey::new("tenant1", "User", "1"), user1)
441            .await
442            .unwrap();
443        storage
444            .put(StorageKey::new("tenant1", "User", "2"), user2)
445            .await
446            .unwrap();
447
448        // Find by userName
449        let found = storage
450            .find_by_attribute(prefix.clone(), "userName", "john.doe")
451            .await
452            .unwrap();
453        assert_eq!(found.len(), 1);
454        assert_eq!(found[0].0.resource_id(), "1");
455
456        // Find by nested email
457        let found = storage
458            .find_by_attribute(prefix.clone(), "emails.0.value", "jane@example.com")
459            .await
460            .unwrap();
461        assert_eq!(found.len(), 1);
462        assert_eq!(found[0].0.resource_id(), "2");
463
464        // Find non-existent
465        let found = storage
466            .find_by_attribute(prefix, "userName", "nonexistent")
467            .await
468            .unwrap();
469        assert_eq!(found.len(), 0);
470    }
471
472    #[tokio::test]
473    async fn test_count() {
474        let storage = InMemoryStorage::new();
475        let prefix = StorageKey::prefix("tenant1", "User");
476
477        // Initially empty
478        assert_eq!(storage.count(prefix.clone()).await.unwrap(), 0);
479
480        // Add some resources
481        for i in 1..=3 {
482            let key = StorageKey::new("tenant1", "User", &format!("{}", i));
483            let data = json!({"id": i});
484            storage.put(key, data).await.unwrap();
485        }
486
487        assert_eq!(storage.count(prefix).await.unwrap(), 3);
488    }
489
490    #[tokio::test]
491    async fn test_tenant_isolation() {
492        let storage = InMemoryStorage::new();
493
494        // Store same resource ID in different tenants
495        let key1 = StorageKey::new("tenant1", "User", "123");
496        let key2 = StorageKey::new("tenant2", "User", "123");
497        let data1 = json!({"tenant": "1"});
498        let data2 = json!({"tenant": "2"});
499
500        storage.put(key1.clone(), data1.clone()).await.unwrap();
501        storage.put(key2.clone(), data2.clone()).await.unwrap();
502
503        // Verify isolation
504        assert_eq!(storage.get(key1).await.unwrap(), Some(data1));
505        assert_eq!(storage.get(key2).await.unwrap(), Some(data2));
506
507        // Verify counts are isolated
508        assert_eq!(
509            storage
510                .count(StorageKey::prefix("tenant1", "User"))
511                .await
512                .unwrap(),
513            1
514        );
515        assert_eq!(
516            storage
517                .count(StorageKey::prefix("tenant2", "User"))
518                .await
519                .unwrap(),
520            1
521        );
522    }
523
524    #[tokio::test]
525    async fn test_stats() {
526        let storage = InMemoryStorage::new();
527
528        // Initially empty
529        let stats = storage.stats().await.unwrap();
530        assert_eq!(stats.tenant_count, 0);
531        assert_eq!(stats.resource_type_count, 0);
532        assert_eq!(stats.total_resources, 0);
533
534        // Add some data
535        storage
536            .put(StorageKey::new("tenant1", "User", "1"), json!({"id": "1"}))
537            .await
538            .unwrap();
539        storage
540            .put(StorageKey::new("tenant1", "User", "2"), json!({"id": "2"}))
541            .await
542            .unwrap();
543        storage
544            .put(StorageKey::new("tenant1", "Group", "1"), json!({"id": "1"}))
545            .await
546            .unwrap();
547        storage
548            .put(StorageKey::new("tenant2", "User", "1"), json!({"id": "1"}))
549            .await
550            .unwrap();
551
552        let stats = storage.stats().await.unwrap();
553        assert_eq!(stats.tenant_count, 2);
554        assert_eq!(stats.resource_type_count, 3); // tenant1:User, tenant1:Group, tenant2:User
555        assert_eq!(stats.total_resources, 4);
556    }
557
558    #[tokio::test]
559    async fn test_clear() {
560        let storage = InMemoryStorage::new();
561
562        // Add some data
563        storage
564            .put(StorageKey::new("tenant1", "User", "1"), json!({"id": "1"}))
565            .await
566            .unwrap();
567
568        // Verify data exists
569        assert_eq!(
570            storage
571                .count(StorageKey::prefix("tenant1", "User"))
572                .await
573                .unwrap(),
574            1
575        );
576
577        // Clear all data
578        let _ = storage.clear().await;
579
580        // Verify data is gone
581        assert_eq!(
582            storage
583                .count(StorageKey::prefix("tenant1", "User"))
584                .await
585                .unwrap(),
586            0
587        );
588        let stats = storage.stats().await.unwrap();
589        assert_eq!(stats.total_resources, 0);
590    }
591
592    #[tokio::test]
593    async fn test_list_tenants_and_resource_types() {
594        let storage = InMemoryStorage::new();
595
596        // Add data for multiple tenants and types
597        storage
598            .put(StorageKey::new("tenant1", "User", "1"), json!({"id": "1"}))
599            .await
600            .unwrap();
601        storage
602            .put(StorageKey::new("tenant1", "Group", "1"), json!({"id": "1"}))
603            .await
604            .unwrap();
605        storage
606            .put(StorageKey::new("tenant2", "User", "1"), json!({"id": "1"}))
607            .await
608            .unwrap();
609
610        // Test list_tenants
611        let mut tenants = storage.list_tenants().await.unwrap();
612        tenants.sort();
613        assert_eq!(tenants, vec!["tenant1", "tenant2"]);
614
615        // Test list_resource_types
616        let mut types1 = storage.list_resource_types("tenant1").await.unwrap();
617        types1.sort();
618        assert_eq!(types1, vec!["Group", "User"]);
619
620        let types2 = storage.list_resource_types("tenant2").await.unwrap();
621        assert_eq!(types2, vec!["User"]);
622
623        // Non-existent tenant
624        let types_none = storage.list_resource_types("nonexistent").await.unwrap();
625        assert!(types_none.is_empty());
626    }
627
628    #[tokio::test]
629    async fn test_extract_attribute_value() {
630        let data = json!({
631            "userName": "john.doe",
632            "emails": [
633                {"value": "john@example.com", "primary": true},
634                {"value": "john.doe@work.com", "primary": false}
635            ],
636            "address": {
637                "street": "123 Main St",
638                "city": "Anytown"
639            }
640        });
641
642        // Simple attribute
643        assert_eq!(
644            InMemoryStorage::extract_attribute_value(&data, "userName"),
645            Some("john.doe".to_string())
646        );
647
648        // Nested object
649        assert_eq!(
650            InMemoryStorage::extract_attribute_value(&data, "address.city"),
651            Some("Anytown".to_string())
652        );
653
654        // Array index
655        assert_eq!(
656            InMemoryStorage::extract_attribute_value(&data, "emails.0.value"),
657            Some("john@example.com".to_string())
658        );
659
660        // Boolean value
661        assert_eq!(
662            InMemoryStorage::extract_attribute_value(&data, "emails.0.primary"),
663            Some("true".to_string())
664        );
665
666        // Non-existent path
667        assert_eq!(
668            InMemoryStorage::extract_attribute_value(&data, "nonexistent"),
669            None
670        );
671
672        // Invalid array index
673        assert_eq!(
674            InMemoryStorage::extract_attribute_value(&data, "emails.99.value"),
675            None
676        );
677    }
678}