use std::fmt;
use std::sync::Arc;
use bytes::Bytes;
use rdkafka::consumer::{Consumer as _, StreamConsumer};
use ruststream::{AckError, Headers, IncomingMessage, Partitioned};
use crate::tracker::{CommitTracker, TrackingContext};
pub const PARTITION_KEY_HEADER: &str = "kafka-partition-key";
pub(crate) enum Settlement {
Advisory,
Tracked {
consumer: Arc<StreamConsumer<TrackingContext>>,
tracker: Arc<CommitTracker>,
},
}
#[derive(Debug)]
pub struct KafkaMessage {
payload: Bytes,
headers: Headers,
topic: String,
partition: i32,
offset: i64,
timestamp_millis: Option<i64>,
settlement: Settlement,
}
impl fmt::Debug for Settlement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Advisory => f.write_str("Advisory"),
Self::Tracked { .. } => f.debug_struct("Tracked").finish_non_exhaustive(),
}
}
}
impl KafkaMessage {
pub(crate) fn new(
payload: Bytes,
headers: Headers,
topic: String,
partition: i32,
offset: i64,
timestamp_millis: Option<i64>,
settlement: Settlement,
) -> Self {
Self {
payload,
headers,
topic,
partition,
offset,
timestamp_millis,
settlement,
}
}
#[must_use]
pub fn topic(&self) -> &str {
&self.topic
}
#[must_use]
pub fn partition(&self) -> i32 {
self.partition
}
#[must_use]
pub fn offset(&self) -> i64 {
self.offset
}
#[must_use]
pub fn timestamp_millis(&self) -> Option<i64> {
self.timestamp_millis
}
#[must_use]
pub fn key(&self) -> Option<&[u8]> {
self.headers.get(PARTITION_KEY_HEADER)
}
fn settle(self) -> Result<(), AckError> {
match self.settlement {
Settlement::Advisory => Ok(()),
Settlement::Tracked { consumer, tracker } => tracker
.settle_with(&self.topic, self.partition, self.offset, |position| {
consumer.store_offset(&self.topic, self.partition, position)
})
.map_err(|err| AckError::Broker(Box::new(err))),
}
}
}
impl IncomingMessage for KafkaMessage {
fn payload(&self) -> &[u8] {
&self.payload
}
fn headers(&self) -> &Headers {
&self.headers
}
async fn ack(self) -> Result<(), AckError> {
self.settle()
}
async fn nack(self, requeue: bool) -> Result<(), AckError> {
if requeue {
return Ok(());
}
self.settle()
}
fn partition_key(&self) -> Option<&[u8]> {
self.headers.get(PARTITION_KEY_HEADER)
}
}
impl Partitioned for KafkaMessage {
fn partition_key(&self) -> Option<&[u8]> {
self.headers.get(PARTITION_KEY_HEADER)
}
}