#![cfg_attr(not(test), allow(dead_code, unused_imports))]
use serde::{Deserialize, Serialize};
use ruststream::memory::prelude::*;
#[derive(Debug, PartialEq, Deserialize, Serialize, schemars::JsonSchema)]
struct Order {
id: u64,
sku: String,
}
impl Order {
fn new(id: u64, sku: &str) -> Self {
Self {
id,
sku: sku.to_owned(),
}
}
}
impl OutgoingDestination for Order {
type Form = FixedName;
const DESTINATION: &'static str = "orders";
}
impl MessageHeaders for Order {
type Contract = NoHeaders;
}
mod outcome {
use std::convert::Infallible;
use std::error::Error;
use std::future::{Future, ready};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use ruststream::memory::prelude::*;
use ruststream::testing::{Outcome, TestApp};
use super::Order;
struct Warehouse {
locked: AtomicBool,
}
impl Warehouse {
fn try_reserve(&self, _sku: &str) -> bool {
!self.locked.swap(false, Ordering::SeqCst)
}
}
struct Reserve;
impl Handle<Order, (), (), (), Arc<Warehouse>> for Reserve {
fn handle(
&self,
order: &Order,
_outs: &(),
ctx: &mut Context<'_, (), Arc<Warehouse>>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
if order.sku.is_empty() {
return ready(Err(HandlerOutcome::drop()));
}
if !ctx.state().try_reserve(&order.sku) {
return ready(Err(HandlerOutcome::retry()));
}
ready(Ok(()))
}
}
fn app() -> impl App<State = Arc<Warehouse>> {
RustStream::new(AppInfo::new("orders", "0.1.0"))
.on_startup(async move |()| {
Ok::<_, Infallible>(Arc::new(Warehouse {
locked: AtomicBool::new(true),
}))
})
.with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Reserve).build());
})
}
#[tokio::test]
async fn each_order_settles_by_what_the_body_returned() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.message(&Order::new(1, "")).publish().await?;
tb.message(&Order::new(2, "lamp")).publish().await?;
assert_eq!(
tb.broker::<MemoryBroker>().subscriber("orders").outcomes(),
[Outcome::Drop, Outcome::Nack, Outcome::Ack],
);
Ok(())
}
}
mod unit {
use std::error::Error;
use std::future::{Future, ready};
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
use super::Order;
struct Audit;
impl Handle<Order> for Audit {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
tracing::info!(order.id, sku = %order.sku, "seen");
ready(Ok(()))
}
}
fn app() -> impl App {
RustStream::new(AppInfo::new("audit", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Audit).build());
})
}
#[tokio::test]
async fn a_body_returning_nothing_acks() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.message(&Order::new(7, "lamp")).publish().await?;
tb.broker::<MemoryBroker>()
.subscriber("orders")
.assert_called_once()
.settled(HandlerOutcome::ack());
Ok(())
}
}
mod result {
use std::error::Error;
use std::fmt;
use std::future::{Future, ready};
use ruststream::memory::prelude::*;
use ruststream::testing::{Outcome, TestApp};
use super::Order;
#[derive(Debug)]
struct UnknownSku;
impl fmt::Display for UnknownSku {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown sku")
}
}
impl Error for UnknownSku {}
fn price_of(sku: &str) -> Result<u64, UnknownSku> {
match sku {
"lamp" => Ok(40),
_ => Err(UnknownSku),
}
}
struct Price;
impl Handle<Order> for Price {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
let priced = price_of(&order.sku).map(|cents| {
tracing::info!(order.id, cents, "priced");
});
ready(priced.map_err(|_| HandlerOutcome::drop()))
}
}
fn app() -> impl App {
RustStream::new(AppInfo::new("pricing", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Price).build());
})
}
#[tokio::test]
async fn an_error_drops_the_order() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.message(&Order::new(1, "lamp")).publish().await?;
tb.message(&Order::new(2, "sofa")).publish().await?;
assert_eq!(
tb.broker::<MemoryBroker>().subscriber("orders").outcomes(),
[Outcome::Ack, Outcome::Drop],
);
Ok(())
}
}
mod result_outcome {
use std::error::Error;
use std::fmt;
use std::future::{Future, ready};
use std::time::Duration;
use ruststream::memory::prelude::*;
use ruststream::testing::{Outcome, TestApp};
use super::Order;
#[derive(Debug)]
struct UnknownSku;
impl fmt::Display for UnknownSku {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown sku")
}
}
impl Error for UnknownSku {}
fn stock_of(sku: &str) -> Result<u32, UnknownSku> {
match sku {
"lamp" => Ok(3),
"vase" => Ok(0),
_ => Err(UnknownSku),
}
}
struct Ship;
impl Handle<Order> for Ship {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<(), HandlerOutcome>> {
ready(match stock_of(&order.sku) {
Err(_) => Err(HandlerOutcome::drop()),
Ok(0) => Err(HandlerOutcome::retry_after(Duration::from_secs(3600))),
Ok(_) => Ok(()),
})
}
}
fn app() -> impl App {
RustStream::new(AppInfo::new("shipping", "0.1.0")).with_broker(MemoryBroker::new(), |b| {
b.include(subscriber("orders", Ship).build());
})
}
#[tokio::test]
async fn the_inner_outcome_settles_the_order() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
for sku in ["lamp", "vase", "sofa"] {
tb.message(&Order::new(1, sku)).publish().await?;
}
assert_eq!(
tb.broker::<MemoryBroker>().subscriber("orders").outcomes(),
[Outcome::Ack, Outcome::Nack, Outcome::Drop],
);
Ok(())
}
}
fn main() {}