use crate::import_analyzer;
use crate::method_analyzer;
use crate::parser;
use crate::route_detector;
use crate::walker::RouteNode;
use std::collections::HashSet;
pub fn generate_router(
node: &RouteNode,
state_name: Option<&str>,
existing_content: &str,
) -> String {
let mut mod_declarations = Vec::new();
let mut imports = vec!["use axum::Router;".to_string()];
for method_file in &node.method_files {
if parser::extract_method_from_filename(method_file).is_some() {
let module_name = method_file.strip_suffix(".rs").unwrap_or(method_file);
mod_declarations.push(format!("pub mod {};", module_name));
}
}
let router_type = if let Some(state) = state_name {
format!("Router<{}>", state)
} else {
"Router".to_string()
};
let mutations = generate_mutations_internal(node, existing_content);
let existing_methods = import_analyzer::extract_existing_routing_imports(existing_content);
let needed_methods = method_analyzer::extract_http_methods(&mutations);
let mut all_methods: HashSet<String> = existing_methods;
all_methods.extend(needed_methods);
if let Some(routing_import) = import_analyzer::generate_routing_import(&all_methods) {
imports.push(routing_import);
}
let mod_decls_str = if mod_declarations.is_empty() {
String::new()
} else {
mod_declarations.join("\n")
};
let cleaned_imports = if existing_content.contains("use axum::Router") {
imports
.iter()
.skip(1)
.cloned()
.collect::<Vec<_>>()
.join("\n")
} else {
imports.join("\n")
};
let imports_str = cleaned_imports;
let generate_block = if mutations.is_empty() {
" dynami::generate! {\n }".to_string()
} else {
format!(" dynami::generate! {{\n{}\n }}", mutations)
};
format!(
r#"{}
{}
pub fn router() -> {} {{
let mut router = Router::new();
{}
router
}}
"#,
mod_decls_str, imports_str, router_type, generate_block
)
}
pub fn generate_mutations(node: &RouteNode, existing_content: &str) -> String {
generate_mutations_internal(node, existing_content)
}
pub fn calculate_routing_import(existing_content: &str, mutations: &str) -> Option<String> {
let existing_methods = import_analyzer::extract_existing_routing_imports(existing_content);
let needed_methods = method_analyzer::extract_http_methods(mutations);
let mut all_methods: HashSet<String> = existing_methods;
all_methods.extend(needed_methods);
import_analyzer::generate_routing_import(&all_methods)
}
fn generate_mutations_internal(node: &RouteNode, existing_content: &str) -> String {
let existing_routes = route_detector::detect_existing_routes(existing_content);
let mut router_mutations = Vec::new();
if !node.method_files.is_empty() {
let mut method_handlers = Vec::new();
for method_file in &node.method_files {
if let Some(method) = parser::extract_method_from_filename(method_file) {
if !existing_routes.contains(&("/".to_string(), method.to_string())) {
let module_name = method_file.strip_suffix(".rs").unwrap_or(method_file);
method_handlers.push(format!("{}({}::handler)", method, module_name));
}
}
}
if !method_handlers.is_empty() {
router_mutations.push(format!(
" router = router.route(\"/\", {});",
method_handlers.join(".")
));
}
}
for child in &node.children {
let child_name = child
.path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown");
router_mutations.push(format!(
" router = router.nest(\"{}\", {}::router());",
child.route_segment, child_name
));
}
router_mutations.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_generate_router_simple() {
let node = RouteNode {
path: PathBuf::from("/routes"),
route_segment: "/".to_string(),
method_files: vec!["get.rs".to_string(), "post.rs".to_string()],
has_mod_rs: true,
children: vec![],
};
let result = generate_router(&node, None, "");
assert!(result.contains("use axum::Router;"));
assert!(result.contains("use axum::routing::"));
assert!(result.contains("pub fn router() -> Router"));
assert!(result.contains("let mut router = Router::new();"));
assert!(result.contains("dynami::generate!"));
assert!(
result.contains("router = router.route(\"/\", get(get::handler).post(post::handler));")
);
}
#[test]
fn test_generate_router_with_state() {
let node = RouteNode {
path: PathBuf::from("/routes"),
route_segment: "/".to_string(),
method_files: vec!["get.rs".to_string()],
has_mod_rs: true,
children: vec![],
};
let result = generate_router(&node, Some("AppState"), "");
assert!(result.contains("pub fn router() -> Router<AppState>"));
}
#[test]
fn test_generate_router_with_nested() {
let child = RouteNode {
path: PathBuf::from("/routes/api"),
route_segment: "/api".to_string(),
method_files: vec![],
has_mod_rs: true,
children: vec![],
};
let node = RouteNode {
path: PathBuf::from("/routes"),
route_segment: "/".to_string(),
method_files: vec!["get.rs".to_string()],
has_mod_rs: true,
children: vec![child],
};
let result = generate_router(&node, None, "");
assert!(result.contains("router = router.nest(\"/api\", api::router());"));
}
#[test]
fn test_generate_router_dynamic_route() {
let child = RouteNode {
path: PathBuf::from("/routes/d_id"),
route_segment: "/{id}".to_string(),
method_files: vec![],
has_mod_rs: true,
children: vec![],
};
let node = RouteNode {
path: PathBuf::from("/routes"),
route_segment: "/".to_string(),
method_files: vec![],
has_mod_rs: true,
children: vec![child],
};
let result = generate_router(&node, None, "");
assert!(result.contains("router = router.nest(\"/{id}\", d_id::router());"));
}
#[test]
fn test_smart_import_generation() {
let node = RouteNode {
path: PathBuf::from("/routes"),
route_segment: "/".to_string(),
method_files: vec!["get.rs".to_string(), "post.rs".to_string()],
has_mod_rs: true,
children: vec![],
};
let result = generate_router(&node, None, "");
assert!(result.contains("use axum::routing::{get}"));
assert!(!result.contains("delete"));
assert!(!result.contains("put"));
}
#[test]
fn test_no_routing_imports_for_only_nested() {
let child = RouteNode {
path: PathBuf::from("/routes/api"),
route_segment: "/api".to_string(),
method_files: vec![],
has_mod_rs: true,
children: vec![],
};
let node = RouteNode {
path: PathBuf::from("/routes"),
route_segment: "/".to_string(),
method_files: vec![],
has_mod_rs: true,
children: vec![child],
};
let result = generate_router(&node, None, "");
assert!(!result.contains("use axum::routing::{"));
assert!(result.contains("use axum::Router;"));
}
}