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).map_err(|e| {
133                Error::Storage(format!("Failed to open storage file: {}", e))
134            })?;
135
136            let reader = BufReader::new(file);
137            let roles: HashMap<String, Role> = serde_json::from_reader(reader)?;
138            
139            *self.roles.write().unwrap() = roles;
140            Ok(())
141        }
142
143        /// Save roles to disk.
144        fn save_to_disk(&self) -> Result<()> {
145            let file = OpenOptions::new()
146                .write(true)
147                .create(true)
148                .truncate(true)
149                .open(&self.storage_path)
150                .map_err(|e| {
151                    Error::Storage(format!("Failed to create storage file: {}", e))
152                })?;
153
154            let writer = BufWriter::new(file);
155            let roles = self.roles.read().unwrap();
156            serde_json::to_writer_pretty(writer, &*roles)?;
157            Ok(())
158        }
159
160        /// Get the storage file path.
161        pub fn storage_path(&self) -> &Path {
162            &self.storage_path
163        }
164
165        /// Get the number of stored roles.
166        pub fn role_count(&self) -> usize {
167            self.roles.read().unwrap().len()
168        }
169    }
170
171    impl Storage for FileStorage {
172        fn store_role(&mut self, role: Role) -> Result<()> {
173            let name = role.name().to_string();
174            self.roles.write().unwrap().insert(name, role);
175            self.save_to_disk()
176        }
177
178        fn get_role(&self, name: &str) -> Result<Option<Role>> {
179            Ok(self.roles.read().unwrap().get(name).cloned())
180        }
181
182        fn role_exists(&self, name: &str) -> Result<bool> {
183            Ok(self.roles.read().unwrap().contains_key(name))
184        }
185
186        fn delete_role(&mut self, name: &str) -> Result<bool> {
187            let removed = self.roles.write().unwrap().remove(name).is_some();
188            if removed {
189                self.save_to_disk()?;
190            }
191            Ok(removed)
192        }
193
194        fn list_roles(&self) -> Result<Vec<String>> {
195            Ok(self.roles.read().unwrap().keys().cloned().collect())
196        }
197
198        fn update_role(&mut self, role: Role) -> Result<()> {
199            let name = role.name().to_string();
200            self.roles.write().unwrap().insert(name, role);
201            self.save_to_disk()
202        }
203    }
204}
205
206#[cfg(feature = "persistence")]
207pub use file_storage::FileStorage;
208
209/// Composite storage that can combine multiple storage backends.
210pub struct CompositeStorage {
211    primary: Box<dyn Storage>,
212    secondary: Option<Box<dyn Storage>>,
213}
214
215impl CompositeStorage {
216    /// Create a new composite storage with primary storage.
217    pub fn new(primary: Box<dyn Storage>) -> Self {
218        Self {
219            primary,
220            secondary: None,
221        }
222    }
223
224    /// Add a secondary storage backend.
225    pub fn with_secondary(mut self, secondary: Box<dyn Storage>) -> Self {
226        self.secondary = Some(secondary);
227        self
228    }
229}
230
231impl Storage for CompositeStorage {
232    fn store_role(&mut self, role: Role) -> Result<()> {
233        // Store in primary first
234        self.primary.store_role(role.clone())?;
235        
236        // Then store in secondary if available
237        if let Some(secondary) = &mut self.secondary {
238            secondary.store_role(role)?;
239        }
240        
241        Ok(())
242    }
243
244    fn get_role(&self, name: &str) -> Result<Option<Role>> {
245        // Try primary first
246        match self.primary.get_role(name)? {
247            Some(role) => Ok(Some(role)),
248            None => {
249                // Fallback to secondary
250                if let Some(secondary) = &self.secondary {
251                    secondary.get_role(name)
252                } else {
253                    Ok(None)
254                }
255            }
256        }
257    }
258
259    fn role_exists(&self, name: &str) -> Result<bool> {
260        if self.primary.role_exists(name)? {
261            Ok(true)
262        } else if let Some(secondary) = &self.secondary {
263            secondary.role_exists(name)
264        } else {
265            Ok(false)
266        }
267    }
268
269    fn delete_role(&mut self, name: &str) -> Result<bool> {
270        let mut deleted = false;
271        
272        if self.primary.delete_role(name)? {
273            deleted = true;
274        }
275        
276        if let Some(secondary) = &mut self.secondary {
277            if secondary.delete_role(name)? {
278                deleted = true;
279            }
280        }
281        
282        Ok(deleted)
283    }
284
285    fn list_roles(&self) -> Result<Vec<String>> {
286        let mut roles = self.primary.list_roles()?;
287        
288        if let Some(secondary) = &self.secondary {
289            let secondary_roles = secondary.list_roles()?;
290            for role in secondary_roles {
291                if !roles.contains(&role) {
292                    roles.push(role);
293                }
294            }
295        }
296        
297        roles.sort();
298        Ok(roles)
299    }
300
301    fn update_role(&mut self, role: Role) -> Result<()> {
302        self.primary.update_role(role.clone())?;
303        
304        if let Some(secondary) = &mut self.secondary {
305            secondary.update_role(role)?;
306        }
307        
308        Ok(())
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use crate::{permission::Permission, role::Role};
316
317    #[test]
318    fn test_memory_storage() {
319        let mut storage = MemoryStorage::new();
320        
321        let role = Role::new("test-role")
322            .add_permission(Permission::new("read", "documents"));
323        
324        // Store role
325        storage.store_role(role.clone()).unwrap();
326        assert_eq!(storage.role_count(), 1);
327        
328        // Check existence
329        assert!(storage.role_exists("test-role").unwrap());
330        assert!(!storage.role_exists("non-existent").unwrap());
331        
332        // Get role
333        let retrieved = storage.get_role("test-role").unwrap().unwrap();
334        assert_eq!(retrieved.name(), "test-role");
335        
336        // List roles
337        let roles = storage.list_roles().unwrap();
338        assert_eq!(roles.len(), 1);
339        assert!(roles.contains(&"test-role".to_string()));
340        
341        // Delete role
342        assert!(storage.delete_role("test-role").unwrap());
343        assert!(!storage.role_exists("test-role").unwrap());
344        assert_eq!(storage.role_count(), 0);
345    }
346
347    #[cfg(feature = "persistence")]
348    #[test]
349    fn test_file_storage() {
350        use std::env;
351        
352        let temp_dir = env::temp_dir();
353        let storage_path = temp_dir.join("test_roles.json");
354        
355        // Clean up any existing file
356        let _ = std::fs::remove_file(&storage_path);
357        
358        {
359            let mut storage = FileStorage::new(&storage_path).unwrap();
360            
361            let role = Role::new("file-test-role")
362                .add_permission(Permission::new("read", "documents"));
363            
364            // Store role
365            storage.store_role(role.clone()).unwrap();
366            assert_eq!(storage.role_count(), 1);
367            
368            // Verify file was created
369            assert!(storage_path.exists());
370        }
371        
372        // Create new storage instance to test persistence
373        {
374            let storage = FileStorage::new(&storage_path).unwrap();
375            assert_eq!(storage.role_count(), 1);
376            
377            let retrieved = storage.get_role("file-test-role").unwrap().unwrap();
378            assert_eq!(retrieved.name(), "file-test-role");
379        }
380        
381        // Clean up
382        let _ = std::fs::remove_file(&storage_path);
383    }
384
385    #[test]
386    fn test_composite_storage() {
387        let primary = Box::new(MemoryStorage::new());
388        let secondary = Box::new(MemoryStorage::new());
389        
390        let mut storage = CompositeStorage::new(primary).with_secondary(secondary);
391        
392        let role = Role::new("composite-test")
393            .add_permission(Permission::new("read", "documents"));
394        
395        // Store in both
396        storage.store_role(role.clone()).unwrap();
397        
398        // Should be able to retrieve
399        let retrieved = storage.get_role("composite-test").unwrap().unwrap();
400        assert_eq!(retrieved.name(), "composite-test");
401        
402        // Should appear in list
403        let roles = storage.list_roles().unwrap();
404        assert!(roles.contains(&"composite-test".to_string()));
405    }
406}