use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub fn extract_category(topic: &str) -> String {
let parts: Vec<&str> = topic.trim_start_matches('/').split('/').collect();
if parts.is_empty() || parts[0].is_empty() {
"uncategorized".to_string()
} else {
format!("/{}", parts[0])
}
}
pub fn format_elapsed_time(elapsed_ms: u64) -> String {
if elapsed_ms < 1000 {
format!("{}ms ago", elapsed_ms)
} else if elapsed_ms < 60_000 {
format!("{}s ago", elapsed_ms / 1000)
} else if elapsed_ms < 3_600_000 {
format!("{}m ago", elapsed_ms / 60_000)
} else if elapsed_ms < 86_400_000 {
format!("{}h ago", elapsed_ms / 3_600_000)
} else {
format!("{}d ago", elapsed_ms / 86_400_000)
}
}
#[derive(Debug, Clone)]
pub struct TopicMetadata {
pub topic: String,
pub message_count: Option<usize>,
pub last_update_ms: Option<u64>,
pub first_timestamp_ms: Option<u64>,
pub last_timestamp_ms: Option<u64>,
}
impl TopicMetadata {
pub fn new(topic: String) -> Self {
Self {
topic,
message_count: None,
last_update_ms: None,
first_timestamp_ms: None,
last_timestamp_ms: None,
}
}
pub fn with_message_count(mut self, count: usize) -> Self {
self.message_count = Some(count);
self
}
pub fn with_last_update(mut self, timestamp_ms: u64) -> Self {
self.last_update_ms = Some(timestamp_ms);
self
}
pub fn with_timestamp_range(mut self, first_ms: u64, last_ms: u64) -> Self {
self.first_timestamp_ms = Some(first_ms);
self.last_timestamp_ms = Some(last_ms);
self
}
pub fn calculate_rate(&self) -> Option<f64> {
if let (Some(first_ms), Some(last_ms), Some(count)) =
(self.first_timestamp_ms, self.last_timestamp_ms, self.message_count)
{
let duration_ms = last_ms.saturating_sub(first_ms);
if duration_ms > 1000 && count > 0 {
return Some((count as f64 * 1000.0) / duration_ms as f64);
}
}
None
}
pub fn category(&self) -> String {
extract_category(&self.topic)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Topics {
#[serde(default)]
pub publishes: Vec<HashMap<String, String>>,
#[serde(default)]
pub subscribes: Vec<HashMap<String, String>>,
}
impl Topics {
pub fn subscribed_topic(&self, role: &str, default: &str) -> String {
Self::lookup(&self.subscribes, role, default)
}
pub fn published_topic(&self, role: &str, default: &str) -> String {
Self::lookup(&self.publishes, role, default)
}
fn lookup(topics: &[HashMap<String, String>], role: &str, default: &str) -> String {
topics
.iter()
.find_map(|t| t.get(role))
.cloned()
.unwrap_or_else(|| default.to_string())
}
}