use std::{
collections::BTreeMap,
sync::{Arc, Mutex},
};
use bytes::{Bytes, BytesMut};
use object_store::{path::Path, GetResult, GetResultPayload, MultipartId, ObjectMeta, PutResult};
use tokio::io::AsyncWrite;
#[derive(Debug)]
struct InMemoryError;
impl std::error::Error for InMemoryError {}
impl std::fmt::Display for InMemoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "InMemoryError")
}
}
pub struct InMemoryStore {
store: Arc<Mutex<BTreeMap<Path, (chrono::DateTime<chrono::Utc>, Bytes)>>>,
}
impl InMemoryStore {
pub fn new() -> Self {
Self {
store: Arc::new(Mutex::new(BTreeMap::new())),
}
}
pub async fn put(&self, location: &Path, bytes: Bytes) -> object_store::Result<PutResult> {
let mut store = self.store.lock().unwrap();
store.insert(location.clone(), (chrono::Utc::now(), bytes));
Ok(PutResult {
e_tag: None,
version: None,
})
}
pub async fn get(&self, location: &Path) -> object_store::Result<GetResult> {
let store = self.store.lock().unwrap();
let (last_modified, data) = store
.get(location)
.ok_or_else(|| object_store::Error::NotFound {
path: location.to_string(),
source: Box::new(InMemoryError),
})?
.clone();
let chunk1_end = data.len() / 3;
let chunk2_end = data.len() * 2 / 3;
let data_chunks = vec![
Ok(data.slice(0..chunk1_end)),
Ok(data.slice(chunk1_end..chunk2_end)),
Ok(data.slice(chunk2_end..)),
];
Ok(GetResult {
range: 0..data.len(),
meta: ObjectMeta {
size: data.len(),
e_tag: None,
version: None,
last_modified,
location: location.clone(),
},
payload: GetResultPayload::Stream(Box::pin(futures::stream::iter(data_chunks))),
})
}
pub async fn delete(&self, location: &Path) -> object_store::Result<()> {
let mut store = self.store.lock().unwrap();
store.remove(location);
Ok(())
}
pub async fn put_multipart(
&self,
location: &Path,
) -> object_store::Result<(MultipartId, Box<dyn AsyncWrite + Unpin + Send>)> {
let writer = MultipartWriter {
location: location.clone(),
acc: BytesMut::new(),
store: self.store.clone(),
};
Ok((location.to_string(), Box::new(writer)))
}
}
struct MultipartWriter {
location: Path,
acc: BytesMut,
store: Arc<Mutex<BTreeMap<Path, (chrono::DateTime<chrono::Utc>, Bytes)>>>,
}
impl AsyncWrite for MultipartWriter {
fn poll_write(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
self.acc.extend_from_slice(buf);
std::task::Poll::Ready(Ok(buf.len()))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_shutdown(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
let data = std::mem::take(&mut self.acc);
let mut store = self.store.lock().unwrap();
store.insert(self.location.clone(), (chrono::Utc::now(), data.freeze()));
std::task::Poll::Ready(Ok(()))
}
}
#[cfg(test)]
mod test {
use bytes::Bytes;
use tokio::io::AsyncWriteExt;
#[tokio::test]
async fn basic_ops() {
let store = super::InMemoryStore::new();
store
.put(&object_store::path::Path::from("foo"), Bytes::from("bar"))
.await
.unwrap();
let get_result = store
.get(&object_store::path::Path::from("foo"))
.await
.unwrap()
.bytes()
.await
.unwrap();
assert_eq!(get_result, Bytes::from("bar"));
store
.delete(&object_store::path::Path::from("foo"))
.await
.unwrap();
let get_result = store
.get(&object_store::path::Path::from("foo"))
.await
.expect_err("Get after delete should fail");
assert!(matches!(get_result, object_store::Error::NotFound { .. }));
}
#[tokio::test]
async fn multipart_upload() {
let store = super::InMemoryStore::new();
let (id, mut writer) = store
.put_multipart(&object_store::path::Path::from("foo"))
.await
.unwrap();
assert_eq!(id, "foo");
writer.write_all(&[1, 2, 3]).await.unwrap();
writer.write_all(&[4, 5, 6]).await.unwrap();
writer.shutdown().await.unwrap();
let get_result = store
.get(&object_store::path::Path::from("foo"))
.await
.unwrap()
.bytes()
.await
.unwrap();
assert_eq!(get_result, Bytes::from(vec![1, 2, 3, 4, 5, 6]));
}
#[tokio::test]
async fn multipart_upload_abort() {
let store = super::InMemoryStore::new();
let (id, mut writer) = store
.put_multipart(&object_store::path::Path::from("foo"))
.await
.unwrap();
assert_eq!(id, "foo");
writer.write_all(&[1, 2, 3]).await.unwrap();
drop(writer);
let get_result = store
.get(&object_store::path::Path::from("foo"))
.await
.expect_err("Get without finishing should fail");
assert!(matches!(get_result, object_store::Error::NotFound { .. }));
}
}