backend.rs
1 //! Storage Backend Trait 2 //! 3 //! Pluggable persistence layer. Currently implements Sled, 4 //! with redb and SQLite planned for future. 5 6 use super::StorageError; 7 8 /// Pluggable storage backend trait 9 /// 10 /// All operations are raw bytes - serialization is handled by higher layers. 11 pub trait StorageBackend: Send + Sync { 12 /// Get a value by key 13 fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError>; 14 15 /// Put a value 16 fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StorageError>; 17 18 /// Delete a key 19 fn delete(&self, key: &[u8]) -> Result<bool, StorageError>; 20 21 /// Scan all keys with a given prefix 22 fn scan_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError>; 23 24 /// Delete all keys with a given prefix (GDPR delete) 25 /// Returns count of deleted keys 26 fn delete_prefix(&self, prefix: &[u8]) -> Result<u64, StorageError>; 27 28 /// Flush to disk 29 fn flush(&self) -> Result<(), StorageError>; 30 } 31 32 // ───────────────────────────────────────────────────────────────────────────── 33 // Sled Backend 34 // ───────────────────────────────────────────────────────────────────────────── 35 36 /// Sled-based storage backend 37 pub struct SledBackend { 38 db: sled::Db, 39 tree: sled::Tree, 40 } 41 42 impl SledBackend { 43 /// Create a new Sled backend 44 pub fn new(db: sled::Db) -> Result<Self, StorageError> { 45 let tree = db.open_tree("user_data")?; 46 Ok(Self { db, tree }) 47 } 48 49 /// Open or create a Sled backend at the given path 50 pub fn open(path: &std::path::Path) -> Result<Self, StorageError> { 51 let db = sled::open(path)?; 52 Self::new(db) 53 } 54 } 55 56 impl StorageBackend for SledBackend { 57 fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError> { 58 Ok(self.tree.get(key)?.map(|v| v.to_vec())) 59 } 60 61 fn put(&self, key: &[u8], value: &[u8]) -> Result<(), StorageError> { 62 self.tree.insert(key, value)?; 63 Ok(()) 64 } 65 66 fn delete(&self, key: &[u8]) -> Result<bool, StorageError> { 67 Ok(self.tree.remove(key)?.is_some()) 68 } 69 70 fn scan_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> { 71 let mut results = Vec::new(); 72 for item in self.tree.scan_prefix(prefix) { 73 let (k, v) = item?; 74 results.push((k.to_vec(), v.to_vec())); 75 } 76 Ok(results) 77 } 78 79 fn delete_prefix(&self, prefix: &[u8]) -> Result<u64, StorageError> { 80 let mut count = 0u64; 81 let keys: Vec<_> = self.tree.scan_prefix(prefix) 82 .filter_map(|r| r.ok().map(|(k, _)| k)) 83 .collect(); 84 85 for key in keys { 86 if self.tree.remove(&key)?.is_some() { 87 count += 1; 88 } 89 } 90 Ok(count) 91 } 92 93 fn flush(&self) -> Result<(), StorageError> { 94 self.db.flush()?; 95 Ok(()) 96 } 97 } 98 99 #[cfg(test)] 100 mod tests { 101 use super::*; 102 use tempfile::tempdir; 103 104 #[test] 105 fn test_sled_backend_crud() { 106 let dir = tempdir().unwrap(); 107 let backend = SledBackend::open(dir.path()).unwrap(); 108 109 // Put and get 110 backend.put(b"key1", b"value1").unwrap(); 111 assert_eq!(backend.get(b"key1").unwrap(), Some(b"value1".to_vec())); 112 113 // Delete 114 assert!(backend.delete(b"key1").unwrap()); 115 assert_eq!(backend.get(b"key1").unwrap(), None); 116 117 // Delete non-existent 118 assert!(!backend.delete(b"key1").unwrap()); 119 } 120 121 #[test] 122 fn test_sled_backend_prefix_scan() { 123 let dir = tempdir().unwrap(); 124 let backend = SledBackend::open(dir.path()).unwrap(); 125 126 backend.put(b"user1/chats/1", b"msg1").unwrap(); 127 backend.put(b"user1/chats/2", b"msg2").unwrap(); 128 backend.put(b"user1/contacts/a", b"alice").unwrap(); 129 backend.put(b"user2/chats/1", b"other").unwrap(); 130 131 let results = backend.scan_prefix(b"user1/chats/").unwrap(); 132 assert_eq!(results.len(), 2); 133 } 134 135 #[test] 136 fn test_sled_backend_prefix_delete() { 137 let dir = tempdir().unwrap(); 138 let backend = SledBackend::open(dir.path()).unwrap(); 139 140 backend.put(b"user1/chats/1", b"msg1").unwrap(); 141 backend.put(b"user1/chats/2", b"msg2").unwrap(); 142 backend.put(b"user2/chats/1", b"other").unwrap(); 143 144 let deleted = backend.delete_prefix(b"user1/").unwrap(); 145 assert_eq!(deleted, 2); 146 147 // user2's data untouched 148 assert_eq!(backend.get(b"user2/chats/1").unwrap(), Some(b"other".to_vec())); 149 } 150 }