use std::{collections::HashMap, sync::RwLock};
use crate::{
object::{Action, ActionId, Blob, ChangeId, ContentHash, State, Tree},
store::{HeddleError, ObjectStore, Result},
};
#[derive(Default)]
pub struct InMemoryStore {
blobs: RwLock<HashMap<ContentHash, Vec<u8>>>,
trees: RwLock<HashMap<ContentHash, Vec<u8>>>,
states: RwLock<HashMap<ChangeId, Vec<u8>>>,
actions: RwLock<HashMap<ActionId, Vec<u8>>>,
}
impl InMemoryStore {
pub fn new() -> Self {
Self::default()
}
}
impl ObjectStore for InMemoryStore {
fn get_blob(&self, hash: &ContentHash) -> Result<Option<Blob>> {
Ok(self
.blobs
.read()
.unwrap()
.get(hash)
.map(|v| Blob::new(v.clone())))
}
fn put_blob(&self, blob: &Blob) -> Result<ContentHash> {
let hash = blob.hash();
self.blobs
.write()
.unwrap()
.insert(hash, blob.content().to_vec());
Ok(hash)
}
fn has_blob(&self, hash: &ContentHash) -> Result<bool> {
Ok(self.blobs.read().unwrap().contains_key(hash))
}
fn blob_size(&self, hash: &ContentHash) -> Result<Option<u64>> {
Ok(self.blobs.read().unwrap().get(hash).map(|v| v.len() as u64))
}
fn list_blobs(&self) -> Result<Vec<ContentHash>> {
Ok(self.blobs.read().unwrap().keys().copied().collect())
}
fn get_tree(&self, hash: &ContentHash) -> Result<Option<Tree>> {
match self.trees.read().unwrap().get(hash) {
Some(bytes) => Ok(Some(rmp_serde::from_slice(bytes)?)),
None => Ok(None),
}
}
fn put_tree(&self, tree: &Tree) -> Result<ContentHash> {
let hash = tree.hash();
self.trees
.write()
.unwrap()
.insert(hash, rmp_serde::to_vec(tree)?);
Ok(hash)
}
fn has_tree(&self, hash: &ContentHash) -> Result<bool> {
Ok(self.trees.read().unwrap().contains_key(hash))
}
fn list_trees(&self) -> Result<Vec<ContentHash>> {
Ok(self.trees.read().unwrap().keys().copied().collect())
}
fn get_state(&self, id: &ChangeId) -> Result<Option<State>> {
match self.states.read().unwrap().get(id) {
Some(bytes) => Ok(Some(rmp_serde::from_slice(bytes)?)),
None => Ok(None),
}
}
fn put_state(&self, state: &State) -> Result<()> {
self.states
.write()
.unwrap()
.insert(state.change_id, rmp_serde::to_vec(state)?);
Ok(())
}
fn has_state(&self, id: &ChangeId) -> Result<bool> {
Ok(self.states.read().unwrap().contains_key(id))
}
fn list_states(&self) -> Result<Vec<ChangeId>> {
Ok(self.states.read().unwrap().keys().copied().collect())
}
fn get_action(&self, id: &ActionId) -> Result<Option<Action>> {
match self.actions.read().unwrap().get(id) {
Some(bytes) => {
let action: Action = rmp_serde::from_slice(bytes)?;
let found_id = action.compute_id();
if found_id != *id {
return Err(HeddleError::InvalidObject(format!(
"action id mismatch: requested {}, found {}",
id, found_id
)));
}
Ok(Some(action))
}
None => Ok(None),
}
}
fn put_action(&self, action: &mut Action) -> Result<ActionId> {
let id = action.id();
self.actions
.write()
.unwrap()
.insert(id, rmp_serde::to_vec(action)?);
Ok(id)
}
fn list_actions(&self) -> Result<Vec<ActionId>> {
Ok(self.actions.read().unwrap().keys().copied().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compliance() {
let store = InMemoryStore::new();
crate::store::store_compliance::run_compliance_tests(&store);
}
#[test]
fn test_blob_put_idempotent() {
let store = InMemoryStore::new();
let blob = Blob::from("idempotent");
let h1 = store.put_blob(&blob).unwrap();
let h2 = store.put_blob(&blob).unwrap();
assert_eq!(h1, h2);
assert_eq!(store.list_blobs().unwrap().len(), 1);
}
#[test]
fn test_has_blob_unknown() {
let store = InMemoryStore::new();
let hash = ContentHash::compute(b"never-stored");
assert!(!store.has_blob(&hash).unwrap());
}
}