use std::sync::Arc;
use lapin::options::{
BasicGetOptions, BasicPublishOptions, ConfirmSelectOptions, ExchangeDeclareOptions,
QueueBindOptions, QueueDeclareOptions,
};
use lapin::types::FieldTable;
use lapin::{Channel, ExchangeKind};
use super::rabbitmq::{connect_channel, message_properties, RabbitReceived};
use super::source::AsyncMessageSource;
use super::{run_source, Bus, BusConsumer, MessageRouter, RunOptions, TransportError};
use super::{Message, MessageKind};
fn retryable(context: &str, err: impl std::fmt::Display) -> TransportError {
TransportError::retryable(format!("{context}: {err}"))
}
pub struct RabbitBus {
uri: String,
channel: Channel,
group: String,
namespace: String,
events_exchange: String,
}
impl RabbitBus {
pub async fn connect(
uri: &str,
group: impl Into<String>,
namespace: impl Into<String>,
) -> Result<Self, TransportError> {
let channel = connect_channel(uri).await?;
channel
.confirm_select(ConfirmSelectOptions::default())
.await
.map_err(|err| retryable("amqp confirm_select", err))?;
let namespace = namespace.into();
Ok(Self {
uri: uri.to_string(),
channel,
group: group.into(),
events_exchange: format!("{namespace}.events"),
namespace,
})
}
fn command_queue(&self, name: &str) -> String {
format!("{}.cmd.{name}", self.namespace)
}
fn command_prefix(&self) -> String {
format!("{}.cmd.", self.namespace)
}
fn group_queue(&self) -> String {
format!("{}.evt.{}", self.namespace, self.group)
}
async fn declare_queue(&self, channel: &Channel, queue: &str) -> Result<(), TransportError> {
channel
.queue_declare(
queue,
QueueDeclareOptions {
durable: true,
..Default::default()
},
FieldTable::default(),
)
.await
.map_err(|err| retryable("amqp queue_declare", err))?;
Ok(())
}
async fn declare_events_exchange(&self, channel: &Channel) -> Result<(), TransportError> {
channel
.exchange_declare(
&self.events_exchange,
ExchangeKind::Topic,
ExchangeDeclareOptions {
durable: true,
..Default::default()
},
FieldTable::default(),
)
.await
.map_err(|err| retryable("amqp exchange_declare", err))?;
Ok(())
}
async fn publish_confirmed(
&self,
exchange: &str,
routing_key: &str,
message: &Message,
) -> Result<(), TransportError> {
let confirm = self
.channel
.basic_publish(
exchange,
routing_key,
BasicPublishOptions::default(),
&message.payload,
message_properties(message),
)
.await
.map_err(|err| retryable("amqp publish", err))?;
if confirm
.await
.map_err(|err| retryable("amqp publisher confirm", err))?
.is_nack()
{
return Err(TransportError::retryable("amqp publisher confirm: nack"));
}
Ok(())
}
pub async fn ensure_subscription<R: MessageRouter>(
&self,
router: &R,
) -> Result<(), TransportError> {
self.declare_events_exchange(&self.channel).await?;
let queue = self.group_queue();
self.declare_queue(&self.channel, &queue).await?;
let plan = router.subscription_plan();
for name in &plan.events {
self.channel
.queue_bind(
&queue,
&self.events_exchange,
name,
QueueBindOptions::default(),
FieldTable::default(),
)
.await
.map_err(|err| retryable("amqp queue_bind", err))?;
}
Ok(())
}
}
impl Bus for RabbitBus {
async fn send(&self, name: &str, payload: Vec<u8>) -> Result<(), TransportError> {
self.send_message(Message::new(name, MessageKind::Command, payload))
.await
}
async fn publish(&self, name: &str, payload: Vec<u8>) -> Result<(), TransportError> {
self.publish_message(Message::new(name, MessageKind::Event, payload))
.await
}
async fn send_message(&self, mut message: Message) -> Result<(), TransportError> {
let queue = self.command_queue(message.name());
self.declare_queue(&self.channel, &queue).await?;
message.name = queue.clone();
self.publish_confirmed("", &queue, &message).await
}
async fn publish_message(&self, message: Message) -> Result<(), TransportError> {
self.declare_events_exchange(&self.channel).await?;
let routing_key = message.name().to_string();
self.publish_confirmed(&self.events_exchange, &routing_key, &message)
.await
}
}
impl BusConsumer for RabbitBus {
async fn listen<R: MessageRouter>(
&self,
router: Arc<R>,
options: RunOptions,
) -> Result<(), TransportError> {
let channel = connect_channel(&self.uri).await?;
let plan = router.subscription_plan();
let mut queues = Vec::new();
for name in &plan.commands {
let queue = self.command_queue(name);
self.declare_queue(&channel, &queue).await?;
queues.push(queue);
}
if queues.is_empty() {
return Ok(());
}
let source = RabbitBusSource {
channel,
queues,
strip_prefix: Some(self.command_prefix()),
};
run_source(router, source, options).await
}
async fn subscribe<R: MessageRouter>(
&self,
router: Arc<R>,
options: RunOptions,
) -> Result<(), TransportError> {
self.ensure_subscription(router.as_ref()).await?;
if router.subscription_plan().events.is_empty() {
return Ok(());
}
let channel = connect_channel(&self.uri).await?;
let source = RabbitBusSource {
channel,
queues: vec![self.group_queue()],
strip_prefix: None,
};
run_source(router, source, options).await
}
}
struct RabbitBusSource {
channel: Channel,
queues: Vec<String>,
strip_prefix: Option<String>,
}
impl AsyncMessageSource for RabbitBusSource {
type Received = RabbitReceived;
async fn recv(&mut self) -> Result<Option<Self::Received>, TransportError> {
for queue in &self.queues {
let got = self
.channel
.basic_get(queue, BasicGetOptions::default())
.await
.map_err(|err| retryable("amqp basic_get", err))?;
if let Some(get) = got {
let routing_key = get.delivery.routing_key.to_string();
let name = match &self.strip_prefix {
Some(prefix) => routing_key
.strip_prefix(prefix.as_str())
.unwrap_or(&routing_key)
.to_string(),
None => routing_key,
};
return Ok(Some(RabbitReceived::from_delivery_with_name(
get.delivery,
name,
)));
}
}
Ok(None)
}
}