#![cfg(all(
feature = "memory",
feature = "macros",
feature = "json",
feature = "testing"
))]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use ruststream::memory::{
MemoryBatchContext, MemoryBroker, MemoryPosition, Retaining, Retention, SeekHandle,
};
use ruststream::prelude::*;
use ruststream::testing::TestApp;
use ruststream::{OutgoingMessage, Publisher, Seeker, nonzero};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct Cursor {
resume_at: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
struct Entry {
id: u64,
}
#[subscriber("replay.log")]
async fn replay(
batch: &[Message<Cursor, Entry>],
ctx: &mut Context<'_, MemoryBatchContext>,
) -> HandlerOutcome {
let target = batch
.iter()
.find_map(|element| element.headers.resume_at)
.map(|sequence| usize::try_from(sequence).expect("test positions are small"));
if let Some(sequence) = target
&& ctx
.context(SeekHandle)
.seek(MemoryPosition::sequence(sequence))
.await
.is_err()
{
return HandlerOutcome::retry();
}
HandlerOutcome::ack()
}
async fn publish_entry(broker: &MemoryBroker<Retaining>, id: u64, resume_at: Option<u64>) {
let payload = serde_json::to_vec(&Entry { id }).expect("serializable");
let msg = OutgoingMessage::new("replay.log", payload.as_slice())
.with_typed_headers(&Cursor { resume_at })
.expect("a flat header contract");
broker
.publisher()
.publish(msg, None)
.await
.expect("publish");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_batch_reads_its_seek_target_from_element_headers() {
let broker = MemoryBroker::retaining(Retention::Messages(nonzero!(32)));
publish_entry(&broker, 0, Some(2)).await;
publish_entry(&broker, 1, None).await;
publish_entry(&broker, 2, None).await;
let app = RustStream::new(AppInfo::new("replay", "0.1.0")).with_broker(broker, |b| {
b.include(replay.batch(nonzero!(64)).start_at(MemoryPosition::start()));
});
let tb = TestApp::start(app).await.expect("startup failed");
tb.settle().await.expect("the replay settles");
let batches: Vec<Vec<u64>> = tb
.broker::<MemoryBroker<Retaining>>()
.subscriber("replay.log")
.batches::<Entry>()
.iter()
.map(|batch| batch.iter().map(|entry| entry.id).collect())
.collect();
assert_eq!(
batches,
[vec![0, 1, 2], vec![2]],
"the batch after the seek must open at the header-named position",
);
}