Skip to main content

sova_storage/
memory.rs

1//! In-memory [`BlobStore`] (tests / ephemeral).
2
3use crate::{normalize_key, normalize_prefix, BlobStore, BoxFuture, PutOpts, StorageError};
4use bytes::Bytes;
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7
8#[derive(Clone, Default)]
9pub struct MemoryStore {
10    inner: Arc<Mutex<HashMap<String, Bytes>>>,
11}
12
13impl MemoryStore {
14    pub fn new() -> Self {
15        Self::default()
16    }
17}
18
19impl BlobStore for MemoryStore {
20    fn put<'a>(
21        &'a self,
22        key: &'a str,
23        data: Bytes,
24        _opts: PutOpts,
25    ) -> BoxFuture<'a, Result<(), StorageError>> {
26        Box::pin(async move {
27            let key = normalize_key(key)?;
28            self.inner.lock().unwrap().insert(key, data);
29            Ok(())
30        })
31    }
32
33    fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>, StorageError>> {
34        Box::pin(async move {
35            let key = normalize_key(key)?;
36            Ok(self.inner.lock().unwrap().get(&key).cloned())
37        })
38    }
39
40    fn delete<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<(), StorageError>> {
41        Box::pin(async move {
42            let key = normalize_key(key)?;
43            self.inner.lock().unwrap().remove(&key);
44            Ok(())
45        })
46    }
47
48    fn exists<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool, StorageError>> {
49        Box::pin(async move {
50            let key = normalize_key(key)?;
51            Ok(self.inner.lock().unwrap().contains_key(&key))
52        })
53    }
54
55    fn list<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>, StorageError>> {
56        Box::pin(async move {
57            let prefix = normalize_prefix(prefix)?;
58            let map = self.inner.lock().unwrap();
59            let mut keys: Vec<String> = map
60                .keys()
61                .filter(|k| {
62                    if prefix.is_empty() {
63                        true
64                    } else if prefix.ends_with('/') {
65                        k.starts_with(&prefix)
66                    } else {
67                        *k == &prefix || k.starts_with(&format!("{prefix}/"))
68                    }
69                })
70                .cloned()
71                .collect();
72            keys.sort();
73            Ok(keys)
74        })
75    }
76}