#![cfg(all(
feature = "macros",
feature = "memory",
feature = "json",
feature = "testing"
))]
use std::convert::Infallible;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use ruststream::memory::{MemoryBroker, MemoryMessage};
use ruststream::runtime::{AppInfo, Ctx, HandlerOutcome, RustStream, State};
use ruststream::testing::TestApp;
use ruststream::{
BuildContext, ContextField, Field, FromRef, IncomingMessage, Outgoing, subscriber,
};
use serde::{Deserialize, Serialize};
#[derive(Outgoing, Serialize, Deserialize, PartialEq, Debug)]
struct Order {
id: u64,
}
struct DeliveryMeta {
payload_len: usize,
}
impl BuildContext<MemoryMessage> for DeliveryMeta {
fn build(msg: &MemoryMessage) -> Self {
Self {
payload_len: msg.payload().len(),
}
}
}
#[derive(Clone, Copy, Default)]
struct PayloadLen;
impl ContextField for PayloadLen {
type Context = DeliveryMeta;
type Value = usize;
fn read(self, src: &DeliveryMeta) -> usize {
src.payload_len
}
}
impl Field<DeliveryMeta> for PayloadLen {
type Value<'a> = usize;
fn get(self, src: &DeliveryMeta) -> usize {
src.payload_len
}
}
#[derive(Clone)]
struct SeenLen(Arc<AtomicUsize>);
#[derive(FromRef)]
struct MeasureState {
seen: SeenLen,
}
#[subscriber("orders")]
async fn measure(
_order: &Order,
Ctx(len): Ctx<PayloadLen>,
State(seen): State<SeenLen>,
) -> HandlerOutcome {
seen.0.store(len, Ordering::Relaxed);
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn ctx_extractor_projects_the_context_from_its_key() {
let seen_len = Arc::new(AtomicUsize::new(0));
let state_seen = SeenLen(Arc::clone(&seen_len));
let app = RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(move |()| async move { Ok::<_, Infallible>(MeasureState { seen: state_seen }) })
.with_broker(MemoryBroker::new(), |b| {
b.include(measure);
});
let tb = TestApp::start(app).await.expect("start");
tb.broker::<MemoryBroker>()
.message(&Order { id: 7 })
.to("orders")
.publish()
.await
.expect("publish");
tb.broker::<MemoryBroker>()
.subscriber("orders")
.assert_called_once()
.settled(HandlerOutcome::ack());
let expected = serde_json::to_vec(&Order { id: 7 }).expect("encode").len();
assert_eq!(
seen_len.load(Ordering::Relaxed),
expected,
"the extracted field must come from this delivery's context",
);
}
#[subscriber("mixed")]
async fn both(
_order: &Order,
ctx: &mut Context<'_, DeliveryMeta>,
Ctx(len): Ctx<PayloadLen>,
) -> HandlerOutcome {
assert_eq!(
ctx.context(PayloadLen),
len,
"the extractor and the key read must agree",
);
HandlerOutcome::ack()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn ctx_extractor_composes_with_an_explicit_ctx_parameter() {
let app =
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(both);
});
let tb = TestApp::start(app).await.expect("start");
tb.broker::<MemoryBroker>()
.message(&Order { id: 1 })
.to("mixed")
.publish()
.await
.expect("publish");
tb.broker::<MemoryBroker>()
.subscriber("mixed")
.assert_called_once()
.settled(HandlerOutcome::ack());
}