use std::collections::BTreeMap;
use std::sync::Arc;
use parking_lot::Mutex;
use crate::backend::{clamped, Backend, ReadCursor, StagingCursor};
use crate::Error;
#[derive(Clone, Default)]
pub(crate) struct MemBackend {
rows: Arc<Mutex<BTreeMap<Vec<u8>, Vec<u8>>>>,
#[cfg(test)]
fail_commit: bool,
}
impl Backend for MemBackend {
type Cursor = MemCursor;
type Staging = MemStaging;
fn name(&self) -> &'static str {
"mem"
}
async fn has(&self, key: &[u8]) -> Result<bool, Error> {
Ok(self.rows.lock().contains_key(key))
}
async fn get(&self, key: &[u8]) -> Result<Option<Self::Cursor>, Error> {
Ok(self
.rows
.lock()
.get(key)
.cloned()
.map(|value| MemCursor { value }))
}
async fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error> {
self.rows
.lock()
.entry(key.to_vec())
.or_insert_with(|| value.to_vec());
Ok(())
}
async fn put_staged(&self) -> Result<Self::Staging, Error> {
Ok(MemStaging {
staged: Vec::new(),
rows: Arc::clone(&self.rows),
#[cfg(test)]
fail_commit: self.fail_commit,
})
}
async fn delete(&self, key: &[u8]) -> Result<(), Error> {
match self.rows.lock().remove(key) {
Some(_) => Ok(()),
None => Err(Error::Missing),
}
}
async fn list(&self) -> Result<Vec<Vec<u8>>, Error> {
Ok(self.rows.lock().keys().cloned().collect())
}
async fn size(&self, key: &[u8]) -> Result<Option<u64>, Error> {
Ok(self.rows.lock().get(key).map(|value| value.len() as u64))
}
}
pub(crate) struct MemCursor {
value: Vec<u8>,
}
impl ReadCursor for MemCursor {
fn len(&self) -> u64 {
self.value.len() as u64
}
async fn read_at(&self, offset: u64, len: u64) -> Result<Vec<u8>, Error> {
let (start, end) = clamped(offset, len, self.value.len());
Ok(self.value[start..end].to_vec())
}
}
pub(crate) struct MemStaging {
staged: Vec<u8>,
rows: Arc<Mutex<BTreeMap<Vec<u8>, Vec<u8>>>>,
#[cfg(test)]
fail_commit: bool,
}
impl ReadCursor for MemStaging {
fn len(&self) -> u64 {
self.staged.len() as u64
}
async fn read_at(&self, offset: u64, len: u64) -> Result<Vec<u8>, Error> {
let (start, end) = clamped(offset, len, self.staged.len());
Ok(self.staged[start..end].to_vec())
}
}
impl StagingCursor for MemStaging {
async fn append(&mut self, chunk: &[u8]) -> Result<(), Error> {
self.staged.extend_from_slice(chunk);
Ok(())
}
async fn commit(mut self, key: &[u8]) -> Result<(), Error> {
#[cfg(test)]
if self.fail_commit {
self.staged.clear();
return Err(Error::Io(
"engine commit failure (mem, injected)".to_owned(),
));
}
let value = std::mem::take(&mut self.staged);
self.rows.lock().entry(key.to_vec()).or_insert(value);
Ok(())
}
async fn discard(mut self) {
self.staged.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &[u8] = b"the quick brown fox jumps over the lazy dog";
async fn stored_value(backend: &MemBackend, key: &[u8]) -> Option<Vec<u8>> {
let cursor = backend.get(key).await.expect("get succeeds")?;
let (bytes, _) = cursor.read_range(0, cursor.len()).await.expect("read");
Some(bytes)
}
#[tokio::test]
async fn backend_method_contract() {
let backend = MemBackend::default();
let key = b"\x01opaque-key-bytes".to_vec();
assert_eq!(backend.name(), "mem", "reporting identity");
assert!(
!backend.has(&key).await.unwrap(),
"virgin store: has is a no-op read"
);
assert!(
backend.get(&key).await.unwrap().is_none(),
"virgin store: get is a no-op read"
);
assert_eq!(
backend.size(&key).await.unwrap(),
None,
"virgin store: size is a no-op read"
);
assert!(
backend.list().await.unwrap().is_empty(),
"virgin store: list is empty"
);
assert_eq!(
backend.delete(&key).await.unwrap_err(),
Error::Missing,
"virgin store: delete reports Missing"
);
backend.put(&key, CONTENT).await.unwrap();
assert!(backend.has(&key).await.unwrap(), "put lands");
assert_eq!(
backend.size(&key).await.unwrap(),
Some(CONTENT.len() as u64),
"size is metadata-only and exact"
);
backend.put(&key, b"overwritten").await.unwrap();
assert_eq!(
stored_value(&backend, &key).await.as_deref(),
Some(CONTENT),
"CAS first-writer wins: re-put is a no-op"
);
assert_eq!(
backend.list().await.unwrap(),
vec![key.clone()],
"list is complete, keys not values"
);
backend.delete(&key).await.unwrap();
assert!(!backend.has(&key).await.unwrap(), "delete lands");
assert_eq!(
backend.delete(&key).await.unwrap_err(),
Error::Missing,
"delete reports Missing when absent"
);
}
#[tokio::test]
async fn list_is_complete_and_well_formed() {
let backend = MemBackend::default();
let keys = [
b"\x02b-key".to_vec(),
b"\x02a-key".to_vec(),
b"\x02c-key".to_vec(),
];
for key in &keys {
backend.put(key, b"value").await.unwrap();
}
let mut listed = backend.list().await.unwrap();
let mut expected = keys.to_vec();
listed.sort();
expected.sort();
assert_eq!(listed, expected, "list is complete and well-formed");
for key in &expected {
assert!(
backend.has(key).await.unwrap(),
"no spurious or mangled keys"
);
}
}
#[tokio::test]
async fn cursor_contract_materialized_body() {
let backend = MemBackend::default();
let key = b"\x01cursor-key".to_vec();
backend.put(&key, CONTENT).await.unwrap();
let cursor = backend
.get(&key)
.await
.expect("get succeeds")
.expect("entry present");
let len = CONTENT.len() as u64;
assert_eq!(cursor.len(), len, "len is the entry length");
assert_eq!(cursor.read_at(0, len).await.unwrap(), CONTENT, "full read");
assert_eq!(
cursor.read_at(0, len + 17).await.unwrap(),
CONTENT,
"over-read clamps at EOF, pread-style"
);
let mid = (CONTENT.len() / 2) as u64;
assert_eq!(
cursor.read_at(mid, len).await.unwrap(),
&CONTENT[mid as usize..],
"tail read clamps"
);
assert_eq!(
cursor.read_at(len, 5).await.unwrap(),
Vec::<u8>::new(),
"read at EOF is empty, never an error"
);
assert_eq!(
cursor.read_at(0, 0).await.unwrap(),
Vec::<u8>::new(),
"zero-length read"
);
let (slice, digest) = cursor.read_range(mid, 7).await.unwrap();
assert_eq!(slice, &CONTENT[mid as usize..(mid as usize + 7)]);
assert_eq!(digest, crate::backend::slice_digest(&slice));
}
#[tokio::test]
async fn empty_entry_round_trips() {
let backend = MemBackend::default();
let key = b"\x01empty-key".to_vec();
backend.put(&key, b"").await.unwrap();
assert!(backend.has(&key).await.unwrap(), "empty value still lands");
assert_eq!(backend.size(&key).await.unwrap(), Some(0), "exact zero");
let cursor = backend.get(&key).await.unwrap().expect("entry present");
assert_eq!(cursor.len(), 0);
assert_eq!(cursor.read_at(0, 8).await.unwrap(), Vec::<u8>::new());
let staged_key = b"\x01empty-staged-key".to_vec();
let mut staging = backend.put_staged().await.unwrap();
staging.append(b"").await.unwrap();
assert_eq!(staging.len(), 0, "empty append stays empty");
staging
.commit(&staged_key)
.await
.expect("empty commit publishes");
assert!(
backend.has(&staged_key).await.unwrap(),
"empty entry landed"
);
assert_eq!(backend.size(&staged_key).await.unwrap(), Some(0));
let committed = backend
.get(&staged_key)
.await
.expect("get succeeds")
.expect("entry present");
assert_eq!(
committed.read_at(0, 8).await.unwrap(),
Vec::<u8>::new(),
"committed empty body reads empty"
);
}
#[tokio::test]
async fn staged_put_round_trips_through_the_read_seam() {
let backend = MemBackend::default();
let key = b"\x01staged-key".to_vec();
let mut staging = backend.put_staged().await.unwrap();
staging.append(&CONTENT[..8]).await.unwrap();
staging.append(&CONTENT[8..16]).await.unwrap();
staging.append(&CONTENT[16..]).await.unwrap();
assert_eq!(
staging.len() as usize,
CONTENT.len(),
"staged length is the appended length"
);
let (bytes, digest) = staging
.read_range(0, staging.len())
.await
.expect("staged bytes readable");
assert_eq!(bytes, CONTENT, "staged artifact readable as a whole");
assert_eq!(digest, crate::backend::slice_digest(CONTENT));
staging.commit(&key).await.expect("commit publishes");
assert!(backend.has(&key).await.unwrap(), "entry published");
let cursor = backend
.get(&key)
.await
.expect("get succeeds")
.expect("entry present");
let (bytes, digest) = cursor
.read_range(0, cursor.len())
.await
.expect("committed read");
assert_eq!(bytes, CONTENT);
assert_eq!(digest, crate::backend::slice_digest(CONTENT));
backend.put(&key, b"overwritten").await.unwrap();
assert_eq!(
stored_value(&backend, &key).await.as_deref(),
Some(CONTENT),
"whole-value re-put does not overwrite"
);
let mut second = backend.put_staged().await.unwrap();
second.append(b"overwritten-by-stage").await.unwrap();
second
.commit(&key)
.await
.expect("commit is dedup, not failure");
assert_eq!(
stored_value(&backend, &key).await.as_deref(),
Some(CONTENT),
"staged re-commit does not overwrite"
);
}
#[tokio::test]
async fn staged_discard_leaves_zero_residue() {
let backend = MemBackend::default();
let key = b"\x01discard-key".to_vec();
let mut staging = backend.put_staged().await.unwrap();
staging.append(b"residue").await.unwrap();
staging.discard().await;
assert!(
!backend.has(&key).await.unwrap(),
"discarded stage published nothing"
);
assert_eq!(backend.size(&key).await.unwrap(), None, "no entry landed");
assert!(
backend.list().await.unwrap().is_empty(),
"the table holds exactly zero entries after discard"
);
}
#[tokio::test]
async fn commit_failure_self_discards() {
let backend = MemBackend {
fail_commit: true,
..Default::default()
};
let key = b"\x01failure-key".to_vec();
let mut staging = backend.put_staged().await.unwrap();
staging.append(CONTENT).await.unwrap();
let err = staging.commit(&key).await.unwrap_err();
assert!(matches!(err, Error::Io(_)), "engine media failure");
assert!(
!backend.has(&key).await.unwrap(),
"failed commit published nothing"
);
assert_eq!(backend.size(&key).await.unwrap(), None, "zero residue");
assert!(
backend.list().await.unwrap().is_empty(),
"zero residue in the table itself"
);
}
}