use anyhow::{Context, Result};
use derive_builder::Builder;
use rdkafka::{
admin::{AdminClient, AdminOptions, NewTopic, TopicReplication},
client::DefaultClientContext,
consumer::{Consumer, StreamConsumer},
producer::FutureProducer,
};
use swiftide_core::indexing::TextNode;
pub use rdkafka::config::ClientConfig;
mod loader;
mod persist;
#[derive(Debug, Clone, Builder)]
#[builder(setter(into, strip_option))]
pub struct Kafka {
client_config: ClientConfig,
topic: String,
#[builder(default)]
persist_key_fn: Option<fn(&TextNode) -> Result<String>>,
#[builder(default)]
persist_payload_fn: Option<fn(&TextNode) -> Result<String>>,
#[builder(default = "1")]
partition: i32,
#[builder(default = "1")]
factor: i32,
#[builder(default)]
create_topic_if_not_exists: bool,
#[builder(default = "32")]
batch_size: usize,
}
impl Kafka {
pub fn from_client_config(config: impl Into<ClientConfig>, topic: impl Into<String>) -> Kafka {
Kafka {
client_config: config.into(),
topic: topic.into(),
persist_key_fn: None,
persist_payload_fn: None,
partition: 1,
factor: 1,
create_topic_if_not_exists: false,
batch_size: 32,
}
}
pub fn builder() -> KafkaBuilder {
KafkaBuilder::default()
}
fn producer(&self) -> Result<FutureProducer<DefaultClientContext>> {
self.client_config
.create()
.context("Failed to create producer")
}
fn topic_exists(&self) -> Result<bool> {
let consumer: StreamConsumer = self
.client_config
.create()
.context("Failed to create consumer")?;
let metadata = consumer.fetch_metadata(Some(&self.topic), None)?;
Ok(!metadata.topics().is_empty())
}
async fn create_topic(&self) -> Result<()> {
let admin_client: AdminClient<DefaultClientContext> = self
.client_config
.create()
.context("Failed to create admin client")?;
admin_client
.create_topics(
vec![&NewTopic::new(
&self.topic,
self.partition,
TopicReplication::Fixed(self.factor),
)],
&AdminOptions::new(),
)
.await?;
Ok(())
}
fn persist_key_for_node(&self, node: &TextNode) -> Result<String> {
if let Some(key_fn) = self.persist_key_fn {
key_fn(node)
} else {
let hash = node.id();
Ok(format!("{}:{}", node.path.to_string_lossy(), hash))
}
}
fn persist_value_for_node(&self, node: &TextNode) -> Result<String> {
if let Some(value_fn) = self.persist_payload_fn {
value_fn(node)
} else {
Ok(serde_json::to_string(node)?)
}
}
fn node_to_key_payload(&self, node: &TextNode) -> Result<(String, String)> {
let key = self
.persist_key_for_node(node)
.map_err(|e| anyhow::anyhow!("persist_key_for_node failed: {e:?} (node: {node:?})"))?;
let payload = self.persist_value_for_node(node).map_err(|e| {
anyhow::anyhow!("persist_value_for_node failed: {e:?} (node: {node:?})")
})?;
Ok((key, payload))
}
}