use shardline_protocol::ByteRange;
use crate::{
DeleteOutcome, ObjectBody, ObjectIntegrity, ObjectKey, ObjectMetadata, ObjectPrefix, PutOutcome,
};
pub trait ObjectStore {
type Error;
fn put_if_absent(
&self,
key: &ObjectKey,
body: ObjectBody<'_>,
integrity: &ObjectIntegrity,
) -> Result<PutOutcome, Self::Error>;
fn read_range(&self, key: &ObjectKey, range: ByteRange) -> Result<Vec<u8>, Self::Error>;
fn contains(&self, key: &ObjectKey) -> Result<bool, Self::Error>;
fn metadata(&self, key: &ObjectKey) -> Result<Option<ObjectMetadata>, Self::Error>;
fn list_prefix(&self, prefix: &ObjectPrefix) -> Result<Vec<ObjectMetadata>, Self::Error>;
fn visit_prefix<Visitor, VisitorError>(
&self,
prefix: &ObjectPrefix,
mut visitor: Visitor,
) -> Result<(), VisitorError>
where
Self::Error: Into<VisitorError>,
Visitor: FnMut(ObjectMetadata) -> Result<(), VisitorError>,
{
for metadata in self.list_prefix(prefix).map_err(Into::into)? {
visitor(metadata)?;
}
Ok(())
}
fn delete_if_present(&self, key: &ObjectKey) -> Result<DeleteOutcome, Self::Error>;
}
#[cfg(test)]
mod tests {
use crate::{
DeleteOutcome, ObjectBody, ObjectIntegrity, ObjectKey, ObjectMetadata, ObjectPrefix,
ObjectStore, PutOutcome,
};
struct MinimalStore;
impl ObjectStore for MinimalStore {
type Error = Box<dyn std::error::Error>;
fn put_if_absent(
&self,
_key: &ObjectKey,
_body: ObjectBody<'_>,
_integrity: &ObjectIntegrity,
) -> Result<PutOutcome, Self::Error> {
Err("not implemented".into())
}
fn read_range(
&self,
_key: &ObjectKey,
_range: shardline_protocol::ByteRange,
) -> Result<Vec<u8>, Self::Error> {
Err("not implemented".into())
}
fn contains(&self, _key: &ObjectKey) -> Result<bool, Self::Error> {
Err("not implemented".into())
}
fn metadata(&self, _key: &ObjectKey) -> Result<Option<ObjectMetadata>, Self::Error> {
Err("not implemented".into())
}
fn list_prefix(&self, prefix: &ObjectPrefix) -> Result<Vec<ObjectMetadata>, Self::Error> {
let key = ObjectKey::parse(&format!("{}file.xorb", prefix.as_str()))
.map_err(|e| -> Box<dyn std::error::Error> { format!("bad key: {e}").into() })?;
Ok(vec![ObjectMetadata::new(key, 42, None)])
}
fn delete_if_present(&self, _key: &ObjectKey) -> Result<DeleteOutcome, Self::Error> {
Err("not implemented".into())
}
}
#[test]
fn default_visit_prefix_delegates_to_list_prefix() {
let store = MinimalStore;
let prefix = ObjectPrefix::parse("ns/").expect("valid prefix");
let mut visited = Vec::new();
let result: Result<(), Box<dyn std::error::Error>> = store.visit_prefix(&prefix, |meta| {
visited.push(meta.key().clone());
Ok(())
});
assert!(result.is_ok());
assert_eq!(visited.len(), 1);
assert_eq!(visited[0].as_str(), "ns/file.xorb");
}
#[test]
fn default_visit_prefix_propagates_visitor_error() {
let store = MinimalStore;
let prefix = ObjectPrefix::parse("ns/").expect("valid prefix");
let result: Result<(), Box<dyn std::error::Error>> =
store.visit_prefix(&prefix, |_meta| Err("visitor rejected".into()));
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "visitor rejected");
}
}