graphrefly_storage/
backend.rs1use std::collections::HashMap;
13use std::sync::Arc;
14
15use parking_lot::Mutex;
16
17use crate::error::StorageError;
18
19pub trait StorageBackend: Send + Sync {
22 fn name(&self) -> &str;
25
26 fn read(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError>;
28
29 fn write(&self, key: &str, bytes: &[u8]) -> Result<(), StorageError>;
31
32 fn delete(&self, key: &str) -> Result<(), StorageError> {
35 let _ = key;
36 Ok(())
37 }
38
39 fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
44 let _ = prefix;
45 Err(StorageError::BackendNoListSupport {
46 tier: self.name().to_string(),
47 })
48 }
49
50 fn flush(&self) -> Result<(), StorageError> {
55 Ok(())
56 }
57}
58
59#[derive(Debug)]
68pub struct MemoryBackend {
69 name: String,
70 data: Mutex<HashMap<String, Vec<u8>>>,
71}
72
73impl Default for MemoryBackend {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl MemoryBackend {
80 #[must_use]
81 pub fn new() -> Self {
82 Self {
83 name: "memory".into(),
84 data: Mutex::new(HashMap::new()),
85 }
86 }
87
88 #[must_use]
89 pub fn with_name(name: impl Into<String>) -> Self {
90 Self {
91 name: name.into(),
92 data: Mutex::new(HashMap::new()),
93 }
94 }
95
96 #[must_use]
98 pub fn len(&self) -> usize {
99 self.data.lock().len()
100 }
101
102 #[must_use]
104 pub fn is_empty(&self) -> bool {
105 self.data.lock().is_empty()
106 }
107}
108
109impl StorageBackend for MemoryBackend {
110 fn name(&self) -> &str {
111 &self.name
112 }
113
114 fn read(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
115 Ok(self.data.lock().get(key).cloned())
116 }
117
118 fn write(&self, key: &str, bytes: &[u8]) -> Result<(), StorageError> {
119 self.data.lock().insert(key.to_string(), bytes.to_vec());
120 Ok(())
121 }
122
123 fn delete(&self, key: &str) -> Result<(), StorageError> {
124 self.data.lock().remove(key);
125 Ok(())
126 }
127
128 fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
129 let guard = self.data.lock();
130 let mut keys: Vec<String> = if prefix.is_empty() {
131 guard.keys().cloned().collect()
132 } else {
133 guard
134 .keys()
135 .filter(|k| k.starts_with(prefix))
136 .cloned()
137 .collect()
138 };
139 keys.sort();
140 Ok(keys)
141 }
142}
143
144#[must_use]
148pub fn memory_backend() -> Arc<MemoryBackend> {
149 Arc::new(MemoryBackend::new())
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn memory_backend_read_write_round_trip() {
158 let b = MemoryBackend::new();
159 assert!(b.is_empty());
160 b.write("k1", b"hello").unwrap();
161 assert_eq!(b.read("k1").unwrap(), Some(b"hello".to_vec()));
162 assert_eq!(b.len(), 1);
163 }
164
165 #[test]
166 fn memory_backend_read_miss_returns_none() {
167 let b = MemoryBackend::new();
168 assert!(b.read("nope").unwrap().is_none());
169 }
170
171 #[test]
172 fn memory_backend_delete_removes_key() {
173 let b = MemoryBackend::new();
174 b.write("k", b"v").unwrap();
175 b.delete("k").unwrap();
176 assert!(b.read("k").unwrap().is_none());
177 }
178
179 #[test]
180 fn memory_backend_list_lex_asc() {
181 let b = MemoryBackend::new();
182 b.write("g/10", b"a").unwrap();
183 b.write("g/02", b"b").unwrap();
184 b.write("g/01", b"c").unwrap();
185 b.write("other", b"d").unwrap();
186 let keys = b.list("g/").unwrap();
187 assert_eq!(keys, vec!["g/01", "g/02", "g/10"]);
188 }
189
190 #[test]
191 fn memory_backend_list_empty_prefix_returns_all() {
192 let b = MemoryBackend::new();
193 b.write("a", b"x").unwrap();
194 b.write("b", b"y").unwrap();
195 let keys = b.list("").unwrap();
196 assert_eq!(keys, vec!["a", "b"]);
197 }
198
199 #[test]
200 fn memory_backend_with_custom_name() {
201 let b = MemoryBackend::with_name("test");
202 assert_eq!(b.name(), "test");
203 }
204
205 #[test]
206 fn memory_backend_factory_returns_shared_arc() {
207 let b = memory_backend();
208 let b2 = Arc::clone(&b);
209 b.write("k", b"v").unwrap();
210 assert_eq!(b2.read("k").unwrap(), Some(b"v".to_vec()));
211 }
212
213 #[test]
216 fn default_list_returns_backend_no_list_support() {
217 struct NoList;
218 impl StorageBackend for NoList {
219 fn name(&self) -> &'static str {
220 "no-list"
221 }
222 fn read(&self, _key: &str) -> Result<Option<Vec<u8>>, StorageError> {
223 Ok(None)
224 }
225 fn write(&self, _k: &str, _b: &[u8]) -> Result<(), StorageError> {
226 Ok(())
227 }
228 }
229 let b = NoList;
230 let r = b.list("g/");
231 assert!(matches!(r, Err(StorageError::BackendNoListSupport { .. })));
232 }
233}