use std::sync::{Arc, Mutex};
use bytes::Bytes;
use lapin::options::{BasicPublishOptions, ConfirmSelectOptions};
use lapin::{BasicProperties, Channel};
use lapin::{Confirmation, PublisherConfirm};
use ruststream::{Headers, OutgoingMessage, Publisher, TransactionalPublisher};
use tokio::sync::OnceCell;
use crate::broker::SharedConn;
use crate::convert;
use crate::error::AmqpError;
type Buffered = (String, Bytes, Headers);
pub(crate) async fn do_publish(
channel: &Channel,
exchange: &str,
routing_key: &str,
payload: &[u8],
properties: BasicProperties,
) -> Result<PublisherConfirm, AmqpError> {
channel
.basic_publish(
convert::short(exchange, "exchange name")?,
convert::short(routing_key, "routing key")?,
BasicPublishOptions::default(),
payload,
properties,
)
.await
.map_err(AmqpError::publish)
}
#[derive(Debug, Clone)]
pub struct LapinPublisher {
conn: SharedConn,
exchange: String,
persistent: bool,
}
impl LapinPublisher {
pub(crate) fn new(conn: SharedConn) -> Self {
Self {
conn,
exchange: String::new(),
persistent: true,
}
}
#[must_use]
pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
self.exchange = exchange.into();
self
}
#[must_use]
pub fn persistent(mut self, persistent: bool) -> Self {
self.persistent = persistent;
self
}
#[must_use]
pub fn confirms(self) -> ConfirmsPublisher {
ConfirmsPublisher {
conn: self.conn,
exchange: self.exchange,
persistent: self.persistent,
channel: Arc::new(OnceCell::new()),
txn: Arc::new(Mutex::new(None)),
}
}
#[must_use]
pub fn server_tx(self) -> ServerTxPublisher {
ServerTxPublisher {
conn: self.conn,
exchange: self.exchange,
persistent: self.persistent,
channel: Arc::new(OnceCell::new()),
open: Arc::new(Mutex::new(false)),
}
}
}
impl Publisher for LapinPublisher {
type Error = AmqpError;
async fn publish(&self, msg: OutgoingMessage<'_>) -> Result<(), Self::Error> {
let state = self.conn.get().ok_or(AmqpError::NotConnected)?;
let properties = convert::properties_for_publish(msg.headers(), self.persistent)?;
let _confirm = do_publish(
state.publish_channel(),
&self.exchange,
msg.name(),
msg.payload(),
properties,
)
.await?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ConfirmsPublisher {
conn: SharedConn,
exchange: String,
persistent: bool,
channel: Arc<OnceCell<Channel>>,
txn: Arc<Mutex<Option<Vec<Buffered>>>>,
}
impl ConfirmsPublisher {
async fn channel(&self) -> Result<&Channel, AmqpError> {
self.channel
.get_or_try_init(|| async {
let state = self.conn.get().ok_or(AmqpError::NotConnected)?;
let channel = state
.connection()
.create_channel()
.await
.map_err(AmqpError::publish)?;
channel
.confirm_select(ConfirmSelectOptions::default())
.await
.map_err(AmqpError::publish)?;
Ok(channel)
})
.await
}
async fn publish_confirmed(
&self,
routing_key: &str,
payload: &[u8],
headers: &Headers,
) -> Result<(), AmqpError> {
let channel = self.channel().await?;
let properties = convert::properties_for_publish(headers, self.persistent)?;
let confirm = do_publish(channel, &self.exchange, routing_key, payload, properties)
.await?
.await
.map_err(AmqpError::publish)?;
confirmation_ok(&confirm, routing_key)
}
}
fn confirmation_ok(confirmation: &Confirmation, routing_key: &str) -> Result<(), AmqpError> {
if confirmation.is_nack() {
return Err(AmqpError::Publish(
format!("the broker negatively confirmed the publish to {routing_key:?}").into(),
));
}
Ok(())
}
impl Publisher for ConfirmsPublisher {
type Error = AmqpError;
async fn publish(&self, msg: OutgoingMessage<'_>) -> Result<(), Self::Error> {
{
let mut txn = self.txn.lock().expect("transaction buffer mutex poisoned");
if let Some(buffer) = txn.as_mut() {
buffer.push((
msg.name().to_owned(),
Bytes::copy_from_slice(msg.payload()),
msg.headers().clone(),
));
return Ok(());
}
}
self.publish_confirmed(msg.name(), msg.payload(), msg.headers())
.await
}
}
impl TransactionalPublisher for ConfirmsPublisher {
async fn begin_transaction(&self) -> Result<(), Self::Error> {
self.txn
.lock()
.expect("transaction buffer mutex poisoned")
.get_or_insert_with(Vec::new);
Ok(())
}
async fn commit(&self) -> Result<(), Self::Error> {
let buffered = {
let mut txn = self.txn.lock().expect("transaction buffer mutex poisoned");
txn.take()
};
let Some(buffered) = buffered else {
return Ok(());
};
if buffered.is_empty() {
return Ok(());
}
let channel = self.channel().await?;
let mut confirms = Vec::with_capacity(buffered.len());
for (routing_key, payload, headers) in &buffered {
let properties = convert::properties_for_publish(headers, self.persistent)?;
let confirm =
do_publish(channel, &self.exchange, routing_key, payload, properties).await?;
confirms.push((routing_key, confirm));
}
for (routing_key, confirm) in confirms {
let confirmation = confirm.await.map_err(AmqpError::publish)?;
confirmation_ok(&confirmation, routing_key)?;
}
Ok(())
}
async fn abort(&self) -> Result<(), Self::Error> {
self.txn
.lock()
.expect("transaction buffer mutex poisoned")
.take();
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ServerTxPublisher {
conn: SharedConn,
exchange: String,
persistent: bool,
channel: Arc<OnceCell<Channel>>,
open: Arc<Mutex<bool>>,
}
impl ServerTxPublisher {
async fn tx_channel(&self) -> Result<&Channel, AmqpError> {
self.channel
.get_or_try_init(|| async {
let state = self.conn.get().ok_or(AmqpError::NotConnected)?;
let channel = state
.connection()
.create_channel()
.await
.map_err(AmqpError::publish)?;
channel.tx_select().await.map_err(AmqpError::publish)?;
Ok(channel)
})
.await
}
fn is_open(&self) -> bool {
*self.open.lock().expect("transaction state mutex poisoned")
}
fn set_open(&self, open: bool) {
*self.open.lock().expect("transaction state mutex poisoned") = open;
}
}
impl Publisher for ServerTxPublisher {
type Error = AmqpError;
async fn publish(&self, msg: OutgoingMessage<'_>) -> Result<(), Self::Error> {
let properties = convert::properties_for_publish(msg.headers(), self.persistent)?;
let channel = if self.is_open() {
self.tx_channel().await?
} else {
let state = self.conn.get().ok_or(AmqpError::NotConnected)?;
state.publish_channel()
};
let _confirm = do_publish(
channel,
&self.exchange,
msg.name(),
msg.payload(),
properties,
)
.await?;
Ok(())
}
}
impl TransactionalPublisher for ServerTxPublisher {
async fn begin_transaction(&self) -> Result<(), Self::Error> {
self.tx_channel().await?;
self.set_open(true);
Ok(())
}
async fn commit(&self) -> Result<(), Self::Error> {
if !self.is_open() {
return Ok(());
}
let channel = self.tx_channel().await?;
channel.tx_commit().await.map_err(AmqpError::publish)?;
self.set_open(false);
Ok(())
}
async fn abort(&self) -> Result<(), Self::Error> {
if !self.is_open() {
return Ok(());
}
let channel = self.tx_channel().await?;
channel.tx_rollback().await.map_err(AmqpError::publish)?;
self.set_open(false);
Ok(())
}
}