use std::num::NonZeroU16;
use std::time::Duration;
use lapin::types::{AMQPValue, FieldTable};
#[cfg(feature = "asyncapi")]
use ruststream::asyncapi::Bindings;
use ruststream::runtime::IntoSource;
use ruststream::{BrokerMoves, RetryDeclaration, SubscriptionSource};
use super::spec::{
Binding, DEAD_LETTER_EXCHANGE, DEAD_LETTER_ROUTING_KEY, DELIVERY_LIMIT, QueueKind, QueueSpec,
queue_settings,
};
use crate::broker::ConnectedLapinBroker;
use crate::delay::Delay;
use crate::error::AmqpError;
use crate::exchange::RabbitExchange;
use crate::subscriber::LapinSubscriber;
#[derive(Debug, Clone, PartialEq)]
pub struct RabbitQuorumQueue {
spec: QueueSpec,
retry: RetryDeclaration,
}
impl RabbitQuorumQueue {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
spec: QueueSpec::new(QueueKind::Quorum, name.into()),
retry: RetryDeclaration::new(),
}
}
#[must_use]
pub fn delivery_limit(mut self, returns: u32) -> Self {
self.spec
.set(DELIVERY_LIMIT, AMQPValue::LongLongInt(i64::from(returns)));
self
}
pub(crate) const fn spec(&self) -> &QueueSpec {
&self.spec
}
pub(crate) const fn retry(&self) -> &RetryDeclaration {
&self.retry
}
pub(crate) fn check_retry(&self, declares_topology: bool) -> Result<(), AmqpError> {
match (self.retry.max_attempts(), self.retry.dead_letter()) {
(Some(_), Some(_)) if declares_topology => Ok(()),
(None, None) => Ok(()),
(Some(_), None) => Err(AmqpError::InvalidOptions(format!(
"quorum queue {:?} was mounted with `max_attempts(..)` and no `dead_letter(..)`: \
the queue carries a spent delivery away only when it knows where to, and a \
delivery limit on its own drops it. Declare both at the mount site, or neither \
and let the queue keep its own policy",
self.name(),
))),
(None, Some(_)) => Err(AmqpError::InvalidOptions(format!(
"quorum queue {:?} was mounted with `dead_letter(..)` and no `max_attempts(..)`: \
nothing tells the queue when a delivery is spent, so the destination is never \
reached. Declare both at the mount site, or neither and let the queue keep its \
own policy",
self.name(),
))),
(Some(_), Some(_)) => Err(AmqpError::InvalidOptions(format!(
"quorum queue {:?} applies `max_attempts(..).dead_letter(..)` as its own \
arguments, and this service does not declare it. Enable \
`declare_topology(true)`, or configure `x-delivery-limit` and \
`x-dead-letter-exchange` on the queue itself and mount the handler without the \
pair",
self.name(),
))),
}
}
}
queue_settings!(RabbitQuorumQueue);
impl IntoSource for RabbitQuorumQueue {
type Source = Self;
fn into_source(self) -> Self {
self
}
}
impl SubscriptionSource<ConnectedLapinBroker> for RabbitQuorumQueue {
type Subscriber = LapinSubscriber;
type Copies = BrokerMoves;
fn name(&self) -> &str {
&self.spec.name
}
async fn subscribe(
self,
connected: &ConnectedLapinBroker,
) -> Result<Self::Subscriber, AmqpError> {
connected.subscribe(self).await
}
fn declare_retry(mut self, declaration: &RetryDeclaration) -> Self {
self.retry = declaration.clone();
self
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self) -> Bindings {
crate::bindings::queue_channel(&self.spec)
}
#[cfg(feature = "asyncapi")]
fn operation_bindings(&self) -> Bindings {
crate::bindings::consumer_operation()
}
}
#[cfg(test)]
mod tests {
use lapin::types::{AMQPValue, ShortString};
use ruststream::{RetryDeclaration, SubscriptionSource, nonzero};
use super::RabbitQuorumQueue;
use crate::broker::ConnectedLapinBroker;
use crate::queue::spec::declared_arguments;
#[test]
fn a_declaration_becomes_the_quorum_queues_arguments() {
let declaration = RetryDeclaration::new()
.with_max_attempts(nonzero!(3u32))
.with_dead_letter("orders.dead");
let queue = <RabbitQuorumQueue as SubscriptionSource<ConnectedLapinBroker>>::declare_retry(
RabbitQuorumQueue::new("orders.quorum"),
&declaration,
);
let arguments = declared_arguments(queue.spec(), Some(queue.retry()));
let argument = |name: &str| arguments.inner().get(&ShortString::from(name)).cloned();
assert_eq!(
argument("x-queue-type"),
Some(AMQPValue::LongString("quorum".into()))
);
assert_eq!(
argument("x-delivery-limit"),
Some(AMQPValue::LongLongInt(2))
);
assert_eq!(
argument("x-dead-letter-exchange"),
Some(AMQPValue::LongString(String::new().into()))
);
assert_eq!(
argument("x-dead-letter-routing-key"),
Some(AMQPValue::LongString("orders.dead".into()))
);
}
}