#![cfg_attr(not(test), allow(dead_code, unused_imports))]
use ruststream::memory::prelude::*;
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 {
order_id: u64,
}
impl OutgoingDestination for Receipt {
type Form = FixedName;
const DESTINATION: &'static str = "receipts";
}
impl MessageHeaders for Receipt {
type Contract = NoHeaders;
}
mod start {
use std::error::Error;
use std::future::{Future, ready};
use std::pin::pin;
use std::time::Duration;
use futures::StreamExt;
use ruststream::Subscriber;
use ruststream::memory::MemorySubscriber;
use ruststream::memory::prelude::*;
use ruststream::runtime::HealthState;
use tokio::time::timeout;
use super::{Order, Receipt};
struct IssueReceipt;
impl Handle<Order, Receipt> for IssueReceipt {
fn handle(
&self,
order: &Order,
_outs: &(),
_ctx: &mut Context<'_>,
) -> impl Future<Output = Result<Receipt, HandlerOutcome>> {
ready(Ok(Receipt { order_id: order.id }))
}
}
fn app(broker: MemoryBroker) -> impl App {
RustStream::new(AppInfo::new("orders", "0.1.0")).with_broker(broker, |b| {
b.include(subscriber("orders", IssueReceipt).reply().build())
.out_reply(Publish);
})
}
async fn serve_forever(broker: MemoryBroker) -> Result<(), Box<dyn Error>> {
app(broker).run().await?;
Ok(())
}
async fn serve_until(
broker: MemoryBroker,
work: impl Future<Output = ()> + Send,
) -> Result<(), Box<dyn Error>> {
app(broker).run_until(work).await?;
Ok(())
}
async fn serve(
broker: MemoryBroker,
work: impl Future<Output = ()> + Send,
) -> Result<(), Box<dyn Error>> {
let running = app(broker).start().await?;
work.await;
running.shutdown().await?;
Ok(())
}
async fn stop(running: RunningApp) -> Result<(), Box<dyn Error>> {
let health = running.health();
running.shutdown().await?;
tracing::info!(state = ?health.state(), "stopped");
Ok(())
}
async fn next_receipt(receipts: &mut MemorySubscriber) -> Option<Receipt> {
let mut stream = pin!(receipts.stream());
match timeout(Duration::from_secs(5), stream.next()).await {
Ok(Some(Ok(delivery))) => serde_json::from_slice(delivery.payload()).ok(),
_ => None,
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn serve_stops_the_service_after_the_work() -> Result<(), Box<dyn Error>> {
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let mut receipt = None;
let work = async {
let sent = orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await;
if sent.is_ok() {
receipt = next_receipt(&mut receipts).await;
}
};
serve(broker, work).await?;
assert_eq!(receipt, Some(Receipt { order_id: 7 }));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn start_runs_the_service_beside_the_caller() -> Result<(), Box<dyn Error>> {
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let running = app(broker).start().await?;
let health = running.health();
assert_eq!(health.state(), HealthState::Running);
orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await?;
assert_eq!(
next_receipt(&mut receipts).await,
Some(Receipt { order_id: 7 })
);
stop(running).await?;
assert_eq!(health.state(), HealthState::Stopped);
Ok(())
}
#[cfg(unix)]
fn raise_sigterm() -> Result<(), Box<dyn Error>> {
let status = std::process::Command::new("sh")
.arg("-c")
.arg(format!("kill -TERM {}", std::process::id()))
.status()?;
assert!(status.success(), "kill -TERM failed: {status:?}");
Ok(())
}
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn run_serves_until_a_termination_signal() -> Result<(), Box<dyn Error>> {
let _guard = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let mut run = tokio::spawn(async move {
serve_forever(broker)
.await
.map_err(|error| error.to_string())
});
let receipt = timeout(Duration::from_secs(30), async {
loop {
orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await?;
let answered = timeout(Duration::from_millis(100), next_receipt(&mut receipts));
if let Ok(Some(receipt)) = answered.await {
return Ok::<_, Box<dyn Error>>(receipt);
}
}
})
.await??;
assert_eq!(receipt, Receipt { order_id: 7 });
let stopped = timeout(Duration::from_secs(30), async {
loop {
raise_sigterm()?;
if let Ok(joined) = timeout(Duration::from_millis(50), &mut run).await {
return Ok::<_, Box<dyn Error>>(joined?);
}
}
})
.await??;
assert_eq!(stopped, Ok(()), "the signalled shutdown must be graceful");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn run_until_stops_when_the_future_resolves() -> Result<(), Box<dyn Error>> {
let broker = MemoryBroker::new();
let mut receipts = broker.subscribe("receipts");
let orders = broker.publisher();
let mut receipt = None;
let first_receipt = async {
let sent = orders
.message(&Order { id: 7 })
.to("orders")
.publish()
.await;
if sent.is_ok() {
receipt = next_receipt(&mut receipts).await;
}
};
serve_until(broker, first_receipt).await?;
assert_eq!(receipt, Some(Receipt { order_id: 7 }));
Ok(())
}
}
fn main() {}