use serde::Serialize;
use std::path::Path;
#[async_trait::async_trait]
pub trait StorageBackend: Send + Sync {
async fn put(&self, key: &[u8], value: &[u8]) -> anyhow::Result<()>;
async fn get(&self, key: &[u8]) -> anyhow::Result<Option<Vec<u8>>>;
async fn delete(&self, key: &[u8]) -> anyhow::Result<()>;
async fn list_prefix(&self, prefix: &[u8]) -> anyhow::Result<Vec<Vec<u8>>>;
async fn batch_put(&self, items: Vec<(Vec<u8>, Vec<u8>)>) -> anyhow::Result<()>;
}
pub struct PersistentStore {
db: sled::Db,
}
impl PersistentStore {
pub fn new<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let db = sled::open(path)?;
Ok(Self { db })
}
pub fn memory() -> anyhow::Result<Self> {
let db = sled::Config::new().temporary(true).open()?;
Ok(Self { db })
}
pub async fn flush(&self) -> anyhow::Result<()> {
self.db.flush_async().await?;
Ok(())
}
pub fn size_on_disk(&self) -> anyhow::Result<u64> {
Ok(self.db.size_on_disk()?)
}
pub fn len(&self) -> usize {
self.db.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[async_trait::async_trait]
impl StorageBackend for PersistentStore {
async fn put(&self, key: &[u8], value: &[u8]) -> anyhow::Result<()> {
self.db.insert(key, value)?;
Ok(())
}
async fn get(&self, key: &[u8]) -> anyhow::Result<Option<Vec<u8>>> {
Ok(self.db.get(key)?.map(|v| v.to_vec()))
}
async fn delete(&self, key: &[u8]) -> anyhow::Result<()> {
self.db.remove(key)?;
Ok(())
}
async fn list_prefix(&self, prefix: &[u8]) -> anyhow::Result<Vec<Vec<u8>>> {
let keys: Vec<Vec<u8>> = self
.db
.scan_prefix(prefix)
.keys()
.filter_map(|k| k.ok().map(|k| k.to_vec()))
.collect();
Ok(keys)
}
async fn batch_put(&self, items: Vec<(Vec<u8>, Vec<u8>)>) -> anyhow::Result<()> {
let mut batch = sled::Batch::default();
for (key, value) in items {
batch.insert(key, value);
}
self.db.apply_batch(batch)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_persistent_store_operations() {
let store = PersistentStore::memory().unwrap();
store.put(b"key1", b"value1").await.unwrap();
assert_eq!(store.len(), 1);
let value = store.get(b"key1").await.unwrap();
assert_eq!(value, Some(b"value1".to_vec()));
store.delete(b"key1").await.unwrap();
assert_eq!(store.len(), 0);
}
#[tokio::test]
async fn test_batch_operations() {
let store = PersistentStore::memory().unwrap();
let items = vec![
(b"key1".to_vec(), b"value1".to_vec()),
(b"key2".to_vec(), b"value2".to_vec()),
(b"key3".to_vec(), b"value3".to_vec()),
];
store.batch_put(items).await.unwrap();
assert_eq!(store.len(), 3);
}
#[tokio::test]
async fn test_prefix_scan() {
let store = PersistentStore::memory().unwrap();
store.put(b"prefix:key1", b"value1").await.unwrap();
store.put(b"prefix:key2", b"value2").await.unwrap();
store.put(b"other:key3", b"value3").await.unwrap();
let keys = store.list_prefix(b"prefix:").await.unwrap();
assert_eq!(keys.len(), 2);
}
}