use std::future::Future;
use std::pin::Pin;
pub type ReplicatedStorageResult<T> = Result<T, ReplicatedStorageError>;
pub type ReplicatedStorageFuture<'a, T> =
Pin<Box<dyn Future<Output = ReplicatedStorageResult<T>> + Send + 'a>>;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ReplicatedStorageError {
#[error("replicated storage lock for {resource} was poisoned")]
LockPoisoned { resource: &'static str },
}
impl From<ReplicatedStorageError> for String {
fn from(error: ReplicatedStorageError) -> Self {
error.to_string()
}
}
pub trait ReplicatedStorage: Send + Sync {
fn store<'a>(&'a mut self, key: &'a [u8], value: &'a [u8]) -> ReplicatedStorageFuture<'a, ()>;
fn retrieve<'a>(&'a self, key: &'a [u8]) -> ReplicatedStorageFuture<'a, Option<Vec<u8>>>;
fn delete<'a>(&'a mut self, key: &'a [u8]) -> ReplicatedStorageFuture<'a, bool>;
fn exists<'a>(&'a self, key: &'a [u8]) -> ReplicatedStorageFuture<'a, bool>;
}
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[derive(Default, Clone)]
pub struct BasicReplicatedStorage {
data: Arc<Mutex<HashMap<Vec<u8>, Vec<u8>>>>,
}
impl ReplicatedStorage for BasicReplicatedStorage {
fn store<'a>(&'a mut self, key: &'a [u8], value: &'a [u8]) -> ReplicatedStorageFuture<'a, ()> {
let data_clone = Arc::clone(&self.data);
let key_vec = key.to_vec();
let value_vec = value.to_vec();
Box::pin(async move {
let mut data = data_clone
.lock()
.map_err(|_| ReplicatedStorageError::LockPoisoned {
resource: "replicated storage data",
})?;
data.insert(key_vec, value_vec);
Ok(())
})
}
fn retrieve<'a>(&'a self, key: &'a [u8]) -> ReplicatedStorageFuture<'a, Option<Vec<u8>>> {
let data_clone = Arc::clone(&self.data);
let key_vec = key.to_vec();
Box::pin(async move {
let data = data_clone
.lock()
.map_err(|_| ReplicatedStorageError::LockPoisoned {
resource: "replicated storage data",
})?;
Ok(data.get(&key_vec).cloned())
})
}
fn delete<'a>(&'a mut self, key: &'a [u8]) -> ReplicatedStorageFuture<'a, bool> {
let data_clone = Arc::clone(&self.data);
let key_vec = key.to_vec();
Box::pin(async move {
let mut data = data_clone
.lock()
.map_err(|_| ReplicatedStorageError::LockPoisoned {
resource: "replicated storage data",
})?;
Ok(data.remove(&key_vec).is_some())
})
}
fn exists<'a>(&'a self, key: &'a [u8]) -> ReplicatedStorageFuture<'a, bool> {
let data_clone = Arc::clone(&self.data);
let key_vec = key.to_vec();
Box::pin(async move {
let data = data_clone
.lock()
.map_err(|_| ReplicatedStorageError::LockPoisoned {
resource: "replicated storage data",
})?;
Ok(data.contains_key(&key_vec))
})
}
}
#[cfg(test)]
mod tests {
use super::{BasicReplicatedStorage, ReplicatedStorage, ReplicatedStorageError};
#[tokio::test]
async fn in_memory_storage_round_trips_values() {
let mut storage = BasicReplicatedStorage::default();
assert!(!storage.exists(b"key").await.expect("exists"));
assert_eq!(storage.retrieve(b"key").await.expect("retrieve"), None);
storage.store(b"key", b"value").await.expect("store");
assert!(storage.exists(b"key").await.expect("exists"));
assert_eq!(
storage.retrieve(b"key").await.expect("retrieve"),
Some(b"value".to_vec())
);
assert!(storage.delete(b"key").await.expect("delete"));
assert!(!storage.delete(b"key").await.expect("delete missing"));
assert_eq!(storage.retrieve(b"key").await.expect("retrieve"), None);
}
#[tokio::test]
async fn in_memory_storage_clones_share_state() {
let mut first = BasicReplicatedStorage::default();
let second = first.clone();
first.store(b"shared", b"value").await.expect("store");
assert_eq!(
second.retrieve(b"shared").await.expect("retrieve"),
Some(b"value".to_vec())
);
}
#[tokio::test]
async fn lock_poisoning_reports_typed_error() {
let storage = BasicReplicatedStorage::default();
let poisoned = storage.clone();
let thread_result = std::thread::spawn(move || {
let _guard = poisoned.data.lock().expect("lock storage");
panic!("poison storage lock");
})
.join();
assert!(thread_result.is_err());
let err = storage.retrieve(b"key").await.unwrap_err();
assert_eq!(
err,
ReplicatedStorageError::LockPoisoned {
resource: "replicated storage data"
}
);
}
}