use anyhow::Result;
use std::path::PathBuf;
use crate::paths;
use crate::types::ProjectConfig;
use super::{NodeTopology, TopicRef, TopologyService};
impl TopologyService {
pub(super) async fn analyze_nodes(&self, config: &ProjectConfig) -> Result<Vec<NodeTopology>> {
let mut nodes = Vec::new();
for spec in config.nodes.get_node_specs() {
let topology = self.analyze_node(&spec.name, &spec.package_path(), None).await?;
nodes.push(topology);
}
Ok(nodes)
}
async fn analyze_node(&self, name: &str, package_path: &str, description: Option<&str>) -> Result<NodeTopology> {
let (publishes, subscribes) = self.load_topics_from_config(name).await.unwrap_or_else(|_| {
let source_path = match self.resolve_node_source(package_path) {
Ok(path) if path.exists() => path,
_ => return (Vec::new(), Vec::new()),
};
match tokio::task::block_in_place(|| {
let rt = tokio::runtime::Handle::current();
rt.block_on(self.parse_node_source(&source_path))
}) {
Ok((pubs, subs)) => (pubs, subs),
Err(_) => (Vec::new(), Vec::new()),
}
});
Ok(NodeTopology {
name: name.to_string(),
package: package_path.to_string(),
enabled: true,
description: description.map(String::from),
publishes,
subscribes,
})
}
async fn load_topics_from_config(&self, node_name: &str) -> Result<(Vec<TopicRef>, Vec<TopicRef>)> {
let mecha10_config_path = self.project_root.join(paths::config::framework_node(node_name));
let project_config_path = self.project_root.join(paths::config::project_node(node_name));
let framework_config_path = self
.project_root
.join(paths::framework::NODES_DIR)
.join(node_name)
.join("configs/config.json");
let config_path = if mecha10_config_path.exists() {
mecha10_config_path
} else if project_config_path.exists() {
project_config_path
} else if framework_config_path.exists() {
framework_config_path
} else {
anyhow::bail!(
"Config file not found at {}, {}, or {}",
mecha10_config_path.display(),
project_config_path.display(),
framework_config_path.display()
);
};
let content = tokio::fs::read_to_string(&config_path).await?;
let config: serde_json::Value = serde_json::from_str(&content)?;
let profile = std::env::var("MECHA10_ENVIRONMENT").unwrap_or_else(|_| "dev".to_string());
let env_config = if config.get("dev").is_some() || config.get("production").is_some() {
let section = match profile.as_str() {
"production" | "prod" => config.get("production"),
_ => config.get("dev"),
};
section.cloned().unwrap_or(config)
} else {
config
};
Ok(self.parse_topics_from_json(&env_config))
}
fn resolve_node_source(&self, package_path: &str) -> Result<PathBuf> {
if package_path.starts_with("./") || package_path.starts_with("../") {
let path = self.project_root.join(package_path).join("src/lib.rs");
Ok(path)
} else if package_path.starts_with("@mecha10/") || package_path.starts_with("mecha10-") {
let package_name = package_path
.strip_prefix("@mecha10/")
.or_else(|| package_path.strip_prefix("mecha10-"))
.unwrap_or(package_path);
let package_name = package_name
.strip_prefix("nodes-")
.or_else(|| package_name.strip_prefix("drivers-"))
.unwrap_or(package_name);
let framework_root = self.find_framework_root()?;
let nodes_path = framework_root
.join(paths::framework::NODES_DIR)
.join(package_name)
.join("src/lib.rs");
if nodes_path.exists() {
return Ok(nodes_path);
}
let drivers_path = framework_root
.join(paths::framework::DRIVERS_DIR)
.join(package_name)
.join("src/lib.rs");
if drivers_path.exists() {
return Ok(drivers_path);
}
Ok(nodes_path)
} else {
Ok(self
.project_root
.join(paths::project::NODES_DIR)
.join(package_path)
.join("src/lib.rs"))
}
}
fn find_framework_root(&self) -> Result<PathBuf> {
if self.project_root.join(paths::framework::NODES_DIR).exists() {
return Ok(self.project_root.clone());
}
let mut current = self.project_root.clone();
loop {
if current.join(paths::framework::NODES_DIR).exists() {
return Ok(current);
}
match current.parent() {
Some(parent) => current = parent.to_path_buf(),
None => break,
}
}
Ok(self.project_root.clone())
}
}