role_system/
storage.rs

1//! Storage abstractions for persisting role system data.
2
3use crate::{error::Result, role::Role};
4use dashmap::DashMap;
5use std::sync::Arc;
6
7/// Trait for storing and retrieving role system data.
8pub trait Storage: Send + Sync {
9    /// Store a role.
10    fn store_role(&mut self, role: Role) -> Result<()>;
11
12    /// Get a role by name.
13    fn get_role(&self, name: &str) -> Result<Option<Role>>;
14
15    /// Check if a role exists.
16    fn role_exists(&self, name: &str) -> Result<bool>;
17
18    /// Delete a role.
19    fn delete_role(&mut self, name: &str) -> Result<bool>;
20
21    /// List all role names.
22    fn list_roles(&self) -> Result<Vec<String>>;
23
24    /// Update an existing role.
25    fn update_role(&mut self, role: Role) -> Result<()>;
26
27    /// Get the number of stored roles.
28    fn role_count(&self) -> usize {
29        // Default implementation that tries to count via list_roles
30        self.list_roles().map(|roles| roles.len()).unwrap_or(0)
31    }
32}
33
34/// In-memory storage implementation using DashMap for thread safety.
35#[derive(Debug, Default, Clone)]
36pub struct MemoryStorage {
37    roles: Arc<DashMap<String, Role>>,
38}
39
40impl MemoryStorage {
41    /// Create a new memory storage instance.
42    pub fn new() -> Self {
43        Self {
44            roles: Arc::new(DashMap::new()),
45        }
46    }
47
48    /// Get the number of stored roles.
49    pub fn role_count(&self) -> usize {
50        self.roles.len()
51    }
52
53    /// Clear all stored data.
54    pub fn clear(&mut self) {
55        self.roles.clear();
56    }
57}
58
59impl Storage for MemoryStorage {
60    fn store_role(&mut self, role: Role) -> Result<()> {
61        let name = role.name().to_string();
62        self.roles.insert(name, role);
63        Ok(())
64    }
65
66    fn get_role(&self, name: &str) -> Result<Option<Role>> {
67        Ok(self.roles.get(name).map(|r| r.clone()))
68    }
69
70    fn role_exists(&self, name: &str) -> Result<bool> {
71        Ok(self.roles.contains_key(name))
72    }
73
74    fn delete_role(&mut self, name: &str) -> Result<bool> {
75        Ok(self.roles.remove(name).is_some())
76    }
77
78    fn list_roles(&self) -> Result<Vec<String>> {
79        Ok(self.roles.iter().map(|entry| entry.key().clone()).collect())
80    }
81
82    fn update_role(&mut self, role: Role) -> Result<()> {
83        let name = role.name().to_string();
84        self.roles.insert(name, role);
85        Ok(())
86    }
87}
88
89/// File-based storage implementation (requires persistence feature).
90#[cfg(feature = "persistence")]
91pub mod file_storage {
92    use super::*;
93    use crate::error::Error;
94    use std::{
95        collections::HashMap,
96        fs::{File, OpenOptions},
97        io::{BufReader, BufWriter},
98        path::{Path, PathBuf},
99        sync::RwLock,
100    };
101
102    /// File-based storage that persists roles to JSON files.
103    #[derive(Debug)]
104    pub struct FileStorage {
105        storage_path: PathBuf,
106        roles: Arc<RwLock<HashMap<String, Role>>>,
107    }
108
109    impl FileStorage {
110        /// Create a new file storage instance.
111        pub fn new(storage_path: impl AsRef<Path>) -> Result<Self> {
112            let storage_path = storage_path.as_ref().to_path_buf();
113
114            // Create directory if it doesn't exist
115            if let Some(parent) = storage_path.parent() {
116                std::fs::create_dir_all(parent).map_err(|e| {
117                    Error::Storage(format!("Failed to create storage directory: {}", e))
118                })?;
119            }
120
121            let mut storage = Self {
122                storage_path,
123                roles: Arc::new(RwLock::new(HashMap::new())),
124            };
125
126            // Load existing data if the file exists
127            storage.load_from_disk()?;
128
129            Ok(storage)
130        }
131
132        /// Load roles from disk.
133        fn load_from_disk(&mut self) -> Result<()> {
134            if !self.storage_path.exists() {
135                return Ok(());
136            }
137
138            let file = File::open(&self.storage_path)
139                .map_err(|e| Error::Storage(format!("Failed to open storage file: {}", e)))?;
140
141            let reader = BufReader::new(file);
142            let roles: HashMap<String, Role> = serde_json::from_reader(reader)?;
143
144            *self
145                .roles
146                .write()
147                .map_err(|e| Error::Storage(format!("Failed to acquire write lock: {}", e)))? =
148                roles;
149            Ok(())
150        }
151
152        /// Save roles to disk.
153        fn save_to_disk(&self) -> Result<()> {
154            let file = OpenOptions::new()
155                .write(true)
156                .create(true)
157                .truncate(true)
158                .open(&self.storage_path)
159                .map_err(|e| Error::Storage(format!("Failed to create storage file: {}", e)))?;
160
161            let writer = BufWriter::new(file);
162            let roles = self
163                .roles
164                .read()
165                .map_err(|e| Error::Storage(format!("Failed to acquire read lock: {}", e)))?;
166            serde_json::to_writer_pretty(writer, &*roles)?;
167            Ok(())
168        }
169
170        /// Get the storage file path.
171        pub fn storage_path(&self) -> &Path {
172            &self.storage_path
173        }
174
175        /// Get the number of stored roles.
176        pub fn role_count(&self) -> usize {
177            self.roles.read().map(|roles| roles.len()).unwrap_or(0)
178        }
179    }
180
181    impl Storage for FileStorage {
182        fn store_role(&mut self, role: Role) -> Result<()> {
183            let name = role.name().to_string();
184            self.roles
185                .write()
186                .map_err(|e| Error::Storage(format!("Failed to acquire write lock: {}", e)))?
187                .insert(name, role);
188            self.save_to_disk()
189        }
190
191        fn get_role(&self, name: &str) -> Result<Option<Role>> {
192            let roles = self
193                .roles
194                .read()
195                .map_err(|e| Error::Storage(format!("Failed to acquire read lock: {}", e)))?;
196            Ok(roles.get(name).cloned())
197        }
198
199        fn role_exists(&self, name: &str) -> Result<bool> {
200            let roles = self
201                .roles
202                .read()
203                .map_err(|e| Error::Storage(format!("Failed to acquire read lock: {}", e)))?;
204            Ok(roles.contains_key(name))
205        }
206
207        fn delete_role(&mut self, name: &str) -> Result<bool> {
208            let removed = self
209                .roles
210                .write()
211                .map_err(|e| Error::Storage(format!("Failed to acquire write lock: {}", e)))?
212                .remove(name)
213                .is_some();
214            if removed {
215                self.save_to_disk()?;
216            }
217            Ok(removed)
218        }
219
220        fn list_roles(&self) -> Result<Vec<String>> {
221            let roles = self
222                .roles
223                .read()
224                .map_err(|e| Error::Storage(format!("Failed to acquire read lock: {}", e)))?;
225            Ok(roles.keys().cloned().collect())
226        }
227
228        fn update_role(&mut self, role: Role) -> Result<()> {
229            let name = role.name().to_string();
230            self.roles
231                .write()
232                .map_err(|e| Error::Storage(format!("Failed to acquire write lock: {}", e)))?
233                .insert(name, role);
234            self.save_to_disk()
235        }
236    }
237}
238
239#[cfg(feature = "persistence")]
240pub use file_storage::FileStorage;
241
242/// Composite storage that can combine multiple storage backends.
243pub struct CompositeStorage {
244    primary: Box<dyn Storage>,
245    secondary: Option<Box<dyn Storage>>,
246}
247
248impl CompositeStorage {
249    /// Create a new composite storage with primary storage.
250    pub fn new(primary: Box<dyn Storage>) -> Self {
251        Self {
252            primary,
253            secondary: None,
254        }
255    }
256
257    /// Add a secondary storage backend.
258    pub fn with_secondary(mut self, secondary: Box<dyn Storage>) -> Self {
259        self.secondary = Some(secondary);
260        self
261    }
262}
263
264impl Storage for CompositeStorage {
265    fn store_role(&mut self, role: Role) -> Result<()> {
266        // Store in primary first
267        self.primary.store_role(role.clone())?;
268
269        // Then store in secondary if available
270        if let Some(secondary) = &mut self.secondary {
271            secondary.store_role(role)?;
272        }
273
274        Ok(())
275    }
276
277    fn get_role(&self, name: &str) -> Result<Option<Role>> {
278        // Try primary first
279        match self.primary.get_role(name)? {
280            Some(role) => Ok(Some(role)),
281            None => {
282                // Fallback to secondary
283                if let Some(secondary) = &self.secondary {
284                    secondary.get_role(name)
285                } else {
286                    Ok(None)
287                }
288            }
289        }
290    }
291
292    fn role_exists(&self, name: &str) -> Result<bool> {
293        if self.primary.role_exists(name)? {
294            Ok(true)
295        } else if let Some(secondary) = &self.secondary {
296            secondary.role_exists(name)
297        } else {
298            Ok(false)
299        }
300    }
301
302    fn delete_role(&mut self, name: &str) -> Result<bool> {
303        let mut deleted = false;
304
305        if self.primary.delete_role(name)? {
306            deleted = true;
307        }
308
309        if let Some(secondary) = &mut self.secondary
310            && secondary.delete_role(name)?
311        {
312            deleted = true;
313        }
314
315        Ok(deleted)
316    }
317
318    fn list_roles(&self) -> Result<Vec<String>> {
319        let mut roles = self.primary.list_roles()?;
320
321        if let Some(secondary) = &self.secondary {
322            let secondary_roles = secondary.list_roles()?;
323            for role in secondary_roles {
324                if !roles.contains(&role) {
325                    roles.push(role);
326                }
327            }
328        }
329
330        roles.sort();
331        Ok(roles)
332    }
333
334    fn update_role(&mut self, role: Role) -> Result<()> {
335        self.primary.update_role(role.clone())?;
336
337        if let Some(secondary) = &mut self.secondary {
338            secondary.update_role(role)?;
339        }
340
341        Ok(())
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use crate::{permission::Permission, role::Role};
349
350    #[test]
351    fn test_memory_storage() {
352        let mut storage = MemoryStorage::new();
353
354        let role = Role::new("test-role").add_permission(Permission::new("read", "documents"));
355
356        // Store role
357        storage
358            .store_role(role.clone())
359            .expect("Failed to store role");
360        assert_eq!(storage.role_count(), 1);
361
362        // Check existence
363        assert!(
364            storage
365                .role_exists("test-role")
366                .expect("Failed to check role existence")
367        );
368        assert!(
369            !storage
370                .role_exists("non-existent")
371                .expect("Failed to check role existence")
372        );
373
374        // Get role
375        let retrieved = storage
376            .get_role("test-role")
377            .expect("Failed to get role")
378            .expect("Role should exist");
379        assert_eq!(retrieved.name(), "test-role");
380
381        // List roles
382        let roles = storage.list_roles().expect("Failed to list roles");
383        assert_eq!(roles.len(), 1);
384        assert!(roles.contains(&"test-role".to_string()));
385
386        // Delete role
387        assert!(
388            storage
389                .delete_role("test-role")
390                .expect("Failed to delete role")
391        );
392        assert!(
393            !storage
394                .role_exists("test-role")
395                .expect("Failed to check role existence")
396        );
397        assert_eq!(storage.role_count(), 0);
398    }
399
400    #[cfg(feature = "persistence")]
401    #[test]
402    fn test_file_storage() {
403        use std::env;
404
405        let temp_dir = env::temp_dir();
406        let storage_path = temp_dir.join("test_roles.json");
407
408        // Clean up any existing file
409        let _ = std::fs::remove_file(&storage_path);
410
411        {
412            let mut storage =
413                FileStorage::new(&storage_path).expect("Failed to create file storage");
414
415            let role =
416                Role::new("file-test-role").add_permission(Permission::new("read", "documents"));
417
418            // Store role
419            storage
420                .store_role(role.clone())
421                .expect("Failed to store role");
422            assert_eq!(storage.role_count(), 1);
423
424            // Verify file was created
425            assert!(storage_path.exists());
426        }
427
428        // Create new storage instance to test persistence
429        {
430            let storage = FileStorage::new(&storage_path).expect("Failed to create file storage");
431            assert_eq!(storage.role_count(), 1);
432
433            let retrieved = storage
434                .get_role("file-test-role")
435                .expect("Failed to get role")
436                .expect("Role should exist");
437            assert_eq!(retrieved.name(), "file-test-role");
438        }
439
440        // Clean up
441        let _ = std::fs::remove_file(&storage_path);
442    }
443
444    #[test]
445    fn test_composite_storage() {
446        let primary = Box::new(MemoryStorage::new());
447        let secondary = Box::new(MemoryStorage::new());
448
449        let mut storage = CompositeStorage::new(primary).with_secondary(secondary);
450
451        let role = Role::new("composite-test").add_permission(Permission::new("read", "documents"));
452
453        // Store in both
454        storage
455            .store_role(role.clone())
456            .expect("Failed to store role");
457
458        // Should be able to retrieve
459        let retrieved = storage
460            .get_role("composite-test")
461            .expect("Failed to get role")
462            .expect("Role should exist");
463        assert_eq!(retrieved.name(), "composite-test");
464
465        // Should appear in list
466        let roles = storage.list_roles().expect("Failed to list roles");
467        assert!(roles.contains(&"composite-test".to_string()));
468    }
469}