use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::Duration;
use futures::future::BoxFuture;
use crate::config::KafkaConfig;
use crate::error::KafkaError;
use crate::message::{Delivery, Message, Record};
pub trait Backend: Send + Sync + 'static {
fn producer(&self, config: &KafkaConfig) -> Result<Arc<dyn ProducerBackend>, KafkaError>;
fn consumer(
&self,
config: &KafkaConfig,
spec: &ConsumerSpec,
) -> Result<Box<dyn ConsumerBackend>, KafkaError>;
}
#[derive(Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ConsumerSpec {
pub name: String,
pub group_id: String,
pub topics: Vec<String>,
pub properties: BTreeMap<String, String>,
}
impl ConsumerSpec {
#[must_use]
pub fn new<I, S>(name: impl Into<String>, group_id: impl Into<String>, topics: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
name: name.into(),
group_id: group_id.into(),
topics: topics.into_iter().map(Into::into).collect(),
properties: BTreeMap::new(),
}
}
#[must_use]
pub fn with_property(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.properties.insert(key.into(), value.into());
self
}
}
impl std::fmt::Debug for ConsumerSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConsumerSpec")
.field("name", &self.name)
.field("group_id", &self.group_id)
.field("topics", &self.topics)
.field("properties", &crate::config::redacted(&self.properties))
.finish()
}
}
pub trait ProducerBackend: Send + Sync + 'static {
fn send(
&self,
record: Record,
timeout: Duration,
) -> BoxFuture<'_, Result<Delivery, KafkaError>>;
fn ping(&self, timeout: Duration) -> BoxFuture<'_, Result<(), KafkaError>>;
fn flush(&self, timeout: Duration) -> BoxFuture<'_, Result<(), KafkaError>>;
}
pub trait ConsumerBackend: Send + 'static {
fn recv(&mut self) -> BoxFuture<'_, Result<Message, KafkaError>>;
fn commit(&mut self, message: &Message) -> Result<(), KafkaError>;
fn close(self: Box<Self>) -> BoxFuture<'static, ()>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spec_debug_redacts_secrets() {
let spec = ConsumerSpec::new("c", "g", ["t"]).with_property("sasl.password", "hunter2");
let text = format!("{spec:?}");
assert!(!text.contains("hunter2"), "{text}");
assert!(text.contains("sasl.password"), "{text}");
}
}