use std::error::Error;
use ruststream::memory::prelude::*;
use ruststream::runtime::{
ContextKind, ForReply, Names, Outgoing, PublishContext, PublishLayer, PublishNext,
PublishPipeline, PublishTransform, Reads,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct Request {
id: u64,
}
#[derive(Debug, Serialize, Outgoing)]
struct Response {
ok: bool,
}
#[derive(Debug, Deserialize, Serialize, Outgoing)]
struct Event {
id: u64,
}
#[derive(Debug, Serialize, Outgoing)]
#[outgoing(name = "receipts")]
struct Receipt {
id: u64,
}
#[subscriber("receipt-requests", publish)]
async fn issue_receipt(req: &Request) -> Receipt {
Receipt { id: req.id }
}
#[subscriber("requests", publish("responses"))]
async fn respond(req: &Request) -> Response {
println!("responding to request {}", req.id);
Response { ok: true }
}
#[subscriber("validated-requests", publish("responses"))]
async fn validate(req: &Request) -> Result<Response, HandlerOutcome> {
if req.id == 0 {
return Err(HandlerOutcome::drop());
}
Ok(Response { ok: true })
}
#[subscriber("ingress")]
async fn forward(event: &Event, Out(out): Out<impl Publisher>) -> HandlerOutcome {
if out.message(event).to("egress").publish().await.is_err() {
return HandlerOutcome::retry();
}
HandlerOutcome::ack()
}
#[derive(OutSlot)]
#[publishes(Event)]
struct Primary;
#[derive(OutSlot)]
#[publishes(Event)]
struct Shadow;
#[subscriber("mirror")]
async fn mirror(
event: &Event,
Out(primary): Out<impl Publisher, Primary>,
Out(shadow): Out<impl Publisher, Shadow>,
) -> HandlerOutcome {
if primary
.message(event)
.to("mirror-primary")
.publish()
.await
.is_err()
|| shadow
.message(event)
.to("mirror-shadow")
.publish()
.await
.is_err()
{
return HandlerOutcome::retry();
}
HandlerOutcome::ack()
}
#[subscriber("gateway-requests", publish("gateway-responses"))]
async fn gateway(req: &Request, Out(out): Out<impl Publisher>) -> Result<Response, HandlerOutcome> {
if out
.message(&Event { id: req.id })
.to("gateway-audit")
.publish()
.await
.is_err()
{
return Err(HandlerOutcome::retry());
}
Ok(Response { ok: true })
}
#[derive(Debug, Serialize, Outgoing)]
#[outgoing(name = "orders.confirmed")]
struct OrderConfirmed {
id: u64,
}
#[derive(Debug, Serialize, Outgoing)]
#[outgoing(name = "orders.{tenant}.placed")]
struct OrderPlaced {
id: u64,
}
#[derive(OutSlot)]
#[publishes(OrderConfirmed, OrderPlaced)]
struct Orders;
#[subscriber("orders.incoming")]
async fn route(
event: &Event,
Out(orders): Out<impl Publisher, Orders, (OrderConfirmed, OrderPlaced)>,
) -> HandlerOutcome {
if orders
.message(&OrderConfirmed { id: event.id })
.publish()
.await
.is_err()
{
return HandlerOutcome::retry();
}
if orders
.message(&OrderPlaced { id: event.id })
.to()
.tenant("acme")
.publish()
.await
.is_err()
{
return HandlerOutcome::retry();
}
HandlerOutcome::ack()
}
struct EnvelopeTransform;
impl<K: ContextKind, Options> PublishTransform<K, Options> for EnvelopeTransform {
type Destination = Reads;
fn apply(&self, out: &mut Outgoing<'_>, _options: &mut Option<Options>, _cx: &K::View<'_>) {
out.headers_mut().insert("x-envelope", b"1".to_vec());
}
}
struct OutboxEnvelope;
impl<K: ContextKind, Options> PublishTransform<K, Options> for OutboxEnvelope {
type Destination = Reads;
fn apply(&self, out: &mut Outgoing<'_>, _options: &mut Option<Options>, _cx: &K::View<'_>) {
out.headers_mut().insert("x-outbox", b"1".to_vec());
}
}
struct ReplyTo;
impl<C, Options> PublishTransform<ForReply<C>, Options> for ReplyTo {
type Destination = Names;
fn apply(
&self,
out: &mut Outgoing<'_>,
_options: &mut Option<Options>,
cx: &PublishContext<'_, C>,
) {
if let Some(to) = cx.headers().get("reply-to")
&& let Ok(to) = std::str::from_utf8(to)
{
out.set_name(to.to_owned());
}
}
}
#[subscriber("asks", publish("answers"))]
async fn answer(req: &Request) -> Response {
println!("answering ask {}", req.id);
Response { ok: true }
}
#[derive(Clone)]
struct AuditPublish;
impl PublishLayer for AuditPublish {
async fn on_publish<'a, N: PublishPipeline, P: Publisher>(
&'a self,
out: &'a mut Outgoing<'a>,
next: PublishNext<'a, N, P>,
) -> Result<(), Box<dyn Error + Send + Sync>> {
println!("publishing to {}", out.name());
next.run(out).await
}
}
#[subscriber("orders", publish("confirmations"))]
async fn confirm(orders: &[Event]) -> Result<Vec<Event>, HandlerOutcome> {
if orders.is_empty() {
return Err(HandlerOutcome::drop()); }
Ok(orders.iter().map(|o| Event { id: o.id }).collect())
}
async fn seed_events<P>(seeder: P) -> Result<(), Box<dyn Error + Send + Sync>>
where
P: TransactionalPublisher,
{
let scope = seeder.begin().await?;
scope
.message(&Event { id: 1 })
.to("events")
.publish()
.await?;
scope
.message(&Event { id: 2 })
.to("events")
.publish()
.await?;
scope.commit().await?;
Ok(())
}
#[ruststream::app]
fn app() -> impl App {
let broker = MemoryBroker::new();
RustStream::new(AppInfo::new("publishing", "0.1.0"))
.publish_layer(AuditPublish)
.with_broker(broker, |b| {
b.after_startup(TransactionalPublish, async move |seeder| {
seed_events(seeder).await.map_err(std::io::Error::other)
});
b.include(issue_receipt);
b.include(respond)
.out_reply(Publish)
.transform(EnvelopeTransform);
b.include(validate);
b.include(answer).out_reply(Publish).transform(ReplyTo);
b.include(forward).out(DefaultSlot, Publish).build();
b.include(mirror)
.out(Shadow, Publish)
.transform(OutboxEnvelope)
.out(Primary, Publish)
.build();
b.include(gateway).out(DefaultSlot, Publish).build();
b.include(route).out(Orders, Publish).build();
b.include(confirm.batch(nonzero!(64)))
.out_reply(TransactionalPublish)
.transactional();
})
}