#![doc(html_root_url = "https://docs.rs/eidetic/0.0.1")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Request {
LoadBlob { key: String },
SaveBlob { key: String, value: Vec<u8> },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Response {
BlobLoaded { key: String, value: Option<Vec<u8>> },
BlobSaved { key: String },
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub message: String,
}
impl Error {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.message)
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
pub trait Store {
fn load_blob(&mut self, key: &str) -> Result<Option<Vec<u8>>>;
fn save_blob(&mut self, key: &str, value: &[u8]) -> Result<()>;
}
pub fn dispatch(store: &mut dyn Store, request: &Request) -> Result<Response> {
match request {
Request::LoadBlob { key } => Ok(Response::BlobLoaded {
key: key.clone(),
value: store.load_blob(key)?,
}),
Request::SaveBlob { key, value } => {
store.save_blob(key, value)?;
Ok(Response::BlobSaved { key: key.clone() })
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[derive(Default)]
struct InMemoryStore {
blobs: HashMap<String, Vec<u8>>,
}
impl Store for InMemoryStore {
fn load_blob(&mut self, key: &str) -> Result<Option<Vec<u8>>> {
Ok(self.blobs.get(key).cloned())
}
fn save_blob(&mut self, key: &str, value: &[u8]) -> Result<()> {
self.blobs.insert(key.to_string(), value.to_vec());
Ok(())
}
}
#[test]
fn dispatch_round_trips_save_then_load() {
let mut store = InMemoryStore::default();
let saved = dispatch(
&mut store,
&Request::SaveBlob {
key: "k".into(),
value: b"hello".to_vec(),
},
)
.unwrap();
assert_eq!(
saved,
Response::BlobSaved {
key: "k".to_string()
}
);
let loaded = dispatch(&mut store, &Request::LoadBlob { key: "k".into() }).unwrap();
assert_eq!(
loaded,
Response::BlobLoaded {
key: "k".to_string(),
value: Some(b"hello".to_vec()),
}
);
}
#[test]
fn dispatch_load_returns_none_for_unknown_key() {
let mut store = InMemoryStore::default();
let loaded = dispatch(
&mut store,
&Request::LoadBlob {
key: "missing".into(),
},
)
.unwrap();
assert_eq!(
loaded,
Response::BlobLoaded {
key: "missing".to_string(),
value: None,
}
);
}
#[test]
fn dispatch_propagates_store_errors() {
struct FailingStore;
impl Store for FailingStore {
fn load_blob(&mut self, _key: &str) -> Result<Option<Vec<u8>>> {
Err(Error::new("disk on fire"))
}
fn save_blob(&mut self, _key: &str, _value: &[u8]) -> Result<()> {
Err(Error::new("disk on fire"))
}
}
let err = dispatch(&mut FailingStore, &Request::LoadBlob { key: "k".into() }).unwrap_err();
assert_eq!(err.message, "disk on fire");
}
}