use std::fmt;
use std::sync::Arc;
use ruststream::memory::prelude::*;
use ruststream::schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::state::Shop;
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema, Outgoing)]
#[outgoing(name = "notices")]
pub(crate) struct Notice {
pub(crate) customer: String,
pub(crate) text: String,
}
#[derive(OutSlot)]
#[publishes(Notice)]
pub(crate) struct Notices;
#[derive(Debug)]
pub(crate) struct MailError(String);
impl fmt::Display for MailError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the mail server refused the message: {}", self.0)
}
}
impl std::error::Error for MailError {}
#[derive(Debug)]
pub(crate) struct Mailer {
server: String,
}
impl Mailer {
pub(crate) async fn connect(server: &str) -> Result<Self, MailError> {
tokio::task::yield_now().await;
Ok(Self {
server: server.to_owned(),
})
}
pub(crate) async fn send(&self, to: &str, text: &str) -> Result<(), MailError> {
tokio::task::yield_now().await;
if !to.contains('@') {
return Err(MailError(format!("no mailbox at {to:?}")));
}
tracing::info!(server = %self.server, to, text, "mail sent");
Ok(())
}
}
#[subscriber("notices")]
async fn send(
notice: &Notice,
ctx: &mut Context<'_, (), Shop>,
State(mailer): State<Arc<Mailer>>,
) -> HandlerOutcome {
match mailer.send(¬ice.customer, ¬ice.text).await {
Ok(()) => HandlerOutcome::ack(),
Err(error) => {
tracing::warn!(subscription = ctx.name(), %error, "notice not sent");
HandlerOutcome::drop()
}
}
}
pub(crate) fn routes() -> impl RouterDef<MemoryBroker, Shop> {
Router::new().include(send)
}
#[cfg(test)]
mod tests {
use std::error::Error;
use ruststream::memory::prelude::*;
use ruststream::testing::TestApp;
use super::Notice;
use crate::service::{shop, test_broker};
#[tokio::test]
async fn a_notice_to_a_mailbox_that_does_not_exist_is_dropped() -> Result<(), Box<dyn Error>> {
let tb = TestApp::start(shop(test_broker())).await?;
let notice = Notice {
customer: "nobody".to_owned(),
text: "We have your order 9.".to_owned(),
};
tb.message(¬ice).publish().await?;
tb.broker::<MemoryBroker>()
.subscriber("notices")
.assert_called_once()
.settled(HandlerOutcome::drop());
Ok(())
}
}