use spate_core::framing::RecordFramer;
use std::collections::VecDeque;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
pub(crate) struct TestLineFramer {
partial: Vec<u8>,
ready: VecDeque<Vec<u8>>,
decoded_bytes: u64,
max_record_bytes: usize,
}
impl TestLineFramer {
pub(crate) fn new(max_record_bytes: usize) -> TestLineFramer {
TestLineFramer {
partial: Vec::new(),
ready: VecDeque::new(),
decoded_bytes: 0,
max_record_bytes,
}
}
fn push_partial(&mut self, bytes: &[u8]) -> io::Result<()> {
if self.partial.len() + bytes.len() > self.max_record_bytes {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"record line exceeds the configured max_record_bytes ({})",
self.max_record_bytes
),
));
}
self.partial.extend_from_slice(bytes);
Ok(())
}
fn complete_line(&mut self) {
if self.partial.last() == Some(&b'\r') {
self.partial.pop();
}
if self.partial.iter().all(u8::is_ascii_whitespace) {
self.partial.clear();
return;
}
self.ready.push_back(std::mem::take(&mut self.partial));
}
}
impl RecordFramer for TestLineFramer {
fn push(&mut self, bytes: &[u8]) -> io::Result<()> {
self.decoded_bytes += bytes.len() as u64;
let mut rest = bytes;
while let Some(nl) = rest.iter().position(|&b| b == b'\n') {
self.push_partial(&rest[..nl])?;
self.complete_line();
rest = &rest[nl + 1..];
}
self.push_partial(rest)
}
fn finish(&mut self) -> io::Result<()> {
if !self.partial.is_empty() {
self.complete_line();
}
Ok(())
}
fn pop(&mut self) -> Option<Vec<u8>> {
self.ready.pop_front()
}
fn decoded_bytes(&self) -> u64 {
self.decoded_bytes
}
fn resync_delimiter(&self) -> Option<u8> {
Some(b'\n')
}
}
pub(crate) type GetHook =
fn(&object_store::path::Path) -> Option<object_store::Result<object_store::GetResult>>;
#[derive(Debug)]
pub(crate) enum PutAnswer {
Fail(object_store::Error),
StoreThenFail(object_store::Error),
}
pub(crate) type PutHook = fn(&object_store::path::Path) -> Option<PutAnswer>;
pub(crate) type DeleteHook = fn(&object_store::path::Path) -> Option<object_store::Error>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Event {
Put(String, Vec<u8>),
Delete(String),
Published(String),
Confirmed(String),
}
pub(crate) type EventLog = std::sync::Arc<std::sync::Mutex<Vec<Event>>>;
#[derive(Debug, Default)]
pub(crate) struct HookStore {
pub(crate) inner: object_store::memory::InMemory,
pub(crate) get: Option<GetHook>,
pub(crate) put: Option<PutHook>,
pub(crate) delete: Option<DeleteHook>,
pub(crate) reverse_list: bool,
pub(crate) creates_exist: bool,
pub(crate) creates_overwrite: bool,
pub(crate) creates: AtomicUsize,
pub(crate) offsets: std::sync::Mutex<Vec<object_store::path::Path>>,
pub(crate) events: Option<EventLog>,
}
impl HookStore {
fn log(&self, event: Event) {
if let Some(events) = &self.events {
events.lock().unwrap().push(event);
}
}
}
impl std::fmt::Display for HookStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "HookStore({})", self.inner)
}
}
#[async_trait::async_trait]
impl object_store::ObjectStore for HookStore {
async fn put_opts(
&self,
location: &object_store::path::Path,
payload: object_store::PutPayload,
opts: object_store::PutOptions,
) -> object_store::Result<object_store::PutResult> {
let body: Vec<u8> = payload.iter().flat_map(|b| b.iter().copied()).collect();
match self.put.and_then(|put| put(location)) {
Some(PutAnswer::Fail(e)) => return Err(e),
Some(PutAnswer::StoreThenFail(e)) => {
self.inner.put_opts(location, payload, opts).await?;
self.log(Event::Put(location.to_string(), body));
return Err(e);
}
None => {}
}
let mut opts = opts;
if matches!(opts.mode, object_store::PutMode::Create) {
self.creates.fetch_add(1, Ordering::SeqCst);
if self.creates_exist {
return Err(object_store::Error::AlreadyExists {
path: location.to_string(),
source: "scripted".into(),
});
}
if self.creates_overwrite {
opts.mode = object_store::PutMode::Overwrite;
}
}
let put = self.inner.put_opts(location, payload, opts).await?;
self.log(Event::Put(location.to_string(), body));
Ok(put)
}
async fn put_multipart_opts(
&self,
location: &object_store::path::Path,
opts: object_store::PutMultipartOptions,
) -> object_store::Result<Box<dyn object_store::MultipartUpload>> {
self.inner.put_multipart_opts(location, opts).await
}
async fn get_opts(
&self,
location: &object_store::path::Path,
options: object_store::GetOptions,
) -> object_store::Result<object_store::GetResult> {
if let Some(answer) = self.get.and_then(|get| get(location)) {
return answer;
}
self.inner.get_opts(location, options).await
}
fn delete_stream(
&self,
locations: futures_util::stream::BoxStream<
'static,
object_store::Result<object_store::path::Path>,
>,
) -> futures_util::stream::BoxStream<'static, object_store::Result<object_store::path::Path>>
{
use futures_util::StreamExt as _;
let events = self.events.clone();
let hook = self.delete;
let locations = locations
.map(move |location| match (hook, location) {
(Some(hook), Ok(path)) => hook(&path).map_or(Ok(path), Err),
(_, location) => location,
})
.boxed();
self.inner
.delete_stream(locations)
.inspect(move |deleted| {
if let (Some(events), Ok(path)) = (&events, deleted) {
events.lock().unwrap().push(Event::Delete(path.to_string()));
}
})
.boxed()
}
fn list(
&self,
prefix: Option<&object_store::path::Path>,
) -> futures_util::stream::BoxStream<'static, object_store::Result<object_store::ObjectMeta>>
{
use futures_util::StreamExt as _;
let listing = self.inner.list(prefix);
if !self.reverse_list {
return listing;
}
futures_util::stream::once(async move {
let mut all: Vec<_> = listing.collect().await;
all.reverse();
futures_util::stream::iter(all)
})
.flatten()
.boxed()
}
fn list_with_offset(
&self,
prefix: Option<&object_store::path::Path>,
offset: &object_store::path::Path,
) -> futures_util::stream::BoxStream<'static, object_store::Result<object_store::ObjectMeta>>
{
self.offsets.lock().unwrap().push(offset.clone());
self.inner.list_with_offset(prefix, offset)
}
async fn list_with_delimiter(
&self,
prefix: Option<&object_store::path::Path>,
) -> object_store::Result<object_store::ListResult> {
self.inner.list_with_delimiter(prefix).await
}
async fn copy_opts(
&self,
from: &object_store::path::Path,
to: &object_store::path::Path,
options: object_store::CopyOptions,
) -> object_store::Result<()> {
self.inner.copy_opts(from, to, options).await
}
}