use std::fmt;
use std::sync::Arc;
use bytes::Bytes;
use futures::Stream;
use rdkafka::Message as _;
use rdkafka::consumer::StreamConsumer;
use ruststream::Subscriber;
use crate::convert;
use crate::error::KafkaError;
use crate::message::{KafkaMessage, Settlement};
use crate::topic::Commit;
use crate::tracker::{CommitTracker, TrackingContext};
pub struct KafkaSubscriber {
consumer: Arc<StreamConsumer<TrackingContext>>,
topic: String,
commit: Commit,
tracker: Arc<CommitTracker>,
}
impl KafkaSubscriber {
pub(crate) fn new(
consumer: Arc<StreamConsumer<TrackingContext>>,
topic: String,
commit: Commit,
tracker: Arc<CommitTracker>,
) -> Self {
Self {
consumer,
topic,
commit,
tracker,
}
}
#[must_use]
pub fn topic(&self) -> &str {
&self.topic
}
fn map_delivery(&self, delivery: &rdkafka::message::BorrowedMessage<'_>) -> KafkaMessage {
let headers = convert::headers_from_message(delivery);
let payload = delivery
.payload()
.map_or_else(Bytes::new, Bytes::copy_from_slice);
let settlement = match self.commit {
Commit::Auto => Settlement::Advisory,
Commit::Tracked => {
self.tracker
.delivered(delivery.topic(), delivery.partition(), delivery.offset());
Settlement::Tracked {
consumer: Arc::clone(&self.consumer),
tracker: Arc::clone(&self.tracker),
}
}
};
KafkaMessage::new(
payload,
headers,
delivery.topic().to_owned(),
delivery.partition(),
delivery.offset(),
delivery.timestamp().to_millis(),
settlement,
)
}
}
impl fmt::Debug for KafkaSubscriber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KafkaSubscriber")
.field("topic", &self.topic)
.field("commit", &self.commit)
.finish_non_exhaustive()
}
}
impl Subscriber for KafkaSubscriber {
type Message = KafkaMessage;
type Error = KafkaError;
fn stream(&mut self) -> impl Stream<Item = Result<Self::Message, Self::Error>> + Send + '_ {
futures::stream::unfold(self, |sub| async move {
let item = match sub.consumer.recv().await {
Ok(delivery) => Ok(sub.map_delivery(&delivery)),
Err(err) => Err(KafkaError::consume(err)),
};
Some((item, sub))
})
}
}