#![cfg(all(feature = "memory", feature = "json", feature = "testing"))]
use std::future::{Future, ready};
use ruststream::memory::prelude::*;
use ruststream::runtime::Reads;
use ruststream::testing::TestApp;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
struct Order {
id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
struct Meta {
tenant: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
struct Confirmation {
id: u64,
tenant: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
struct Event {
id: u64,
tenant: String,
}
impl OutgoingDestination for Event {
type Form = CallerName;
}
impl MessageHeaders for Event {
type Contract = NoHeaders;
}
impl OutgoingDestination for Confirmation {
type Form = CallerName;
}
struct Analytics;
impl OutSlot for Analytics {
const NAME: &'static str = "Analytics";
type Destination = Reads;
}
impl PublishedThrough<Analytics> for Event {}
fn event_of(msg: &Message<Meta, Order>) -> Event {
Event {
id: msg.body.id,
tenant: msg.headers.tenant.clone(),
}
}
fn confirmation_of(msg: &Message<Meta, Order>) -> Confirmation {
Confirmation {
id: msg.body.id,
tenant: msg.headers.tenant.clone(),
}
}
type AnalyticsArena<A> = Outs<(A,)>;
struct MirrorPair;
impl<A> Handle<Message<Meta, Order>, (), AnalyticsArena<A>> for MirrorPair
where
A: OutEntry<Analytics, Wire: Publisher>,
{
async fn handle(
&self,
msg: &Message<Meta, Order>,
outs: &AnalyticsArena<A>,
_ctx: &mut Context<'_>,
) -> Result<(), HandlerOutcome> {
if outs
.get(Analytics)
.message(&event_of(msg))
.to("matrix.events")
.publish()
.await
.is_err()
{
return Err(HandlerOutcome::retry());
}
Ok(())
}
}
struct ConfirmPair;
impl Handle<Message<Meta, Order>, Confirmation> for ConfirmPair {
fn handle(
&self,
msg: &Message<Meta, Order>,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<Confirmation, HandlerOutcome>> {
ready(Ok(confirmation_of(msg)))
}
}
struct GatewayPair;
impl<A> Handle<Message<Meta, Order>, Confirmation, AnalyticsArena<A>> for GatewayPair
where
A: OutEntry<Analytics, Wire: Publisher>,
{
async fn handle(
&self,
msg: &Message<Meta, Order>,
outs: &AnalyticsArena<A>,
_ctx: &mut Context<'_>,
) -> Result<Confirmation, HandlerOutcome> {
if outs
.get(Analytics)
.message(&event_of(msg))
.to("matrix.gateway-events")
.publish()
.await
.is_err()
{
return Err(HandlerOutcome::retry());
}
Ok(confirmation_of(msg))
}
}
struct MirrorPairBatch;
impl<A> Handle<[Message<Meta, Order>], (), AnalyticsArena<A>> for MirrorPairBatch
where
A: OutEntry<Analytics, Wire: Publisher>,
{
async fn handle(
&self,
batch: &[Message<Meta, Order>],
outs: &AnalyticsArena<A>,
_ctx: &mut Context<'_>,
) -> Result<(), Vec<HandlerOutcome>> {
for msg in batch {
if outs
.get(Analytics)
.message(&event_of(msg))
.to("matrix.batch-events")
.publish()
.await
.is_err()
{
return Err(batch.iter().map(|_| HandlerOutcome::retry()).collect());
}
}
Ok(())
}
}
struct ConfirmPairBatch;
impl Handle<[Message<Meta, Order>], Vec<Confirmation>> for ConfirmPairBatch {
fn handle(
&self,
batch: &[Message<Meta, Order>],
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<Vec<Confirmation>, Vec<HandlerOutcome>>> {
ready(Ok(batch.iter().map(confirmation_of).collect()))
}
}
struct GatewayPairBatch;
impl<A> Handle<[Message<Meta, Order>], Vec<Confirmation>, AnalyticsArena<A>> for GatewayPairBatch
where
A: OutEntry<Analytics, Wire: Publisher>,
{
async fn handle(
&self,
batch: &[Message<Meta, Order>],
outs: &AnalyticsArena<A>,
_ctx: &mut Context<'_>,
) -> Result<Vec<Confirmation>, Vec<HandlerOutcome>> {
for msg in batch {
if outs
.get(Analytics)
.message(&event_of(msg))
.to("matrix.gateway-batch-events")
.publish()
.await
.is_err()
{
return Err(batch.iter().map(|_| HandlerOutcome::retry()).collect());
}
}
Ok(batch.iter().map(confirmation_of).collect())
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_pair_input_reaches_every_single_message_cell() {
let app =
RustStream::new(AppInfo::new("matrix", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("matrix.mirror", MirrorPair).build())
.out(Analytics, Publish)
.build();
b.include(
subscriber("matrix.confirm", ConfirmPair)
.reply()
.to("matrix.confirmations")
.build(),
);
b.include(
subscriber("matrix.gateway", GatewayPair)
.reply()
.to("matrix.gateway-confirmations")
.build(),
)
.out(Analytics, Publish)
.build();
});
let tb = TestApp::start(app).await.expect("harness start");
let meta = Meta {
tenant: "acme".to_owned(),
};
for subject in ["matrix.mirror", "matrix.confirm", "matrix.gateway"] {
tb.broker::<MemoryBroker>()
.publish_with_headers(subject, &Order { id: 7 }, &meta)
.await
.expect("publish");
}
tb.settle().await.expect("settle");
let expected_event = Event {
id: 7,
tenant: "acme".to_owned(),
};
let expected_reply = Confirmation {
id: 7,
tenant: "acme".to_owned(),
};
let broker = tb.broker::<MemoryBroker>();
broker
.published::<Event>("matrix.events")
.assert_called_once()
.with(&expected_event);
broker
.published::<Confirmation>("matrix.confirmations")
.assert_called_once()
.with(&expected_reply);
broker
.published::<Event>("matrix.gateway-events")
.assert_called_once()
.with(&expected_event);
broker
.published::<Confirmation>("matrix.gateway-confirmations")
.assert_called_once()
.with(&expected_reply);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_pair_input_reaches_every_batch_cell() {
let app = RustStream::new(AppInfo::new("matrix-batch", "0.1.0")).with_broker(
MemoryBroker::new(),
|b| {
b.include(
subscriber("matrix.mirror-batch", MirrorPairBatch)
.batch(nonzero!(8))
.build(),
)
.out(Analytics, Publish)
.build();
b.include(
subscriber("matrix.confirm-batch", ConfirmPairBatch)
.reply()
.to("matrix.batch-confirmations")
.batch(nonzero!(8))
.build(),
);
b.include(
subscriber("matrix.gateway-batch", GatewayPairBatch)
.reply()
.to("matrix.gateway-batch-confirmations")
.batch(nonzero!(8))
.build(),
)
.out(Analytics, Publish)
.build();
},
);
let tb = TestApp::start(app).await.expect("harness start");
let meta = Meta {
tenant: "acme".to_owned(),
};
for subject in [
"matrix.mirror-batch",
"matrix.confirm-batch",
"matrix.gateway-batch",
] {
tb.broker::<MemoryBroker>()
.publish_with_headers(subject, &Order { id: 9 }, &meta)
.await
.expect("publish");
}
tb.settle().await.expect("settle");
let expected_event = Event {
id: 9,
tenant: "acme".to_owned(),
};
let expected_reply = Confirmation {
id: 9,
tenant: "acme".to_owned(),
};
let broker = tb.broker::<MemoryBroker>();
broker
.published::<Event>("matrix.batch-events")
.assert_called_once()
.with(&expected_event);
broker
.published::<Confirmation>("matrix.batch-confirmations")
.assert_called_once()
.with(&expected_reply);
broker
.published::<Event>("matrix.gateway-batch-events")
.assert_called_once()
.with(&expected_event);
broker
.published::<Confirmation>("matrix.gateway-batch-confirmations")
.assert_called_once()
.with(&expected_reply);
}