use std::fs;
use std::path::Path;
use crate::error::TemplateError;
#[derive(Debug)]
pub struct RegisterResult {
pub registered: bool,
pub message: String,
}
pub fn detect_and_register_handler(
search_root: &Path,
module_name: &str,
type_name: &str,
) -> Result<RegisterResult, TemplateError> {
let candidates = ["lib.rs", "handlers.rs"];
for filename in &candidates {
let path = search_root.join(filename);
if !path.exists() {
continue;
}
let content = fs::read_to_string(&path).map_err(TemplateError::Io)?;
if !content.contains("fn handlers()") {
continue;
}
let updated = insert_handler_registration(&content, module_name, type_name);
if let Some(new_content) = updated {
fs::write(&path, new_content).map_err(TemplateError::Io)?;
return Ok(RegisterResult {
registered: true,
message: format!("Registered {type_name} in {}", path.display()),
});
}
}
Ok(RegisterResult {
registered: false,
message: format!(
"Could not detect handlers() function. Add manually:\n\
\n\
mod {module_name};\n\
pub use {module_name}::{type_name};\n\
\n\
// In handlers():\n\
Box::new({type_name}),",
),
})
}
fn insert_handler_registration(
content: &str,
module_name: &str,
type_name: &str,
) -> Option<String> {
let rust_module = module_name.replace('-', "_");
if content.contains(&format!("mod {rust_module};")) {
return None;
}
insert_handler_simple(content, &rust_module, type_name)
}
fn insert_handler_simple(content: &str, rust_module: &str, type_name: &str) -> Option<String> {
let lines: Vec<&str> = content.lines().collect();
let last_mod_idx = lines.iter().rposition(|l| {
let t = l.trim();
t.starts_with("mod ") && t.ends_with(';')
})?;
let last_use_idx = lines.iter().rposition(|l| {
let t = l.trim();
t.starts_with("pub use ") && t.ends_with(';')
})?;
let handlers_idx = lines.iter().position(|l| l.contains("fn handlers()"))?;
let vec_close_idx = lines[handlers_idx..].iter().rposition(|l| {
let t = l.trim();
t == "]" || t == "];"
})?;
let vec_close_idx = handlers_idx + vec_close_idx;
let mut result = Vec::with_capacity(lines.len() + 3);
for (i, line) in lines.iter().enumerate() {
result.push((*line).to_string());
if i == last_mod_idx {
result.push(format!("mod {rust_module};"));
}
if i == last_use_idx {
result.push(format!("pub use {rust_module}::{type_name};"));
}
if i == vec_close_idx - 1 {
result.push(format!(" Box::new({type_name}),"));
}
}
let mut output = result.join("\n");
if content.ends_with('\n') {
output.push('\n');
}
Some(output)
}
#[cfg(test)]
mod tests {
use tempfile::TempDir;
use super::*;
fn sample_lib_rs() -> &'static str {
"\
mod greeting;
mod deploy;
pub use greeting::Greeting;
pub use deploy::Deploy;
use ironflow_engine::handler::WorkflowHandler;
pub fn handlers() -> Vec<Box<dyn WorkflowHandler>> {
vec![
Box::new(Greeting),
Box::new(Deploy),
]
}
"
}
#[test]
fn register_in_handlers() {
let tmp = TempDir::new().unwrap();
let lib_path = tmp.path().join("lib.rs");
fs::write(&lib_path, sample_lib_rs()).unwrap();
let result = detect_and_register_handler(tmp.path(), "ci-pipeline", "CiPipeline").unwrap();
assert!(result.registered);
assert!(result.message.contains("CiPipeline"));
let content = fs::read_to_string(&lib_path).unwrap();
assert!(content.contains("mod ci_pipeline;"));
assert!(content.contains("pub use ci_pipeline::CiPipeline;"));
assert!(content.contains("Box::new(CiPipeline),"));
}
#[test]
fn handlers_not_found() {
let tmp = TempDir::new().unwrap();
let lib_path = tmp.path().join("lib.rs");
fs::write(&lib_path, "// no handlers here\n").unwrap();
let result = detect_and_register_handler(tmp.path(), "ci-pipeline", "CiPipeline").unwrap();
assert!(!result.registered);
assert!(result.message.contains("Add manually"));
assert!(result.message.contains("CiPipeline"));
}
#[test]
fn no_candidate_files() {
let tmp = TempDir::new().unwrap();
let result = detect_and_register_handler(tmp.path(), "ci-pipeline", "CiPipeline").unwrap();
assert!(!result.registered);
}
#[test]
fn already_registered_returns_none() {
let content = "\
mod ci_pipeline;
mod deploy;
pub use ci_pipeline::CiPipeline;
pub use deploy::Deploy;
pub fn handlers() -> Vec<Box<dyn WorkflowHandler>> {
vec![
Box::new(CiPipeline),
Box::new(Deploy),
]
}
";
let result = insert_handler_registration(content, "ci-pipeline", "CiPipeline");
assert!(result.is_none());
}
#[test]
fn hyphenated_name_uses_underscore() {
let tmp = TempDir::new().unwrap();
let lib_path = tmp.path().join("lib.rs");
fs::write(&lib_path, sample_lib_rs()).unwrap();
detect_and_register_handler(tmp.path(), "my-template", "MyTemplate").unwrap();
let content = fs::read_to_string(&lib_path).unwrap();
assert!(content.contains("mod my_template;"));
assert!(content.contains("pub use my_template::MyTemplate;"));
}
}