use anyhow::{Context, Result};
use regex::Regex;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use super::{TopicRef, TopologyService};
impl TopologyService {
pub(super) async fn parse_node_source(&self, source_path: &Path) -> Result<(Vec<TopicRef>, Vec<TopicRef>)> {
let content = tokio::fs::read_to_string(source_path)
.await
.context(format!("Failed to read source file: {}", source_path.display()))?;
let topic_defs = self.extract_topic_definitions(&content);
let publishes = self.extract_publish_calls(&content, &topic_defs);
let subscribes = self.extract_subscribe_calls(&content, &topic_defs);
Ok((publishes, subscribes))
}
pub fn extract_topic_definitions(&self, content: &str) -> HashMap<String, TopicRef> {
let mut topics = HashMap::new();
let topic_pattern =
Regex::new(r#"pub\s+const\s+([A-Z_]+):\s*Topic<([^>]+)>\s*=\s*Topic::new\("([^"]+)"\)"#).unwrap();
for caps in topic_pattern.captures_iter(content) {
let const_name = caps.get(1).map(|m| m.as_str()).unwrap_or("");
let message_type = caps.get(2).map(|m| m.as_str().trim()).unwrap_or("");
let topic_path = caps.get(3).map(|m| m.as_str()).unwrap_or("");
topics.insert(
const_name.to_string(),
TopicRef {
path: topic_path.to_string(),
message_type: Some(message_type.to_string()),
},
);
}
topics
}
pub fn extract_publish_calls(&self, content: &str, topic_defs: &HashMap<String, TopicRef>) -> Vec<TopicRef> {
let publish_pattern = Regex::new(r"publish_to\s*\(\s*(?:[a-z_]+::)?([A-Z_][A-Z0-9_]*)\s*,").unwrap();
Self::extract_topic_refs(content, topic_defs, &publish_pattern)
}
pub fn extract_subscribe_calls(&self, content: &str, topic_defs: &HashMap<String, TopicRef>) -> Vec<TopicRef> {
let subscribe_pattern =
Regex::new(r"subscribe\s*(?:::\s*<[^>]+>\s*)?\(\s*(?:[a-z_]+::)?([A-Z_][A-Z0-9_]*)\s*\)").unwrap();
Self::extract_topic_refs(content, topic_defs, &subscribe_pattern)
}
fn extract_topic_refs(content: &str, topic_defs: &HashMap<String, TopicRef>, pattern: &Regex) -> Vec<TopicRef> {
let mut refs = Vec::new();
let mut seen = HashSet::new();
for caps in pattern.captures_iter(content) {
if let Some(const_name) = caps.get(1) {
let const_name = const_name.as_str();
if let Some(topic) = topic_defs.get(const_name) {
if seen.insert(topic.path.clone()) {
refs.push(topic.clone());
}
}
}
}
refs
}
}