use ruststream::runtime::{Outgoing, PublishContext};
use ruststream_zeromq::rpc::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Deserialize)]
struct ScoreRequest {
customer: u64,
amount_cents: u64,
}
#[derive(Debug, PartialEq, Serialize, Outgoing)]
struct Score {
customer: u64,
approved: bool,
}
#[subscriber("score", reply("reply"))]
async fn score(request: &ScoreRequest) -> Result<Score, HandlerOutcome> {
if request.amount_cents == 0 {
tracing::warn!(request.customer, "a request without an amount, dropped");
return Err(HandlerOutcome::drop());
}
tracing::info!(request.customer, request.amount_cents, "scoring");
Ok(Score {
customer: request.customer,
approved: request.amount_cents <= 50_000,
})
}
#[ruststream::app]
fn app() -> impl App {
RustStream::new(AppInfo::new("scoring", "0.1.0")).with_broker(
ZmqRpc::new(ZmqEndpoint::bind("tcp://0.0.0.0:5557")),
|b| {
b.include(score)
.out_reply(Publish)
.transform(ReplyToRequester)
.out_retry(Publish)
.to("score.retry");
},
)
}
struct ReplyToRequester;
impl<C, Options> PublishTransform<ForReply<C>, Options> for ReplyToRequester {
type Destination = Names;
fn apply(
&self,
out: &mut Outgoing<'_>,
_options: &mut Option<Options>,
cx: &PublishContext<'_, C>,
) {
if let Some(reply_to) = cx.headers().get_shared("reply-to")
&& let Ok(reply_to) = Str::try_from(reply_to)
{
out.set_name(reply_to);
}
if let Some(correlation) = cx.headers().get_shared("correlation-id") {
out.headers_mut()
.insert(Str::from_static("correlation-id"), correlation);
}
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use ruststream::testing::TestApp;
use ruststream_zeromq::rpc::prelude::*;
use serde::Serialize;
use super::{ScoreRequest, app};
#[derive(Serialize, Outgoing)]
#[outgoing(name = "score")]
struct Asked {
customer: u64,
amount_cents: u64,
}
#[tokio::test]
async fn a_request_without_an_amount_is_dropped() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.broker::<ZmqRpc>()
.message(&Asked {
customer: 7,
amount_cents: 0,
})
.publish()
.await?;
tb.broker::<ZmqRpc>()
.subscriber("score")
.assert_called_once()
.with(&ScoreRequest {
customer: 7,
amount_cents: 0,
})
.settled(HandlerOutcome::drop());
tb.shutdown().await?;
Ok(())
}
#[tokio::test]
async fn a_request_with_an_amount_is_answered() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(app()).await?;
tb.broker::<ZmqRpc>()
.message(&Asked {
customer: 8,
amount_cents: 1250,
})
.publish()
.await?;
tb.broker::<ZmqRpc>()
.subscriber("score")
.assert_called_once()
.settled(HandlerOutcome::ack());
tb.shutdown().await?;
Ok(())
}
}