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