ferro_storage/drivers/
memory.rs1use crate::storage::{FileMetadata, PutOptions, StorageDriver, Visibility};
4use crate::Error;
5use async_trait::async_trait;
6use bytes::Bytes;
7use dashmap::DashMap;
8use std::collections::HashSet;
9use std::sync::Arc;
10use std::time::SystemTime;
11
12#[derive(Clone)]
14struct StoredFile {
15 contents: Bytes,
16 #[allow(dead_code)]
17 visibility: Visibility,
18 content_type: Option<String>,
19 created_at: SystemTime,
20}
21
22#[derive(Clone)]
24pub struct MemoryDriver {
25 files: Arc<DashMap<String, StoredFile>>,
26 url_base: Option<String>,
27}
28
29impl Default for MemoryDriver {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl MemoryDriver {
36 pub fn new() -> Self {
38 Self {
39 files: Arc::new(DashMap::new()),
40 url_base: None,
41 }
42 }
43
44 pub fn with_url_base(mut self, url: impl Into<String>) -> Self {
46 self.url_base = Some(url.into());
47 self
48 }
49
50 pub fn clear(&self) {
52 self.files.clear();
53 }
54
55 pub fn len(&self) -> usize {
57 self.files.len()
58 }
59
60 pub fn is_empty(&self) -> bool {
62 self.files.is_empty()
63 }
64
65 fn normalize_path(path: &str) -> String {
67 path.trim_start_matches('/').replace('\\', "/")
68 }
69}
70
71#[async_trait]
72impl StorageDriver for MemoryDriver {
73 async fn exists(&self, path: &str) -> Result<bool, Error> {
74 let path = Self::normalize_path(path);
75 Ok(self.files.contains_key(&path))
76 }
77
78 async fn get(&self, path: &str) -> Result<Bytes, Error> {
79 let path = Self::normalize_path(path);
80 self.files
81 .get(&path)
82 .map(|f| f.contents.clone())
83 .ok_or_else(|| Error::not_found(&path))
84 }
85
86 async fn put(&self, path: &str, contents: Bytes, options: PutOptions) -> Result<(), Error> {
87 let path = Self::normalize_path(path);
88 self.files.insert(
89 path,
90 StoredFile {
91 contents,
92 visibility: options.visibility,
93 content_type: options.content_type,
94 created_at: SystemTime::now(),
95 },
96 );
97 Ok(())
98 }
99
100 async fn delete(&self, path: &str) -> Result<(), Error> {
101 let path = Self::normalize_path(path);
102 self.files
103 .remove(&path)
104 .ok_or_else(|| Error::not_found(&path))?;
105 Ok(())
106 }
107
108 async fn copy(&self, from: &str, to: &str) -> Result<(), Error> {
109 let from = Self::normalize_path(from);
110 let to = Self::normalize_path(to);
111
112 let file = self
113 .files
114 .get(&from)
115 .ok_or_else(|| Error::not_found(&from))?
116 .clone();
117
118 self.files.insert(to, file);
119 Ok(())
120 }
121
122 async fn size(&self, path: &str) -> Result<u64, Error> {
123 let path = Self::normalize_path(path);
124 self.files
125 .get(&path)
126 .map(|f| f.contents.len() as u64)
127 .ok_or_else(|| Error::not_found(&path))
128 }
129
130 async fn metadata(&self, path: &str) -> Result<FileMetadata, Error> {
131 let normalized = Self::normalize_path(path);
132 let file = self
133 .files
134 .get(&normalized)
135 .ok_or_else(|| Error::not_found(&normalized))?;
136
137 let mut meta =
138 FileMetadata::new(path, file.contents.len() as u64).with_last_modified(file.created_at);
139
140 if let Some(ref content_type) = file.content_type {
141 meta = meta.with_mime_type(content_type);
142 }
143
144 Ok(meta)
145 }
146
147 async fn url(&self, path: &str) -> Result<String, Error> {
148 let path = Self::normalize_path(path);
149 match &self.url_base {
150 Some(base) => Ok(format!("{}/{}", base.trim_end_matches('/'), path)),
151 None => Ok(format!("memory://{path}")),
152 }
153 }
154
155 async fn temporary_url(
156 &self,
157 path: &str,
158 _expiration: std::time::Duration,
159 ) -> Result<String, Error> {
160 self.url(path).await
161 }
162
163 async fn files(&self, directory: &str) -> Result<Vec<String>, Error> {
164 let dir = Self::normalize_path(directory);
165 let prefix = if dir.is_empty() {
166 String::new()
167 } else {
168 format!("{dir}/")
169 };
170
171 let mut files = Vec::new();
172 for entry in self.files.iter() {
173 let path = entry.key();
174 if path.starts_with(&prefix) || (prefix.is_empty() && !path.contains('/')) {
175 let relative = path.strip_prefix(&prefix).unwrap_or(path);
176 if !relative.contains('/') {
178 files.push(relative.to_string());
179 }
180 }
181 }
182
183 Ok(files)
184 }
185
186 async fn all_files(&self, directory: &str) -> Result<Vec<String>, Error> {
187 let dir = Self::normalize_path(directory);
188 let prefix = if dir.is_empty() {
189 String::new()
190 } else {
191 format!("{dir}/")
192 };
193
194 let mut files = Vec::new();
195 for entry in self.files.iter() {
196 let path = entry.key();
197 if path.starts_with(&prefix) {
198 let relative = path.strip_prefix(&prefix).unwrap_or(path);
199 files.push(relative.to_string());
200 } else if prefix.is_empty() {
201 files.push(path.clone());
202 }
203 }
204
205 Ok(files)
206 }
207
208 async fn directories(&self, directory: &str) -> Result<Vec<String>, Error> {
209 let dir = Self::normalize_path(directory);
210 let prefix = if dir.is_empty() {
211 String::new()
212 } else {
213 format!("{dir}/")
214 };
215
216 let mut dirs: HashSet<String> = HashSet::new();
217 for entry in self.files.iter() {
218 let path = entry.key();
219 if path.starts_with(&prefix) {
220 let relative = path.strip_prefix(&prefix).unwrap_or(path);
221 if let Some(slash_idx) = relative.find('/') {
222 dirs.insert(relative[..slash_idx].to_string());
223 }
224 }
225 }
226
227 Ok(dirs.into_iter().collect())
228 }
229
230 async fn make_directory(&self, _path: &str) -> Result<(), Error> {
231 Ok(())
233 }
234
235 async fn delete_directory(&self, path: &str) -> Result<(), Error> {
236 let dir = Self::normalize_path(path);
237 let prefix = format!("{dir}/");
238
239 let keys_to_remove: Vec<String> = self
240 .files
241 .iter()
242 .filter(|entry| entry.key().starts_with(&prefix))
243 .map(|entry| entry.key().clone())
244 .collect();
245
246 for key in keys_to_remove {
247 self.files.remove(&key);
248 }
249
250 Ok(())
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[tokio::test]
259 async fn test_memory_driver_put_get() {
260 let driver = MemoryDriver::new();
261
262 driver
263 .put("test.txt", Bytes::from("hello"), PutOptions::new())
264 .await
265 .unwrap();
266
267 let contents = driver.get("test.txt").await.unwrap();
268 assert_eq!(contents, Bytes::from("hello"));
269 }
270
271 #[tokio::test]
272 async fn test_memory_driver_exists() {
273 let driver = MemoryDriver::new();
274
275 assert!(!driver.exists("missing.txt").await.unwrap());
276
277 driver
278 .put("exists.txt", Bytes::from("data"), PutOptions::new())
279 .await
280 .unwrap();
281
282 assert!(driver.exists("exists.txt").await.unwrap());
283 }
284
285 #[tokio::test]
286 async fn test_memory_driver_delete() {
287 let driver = MemoryDriver::new();
288
289 driver
290 .put("to_delete.txt", Bytes::from("data"), PutOptions::new())
291 .await
292 .unwrap();
293
294 driver.delete("to_delete.txt").await.unwrap();
295 assert!(!driver.exists("to_delete.txt").await.unwrap());
296 }
297
298 #[tokio::test]
299 async fn test_memory_driver_copy() {
300 let driver = MemoryDriver::new();
301
302 driver
303 .put("original.txt", Bytes::from("content"), PutOptions::new())
304 .await
305 .unwrap();
306
307 driver.copy("original.txt", "copy.txt").await.unwrap();
308
309 assert_eq!(
310 driver.get("copy.txt").await.unwrap(),
311 Bytes::from("content")
312 );
313 }
314
315 #[tokio::test]
316 async fn test_memory_driver_url() {
317 let driver = MemoryDriver::new().with_url_base("https://cdn.example.com");
318
319 let url = driver.url("images/photo.jpg").await.unwrap();
320 assert_eq!(url, "https://cdn.example.com/images/photo.jpg");
321 }
322
323 #[tokio::test]
324 async fn test_memory_driver_directories() {
325 let driver = MemoryDriver::new();
326
327 driver
328 .put("images/a.jpg", Bytes::from("a"), PutOptions::new())
329 .await
330 .unwrap();
331 driver
332 .put("images/b.jpg", Bytes::from("b"), PutOptions::new())
333 .await
334 .unwrap();
335 driver
336 .put("docs/readme.md", Bytes::from("readme"), PutOptions::new())
337 .await
338 .unwrap();
339
340 let dirs = driver.directories("").await.unwrap();
341 assert!(dirs.contains(&"images".to_string()));
342 assert!(dirs.contains(&"docs".to_string()));
343 }
344
345 #[tokio::test]
346 async fn test_memory_driver_clear() {
347 let driver = MemoryDriver::new();
348
349 driver
350 .put("a.txt", Bytes::from("a"), PutOptions::new())
351 .await
352 .unwrap();
353 driver
354 .put("b.txt", Bytes::from("b"), PutOptions::new())
355 .await
356 .unwrap();
357
358 assert_eq!(driver.len(), 2);
359
360 driver.clear();
361 assert!(driver.is_empty());
362 }
363}