use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::Duration;
#[cfg(feature = "testing")]
use futures::future::Either;
use futures::{Stream, StreamExt};
use lapin::{Channel, Consumer};
use ruststream::{BatchSubscriber, BufferedSubscriber, Subscriber};
use crate::broker::AmqpConnection;
use crate::delay::DelayContext;
use crate::error::AmqpError;
#[cfg(feature = "testing")]
use crate::in_process::BusDeliveries;
use crate::message::LapinMessage;
pub struct LapinSubscriber {
deliveries: BufferedSubscriber<Deliveries>,
queue: String,
}
impl LapinSubscriber {
pub(crate) fn new(
connection: Arc<AmqpConnection>,
channel: Channel,
consumer: Consumer,
queue: String,
batch_wait: Duration,
delay: Option<DelayContext>,
) -> Self {
let deliveries = Deliveries::Amqp(AmqpDeliveries {
connection,
_channel: channel,
consumer,
delay,
});
Self {
deliveries: BufferedSubscriber::new(deliveries).max_wait(batch_wait),
queue,
}
}
#[cfg(feature = "testing")]
pub(crate) fn in_process(
deliveries: BusDeliveries,
queue: String,
batch_wait: Duration,
) -> Self {
Self {
deliveries: BufferedSubscriber::new(Deliveries::InProcess(deliveries))
.max_wait(batch_wait),
queue,
}
}
#[must_use]
pub fn queue(&self) -> &str {
&self.queue
}
}
impl std::fmt::Debug for LapinSubscriber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LapinSubscriber")
.field("queue", &self.queue)
.finish_non_exhaustive()
}
}
impl Subscriber for LapinSubscriber {
type Message = LapinMessage;
type Error = AmqpError;
fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
self.deliveries.stream()
}
}
impl BatchSubscriber for LapinSubscriber {
type Batch = Vec<LapinMessage>;
fn batches(
&mut self,
size: NonZeroUsize,
) -> impl Stream<Item = Result<Self::Batch, <Self as Subscriber>::Error>> + Send + '_ {
self.deliveries.batches(size)
}
}
#[cfg_attr(feature = "testing", allow(clippy::large_enum_variant))]
enum Deliveries {
Amqp(AmqpDeliveries),
#[cfg(feature = "testing")]
InProcess(BusDeliveries),
}
#[cfg(not(feature = "testing"))]
const _: () = assert!(size_of::<Deliveries>() == size_of::<AmqpDeliveries>());
impl Subscriber for Deliveries {
type Message = LapinMessage;
type Error = AmqpError;
fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
match self {
#[cfg(not(feature = "testing"))]
Self::Amqp(consumer) => consumer.stream(),
#[cfg(feature = "testing")]
Self::Amqp(consumer) => Either::Left(consumer.stream()),
#[cfg(feature = "testing")]
Self::InProcess(deliveries) => Either::Right(deliveries.stream()),
}
}
}
struct AmqpDeliveries {
connection: Arc<AmqpConnection>,
_channel: Channel,
consumer: Consumer,
delay: Option<DelayContext>,
}
impl AmqpDeliveries {
fn stream(&mut self) -> impl Stream<Item = Result<LapinMessage, AmqpError>> + Send + '_ {
let delay = self.delay.clone();
let connection = &self.connection;
futures::stream::unfold(
(&mut self.consumer, delay),
move |(consumer, delay)| async move {
let item = consumer.next().await?;
let mapped = match item {
Ok(delivery) => Ok(LapinMessage::from_delivery(
delivery,
Some(Arc::clone(connection)),
delay.clone(),
)),
Err(err) => Err(AmqpError::consume(err)),
};
Some((mapped, (consumer, delay)))
},
)
}
}