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