#![cfg_attr(not(test), allow(dead_code, unused_imports))]
use std::future::{Future, ready};
use ruststream::memory::prelude::*;
use ruststream::runtime::{Outgoing, PublishContext, SlotContext};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Deserialize, Serialize, schemars::JsonSchema)]
struct Order {
id: u64,
}
impl OutgoingDestination for Order {
type Form = CallerName;
}
impl MessageHeaders for Order {
type Contract = NoHeaders;
}
#[derive(Debug, PartialEq, Deserialize, Serialize, schemars::JsonSchema)]
struct Receipt {
id: u64,
}
impl OutgoingDestination for Receipt {
type Form = CallerName;
}
impl MessageHeaders for Receipt {
type Contract = NoHeaders;
}
#[derive(Debug, PartialEq, Deserialize, Serialize, schemars::JsonSchema)]
struct Payment {
id: u64,
}
impl OutgoingDestination for Payment {
type Form = CallerName;
}
impl MessageHeaders for Payment {
type Contract = NoHeaders;
}
#[derive(Debug, PartialEq, Deserialize, Serialize, schemars::JsonSchema)]
struct Audit {
id: u64,
}
impl OutgoingDestination for Audit {
type Form = CallerName;
}
impl MessageHeaders for Audit {
type Contract = NoHeaders;
}
struct Correlate;
impl<C, Options> PublishTransform<ForReply<C>, Options> for Correlate {
type Destination = Reads;
fn apply(
&self,
out: &mut Outgoing<'_>,
_options: &mut Option<Options>,
cx: &PublishContext<'_, C>,
) {
if let Some(id) = cx.headers().get_shared("correlation-id") {
out.headers_mut().insert("correlation-id", id);
}
}
}
struct SchemaVersion(&'static str);
impl<Options> PublishTransform<ForSlot, Options> for SchemaVersion {
type Destination = Reads;
fn apply(&self, out: &mut Outgoing<'_>, _options: &mut Option<Options>, _cx: &SlotContext<'_>) {
out.headers_mut().insert("schema-version", self.0);
}
}
struct Confirm;
impl Handle<Order, Receipt> for Confirm {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<Receipt, HandlerOutcome>> {
ready(Ok(Receipt { id: order.id }))
}
}
struct Trail;
impl OutSlot for Trail {
const NAME: &'static str = "Trail";
type Destination = Names;
}
impl PublishedThrough<Trail> for Audit {}
struct AuditPayment;
impl<T> Handle<Payment, (), Outs<(T,)>> for AuditPayment
where
T: OutEntry<Trail, Wire: Publisher>,
{
async fn handle(
&self,
payment: &Payment,
outs: &Outs<(T,)>,
_ctx: &mut Context<'_>,
) -> Result<(), HandlerOutcome> {
let record = Audit { id: payment.id };
outs.get(Trail)
.message(&record)
.to("audit")
.publish()
.await
.map_err(|_| HandlerOutcome::retry())
}
}
fn app() -> impl App {
RustStream::new(AppInfo::new("billing", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Confirm).reply().to("receipts").build())
.out_reply(Publish)
.transform(Correlate);
b.include(subscriber("payments", AuditPayment).build())
.out(Trail, Publish)
.transform(SchemaVersion("2"))
.build();
})
}
fn main() {}
#[cfg(test)]
mod tests {
use std::error::Error;
use ruststream::HeaderMap;
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
use super::{Audit, Order, Payment, Receipt, app};
#[tokio::test]
async fn a_receipt_carries_its_orders_correlation_id() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
let broker = tb.broker::<MemoryBroker>();
let mut headers = HeaderMap::new();
headers.insert("correlation-id", "c-17");
broker
.message(&Order { id: 17 })
.to("orders")
.with_headers(headers)
.publish()
.await?;
broker
.published::<Receipt>("receipts")
.assert_called_once()
.with(&Receipt { id: 17 })
.with_header("correlation-id", "c-17");
Ok(())
}
#[tokio::test]
async fn an_audit_record_carries_the_schema_version() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
let broker = tb.broker::<MemoryBroker>();
broker
.message(&Payment { id: 2 })
.to("payments")
.publish()
.await?;
broker
.published::<Audit>("audit")
.assert_called_once()
.with(&Audit { id: 2 })
.with_header("schema-version", "2");
Ok(())
}
}