use lib::{
Error, ExportedKafkaRecord, KafkaRecord, TopicConfig, TopicDetail, kafka::SchemaRegistryClient,
};
use rdkafka::{
TopicPartitionList,
consumer::{Consumer as AA, StreamConsumer},
};
use thousands::Separable;
use tracing::info;
use std::{collections::HashSet, fs, time::Duration};
use itertools::Itertools;
use crate::{
AdminClient,
configuration::{Configuration, ConsumerConfig, InternalConfig, YozefuConfig},
consumer::Consumer,
search::ValidSearchQuery,
};
#[derive(Debug, Clone)]
pub struct App {
pub cluster: String,
pub config: InternalConfig,
pub search_query: ValidSearchQuery,
}
impl App {
pub fn new(cluster: String, config: InternalConfig, search_query: ValidSearchQuery) -> Self {
Self {
cluster,
config,
search_query,
}
}
pub fn schema_registry(&self) -> Option<SchemaRegistryClient> {
match self.config.schema_registry_config_of(&self.cluster) {
Some(config) => Some(SchemaRegistryClient::new(config.url, &config.headers)),
None => None,
}
}
pub fn create_consumer_2(&self, topics: &Vec<String>) -> Result<Consumer, Error> {
Consumer::new(
self.config.specific.clone(),
self.consumer_config(),
self.search_query.query().clone(),
topics,
)
}
pub fn create_consumer(&self, topics: &Vec<String>) -> Result<StreamConsumer, Error> {
Ok(self.create_consumer_2(topics)?.stream_consumer())
}
pub fn consumer_config(&self) -> ConsumerConfig {
self.config.consumer_config(&self.cluster)
}
pub fn export_record(&self, record: &KafkaRecord) -> Result<(), Error> {
let output_file = self.config.output_file();
fs::create_dir_all(output_file.parent().unwrap())?;
let content = fs::read_to_string(output_file).unwrap_or("[]".to_string());
let mut exported_records: Vec<ExportedKafkaRecord> = serde_json::from_str(&content)?;
let mut exported_record_kafka: ExportedKafkaRecord = record.into();
exported_record_kafka.set_search_query(self.search_query.query());
exported_records.push(exported_record_kafka);
exported_records.sort_by(|a, b| {
a.record
.timestamp
.cmp(&b.record.timestamp)
.then(a.record.offset.cmp(&b.record.offset))
});
exported_records.dedup();
for i in 1..exported_records.len() {
let first_ts = exported_records.first().unwrap().record.timestamp;
let previous_ts = exported_records.get(i - 1).unwrap().record.timestamp;
let current = exported_records.get_mut(i).unwrap();
current.compute_deltas_ms(first_ts, previous_ts);
}
fs::write(
output_file,
serde_json::to_string_pretty(&exported_records)?,
)?;
info!(
"A record has been exported into file '{}'",
output_file.display()
);
Ok(())
}
pub fn estimate_number_of_records_to_read(
&self,
topic_partition_list: &TopicPartitionList,
) -> Result<i64, Error> {
let count = self
.admin_client()?
.estimate_number_of_records_to_read(topic_partition_list)?;
info!(
"{} records are about to be consumed from the following topic partitions: [{}]",
count.separate_with_underscores(),
topic_partition_list
.elements()
.iter()
.map(|e| format!("{}-{}", e.topic(), e.partition()))
.join(", ")
);
Ok(count)
}
pub fn topic_details(&self, topics: HashSet<String>) -> Result<Vec<TopicDetail>, Error> {
self.admin_client()?.topic_details(topics)
}
pub async fn topic_config_of(&self, topic: &str) -> Result<Option<TopicConfig>, Error> {
self.admin_client()?.topic_config(topic).await
}
pub fn admin_client(&self) -> Result<AdminClient, Error> {
AdminClient::new(self.config.clone())
}
pub fn list_topics(&self) -> Result<Vec<String>, Error> {
self.admin_client()?.list_topics()
}
pub fn list_topics_from_client(yozefu_config: &YozefuConfig) -> Result<Vec<String>, Error> {
let consumer: StreamConsumer = yozefu_config.create_kafka_consumer()?;
let metadata = consumer.fetch_metadata(None, Duration::from_secs(3))?;
let topics = metadata
.topics()
.iter()
.map(|t| t.name().to_string())
.collect_vec();
Ok(topics)
}
}