use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::Mutex;
use crate::error::StorageError;
pub trait StorageBackend: Send + Sync {
fn name(&self) -> &str;
fn read(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError>;
fn write(&self, key: &str, bytes: &[u8]) -> Result<(), StorageError>;
fn delete(&self, key: &str) -> Result<(), StorageError> {
let _ = key;
Ok(())
}
fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
let _ = prefix;
Err(StorageError::BackendNoListSupport {
tier: self.name().to_string(),
})
}
fn flush(&self) -> Result<(), StorageError> {
Ok(())
}
}
#[derive(Debug)]
pub struct MemoryBackend {
name: String,
data: Mutex<HashMap<String, Vec<u8>>>,
}
impl Default for MemoryBackend {
fn default() -> Self {
Self::new()
}
}
impl MemoryBackend {
#[must_use]
pub fn new() -> Self {
Self {
name: "memory".into(),
data: Mutex::new(HashMap::new()),
}
}
#[must_use]
pub fn with_name(name: impl Into<String>) -> Self {
Self {
name: name.into(),
data: Mutex::new(HashMap::new()),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.data.lock().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.data.lock().is_empty()
}
}
impl StorageBackend for MemoryBackend {
fn name(&self) -> &str {
&self.name
}
fn read(&self, key: &str) -> Result<Option<Vec<u8>>, StorageError> {
Ok(self.data.lock().get(key).cloned())
}
fn write(&self, key: &str, bytes: &[u8]) -> Result<(), StorageError> {
self.data.lock().insert(key.to_string(), bytes.to_vec());
Ok(())
}
fn delete(&self, key: &str) -> Result<(), StorageError> {
self.data.lock().remove(key);
Ok(())
}
fn list(&self, prefix: &str) -> Result<Vec<String>, StorageError> {
let guard = self.data.lock();
let mut keys: Vec<String> = if prefix.is_empty() {
guard.keys().cloned().collect()
} else {
guard
.keys()
.filter(|k| k.starts_with(prefix))
.cloned()
.collect()
};
keys.sort();
Ok(keys)
}
}
#[must_use]
pub fn memory_backend() -> Arc<MemoryBackend> {
Arc::new(MemoryBackend::new())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn memory_backend_read_write_round_trip() {
let b = MemoryBackend::new();
assert!(b.is_empty());
b.write("k1", b"hello").unwrap();
assert_eq!(b.read("k1").unwrap(), Some(b"hello".to_vec()));
assert_eq!(b.len(), 1);
}
#[test]
fn memory_backend_read_miss_returns_none() {
let b = MemoryBackend::new();
assert!(b.read("nope").unwrap().is_none());
}
#[test]
fn memory_backend_delete_removes_key() {
let b = MemoryBackend::new();
b.write("k", b"v").unwrap();
b.delete("k").unwrap();
assert!(b.read("k").unwrap().is_none());
}
#[test]
fn memory_backend_list_lex_asc() {
let b = MemoryBackend::new();
b.write("g/10", b"a").unwrap();
b.write("g/02", b"b").unwrap();
b.write("g/01", b"c").unwrap();
b.write("other", b"d").unwrap();
let keys = b.list("g/").unwrap();
assert_eq!(keys, vec!["g/01", "g/02", "g/10"]);
}
#[test]
fn memory_backend_list_empty_prefix_returns_all() {
let b = MemoryBackend::new();
b.write("a", b"x").unwrap();
b.write("b", b"y").unwrap();
let keys = b.list("").unwrap();
assert_eq!(keys, vec!["a", "b"]);
}
#[test]
fn memory_backend_with_custom_name() {
let b = MemoryBackend::with_name("test");
assert_eq!(b.name(), "test");
}
#[test]
fn memory_backend_factory_returns_shared_arc() {
let b = memory_backend();
let b2 = Arc::clone(&b);
b.write("k", b"v").unwrap();
assert_eq!(b2.read("k").unwrap(), Some(b"v".to_vec()));
}
#[test]
fn default_list_returns_backend_no_list_support() {
struct NoList;
impl StorageBackend for NoList {
fn name(&self) -> &'static str {
"no-list"
}
fn read(&self, _key: &str) -> Result<Option<Vec<u8>>, StorageError> {
Ok(None)
}
fn write(&self, _k: &str, _b: &[u8]) -> Result<(), StorageError> {
Ok(())
}
}
let b = NoList;
let r = b.list("g/");
assert!(matches!(r, Err(StorageError::BackendNoListSupport { .. })));
}
}