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