sova-storage 0.1.0

Object storage for Sova (local / memory / S3 / GCS / Azure)
Documentation
//! In-memory [`BlobStore`] (tests / ephemeral).

use crate::{normalize_key, normalize_prefix, BlobStore, BoxFuture, PutOpts, StorageError};
use bytes::Bytes;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(Clone, Default)]
pub struct MemoryStore {
    inner: Arc<Mutex<HashMap<String, Bytes>>>,
}

impl MemoryStore {
    pub fn new() -> Self {
        Self::default()
    }
}

impl BlobStore for MemoryStore {
    fn put<'a>(
        &'a self,
        key: &'a str,
        data: Bytes,
        _opts: PutOpts,
    ) -> BoxFuture<'a, Result<(), StorageError>> {
        Box::pin(async move {
            let key = normalize_key(key)?;
            self.inner.lock().await.insert(key, data);
            Ok(())
        })
    }

    fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Bytes>, StorageError>> {
        Box::pin(async move {
            let key = normalize_key(key)?;
            Ok(self.inner.lock().await.get(&key).cloned())
        })
    }

    fn delete<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<(), StorageError>> {
        Box::pin(async move {
            let key = normalize_key(key)?;
            self.inner.lock().await.remove(&key);
            Ok(())
        })
    }

    fn exists<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool, StorageError>> {
        Box::pin(async move {
            let key = normalize_key(key)?;
            Ok(self.inner.lock().await.contains_key(&key))
        })
    }

    fn list<'a>(&'a self, prefix: &'a str) -> BoxFuture<'a, Result<Vec<String>, StorageError>> {
        Box::pin(async move {
            let prefix = normalize_prefix(prefix)?;
            let map = self.inner.lock().await;
            let mut keys: Vec<String> = map
                .keys()
                .filter(|k| {
                    if prefix.is_empty() {
                        true
                    } else if prefix.ends_with('/') {
                        k.starts_with(&prefix)
                    } else {
                        *k == &prefix || k.starts_with(&format!("{prefix}/"))
                    }
                })
                .cloned()
                .collect();
            keys.sort();
            Ok(keys)
        })
    }
}