#![cfg_attr(not(test), allow(dead_code, unused_imports))]
use std::sync::Mutex;
use serde::{Deserialize, Serialize};
use ruststream::memory::prelude::*;
#[derive(Debug, PartialEq, Deserialize, Serialize, schemars::JsonSchema)]
struct Order {
id: u64,
}
impl OutgoingDestination for Order {
type Form = FixedName;
const DESTINATION: &'static str = "orders";
}
impl MessageHeaders for Order {
type Contract = NoHeaders;
}
#[derive(Default)]
struct Notifier {
sent: Mutex<Vec<String>>,
}
impl Notifier {
async fn send(&self, note: String) {
tokio::task::yield_now().await;
if let Ok(mut sent) = self.sent.lock() {
sent.push(note);
}
}
fn sent(&self) -> Vec<String> {
let mut sent = self
.sent
.lock()
.map(|sent| sent.clone())
.unwrap_or_default();
sent.sort();
sent
}
}
mod single {
use std::convert::Infallible;
use std::error::Error;
use std::future::{Future, ready};
use std::sync::Arc;
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
use super::{Notifier, Order};
struct Accept;
impl Handle<Order, (), (), (), Arc<Notifier>> for Accept {
fn handle(
&self,
order: &Order,
_outs: &(),
ctx: &mut Context<'_, (), Arc<Notifier>>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
let notifier = Arc::clone(ctx.state());
let id = order.id;
ready(Err(HandlerOutcome::ack().and_after(async move {
notifier.send(format!("accepted {id}")).await;
})))
}
}
fn app() -> impl App<State = Arc<Notifier>> {
RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(async move |()| Ok::<_, Infallible>(Arc::new(Notifier::default())))
.with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Accept).build());
})
}
#[tokio::test]
async fn the_notice_follows_the_ack() -> Result<(), Box<dyn Error>> {
let notifier = Arc::new(Notifier::default());
let tb = TestApp::with_state(app(), |_| Arc::clone(¬ifier)).await?;
tb.message(&Order { id: 7 }).publish().await?;
tb.drain().await;
assert_eq!(notifier.sent(), ["accepted 7"]);
Ok(())
}
}
mod batch {
use std::convert::Infallible;
use std::error::Error;
use std::future::{Future, ready};
use std::sync::Arc;
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
use super::{Notifier, Order};
struct Accept;
impl Handle<[Order], (), (), (), Arc<Notifier>> for Accept {
fn handle(
&self,
orders: &[Order],
_outs: &(),
ctx: &mut Context<'_, (), Arc<Notifier>>,
) -> impl Future<Output = Result<(), Vec<HandlerOutcome>>> {
ready(Err(orders
.iter()
.map(|order| {
if order.id == 0 {
return HandlerOutcome::drop();
}
let notifier = Arc::clone(ctx.state());
let id = order.id;
HandlerOutcome::ack().and_after(async move {
notifier.send(format!("accepted {id}")).await;
})
})
.collect()))
}
}
fn app(broker: MemoryBroker<Retaining>) -> impl App<State = Arc<Notifier>> {
RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(async move |()| Ok::<_, Infallible>(Arc::new(Notifier::default())))
.with_broker(broker, |b| {
b.include(
subscriber("orders", Accept)
.batch(nonzero!(3))
.start_at(MemoryPosition::start())
.build(),
);
})
}
#[tokio::test]
async fn only_the_acked_orders_are_announced() -> Result<(), Box<dyn Error>> {
let broker = MemoryBroker::retaining(Retention::Messages(nonzero!(16)));
let publisher = broker.publisher();
for id in [1, 0, 2] {
publisher.message(&Order { id }).publish().await?;
}
let notifier = Arc::new(Notifier::default());
let tb = TestApp::with_state(app(broker), |_| Arc::clone(¬ifier)).await?;
tb.settle().await?;
tb.drain().await;
assert_eq!(notifier.sent(), ["accepted 1", "accepted 2"]);
Ok(())
}
}
mod hooks {
use std::convert::Infallible;
use std::error::Error;
use std::future::{Future, ready};
use std::sync::Arc;
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
use super::{Notifier, Order};
struct Accept;
impl Handle<Order, (), (), (), Arc<Notifier>> for Accept {
fn handle(
&self,
order: &Order,
_outs: &(),
ctx: &mut Context<'_, (), Arc<Notifier>>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
let id = order.id;
let notifier = Arc::clone(ctx.state());
ctx.after_ack(async move { notifier.send(format!("shipping {id}")).await });
let notifier = Arc::clone(ctx.state());
ctx.after(HandlerOutcome::drop())
.then(async move { notifier.send(format!("rejected {id}")).await });
let notifier = Arc::clone(ctx.state());
ctx.after_settle(async move { notifier.send(format!("settled {id}")).await });
if id == 0 {
return ready(Err(HandlerOutcome::drop()));
}
ready(Ok(()))
}
}
fn app() -> impl App<State = Arc<Notifier>> {
RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(async move |()| Ok::<_, Infallible>(Arc::new(Notifier::default())))
.with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Accept).build());
})
}
#[tokio::test]
async fn each_hook_runs_on_its_own_settlement() -> Result<(), Box<dyn Error>> {
let notifier = Arc::new(Notifier::default());
let tb = TestApp::with_state(app(), |_| Arc::clone(¬ifier)).await?;
tb.message(&Order { id: 7 }).publish().await?;
tb.message(&Order { id: 0 }).publish().await?;
tb.drain().await;
assert_eq!(
notifier.sent(),
["rejected 0", "settled 0", "settled 7", "shipping 7"],
);
Ok(())
}
}
fn main() {}