use crate::parse_utils;
use std::collections::HashSet;
use syn::{Expr, ExprMethodCall, Item, Stmt, parse_file};
pub fn detect_existing_routes(content: &str) -> HashSet<(String, String)> {
let mut routes = HashSet::new();
let user_code = extract_user_code(content);
if let Ok(file) = parse_file(&user_code) {
for item in &file.items {
if let Item::Fn(func) = item {
extract_routes_from_function(&func.block.stmts, &mut routes);
}
}
}
routes
}
fn extract_user_code(content: &str) -> String {
if let Some(marker_pos) = content.find("dynami::generate!") {
let after_marker = marker_pos + "dynami::generate!".len();
if let Some((_, _open_pos, close_pos)) =
parse_utils::extract_braced_content(content, after_marker)
{
let before = &content[..marker_pos];
let after = if close_pos + 1 < content.len() {
&content[close_pos + 1..]
} else {
""
};
return format!("{}{}", before, after);
}
}
content.to_string()
}
fn extract_routes_from_function(stmts: &[Stmt], routes: &mut HashSet<(String, String)>) {
for stmt in stmts {
match stmt {
Stmt::Expr(expr, _) => {
extract_routes_from_expr(expr, routes);
}
Stmt::Local(local) => {
if let Some(init) = &local.init {
extract_routes_from_expr(&init.expr, routes);
}
}
_ => {}
}
}
}
fn extract_routes_from_expr(expr: &Expr, routes: &mut HashSet<(String, String)>) {
match expr {
Expr::Assign(assign) => {
extract_routes_from_expr(&assign.right, routes);
}
Expr::MethodCall(method_call) => {
if method_call.method == "route" {
if let Some(route_info) = extract_route_info(method_call) {
routes.insert(route_info);
}
}
extract_routes_from_expr(&method_call.receiver, routes);
}
Expr::Call(call) => {
extract_routes_from_expr(&call.func, routes);
}
Expr::Block(block) => {
for stmt in &block.block.stmts {
if let Stmt::Expr(e, _) = stmt {
extract_routes_from_expr(e, routes);
}
}
}
_ => {}
}
}
fn extract_route_info(method_call: &ExprMethodCall) -> Option<(String, String)> {
if method_call.args.len() >= 2 {
if let Some(path) = extract_string_literal(&method_call.args[0]) {
if let Some(method) = extract_method_name(&method_call.args[1]) {
return Some((path, method));
}
}
}
None
}
fn extract_string_literal(expr: &Expr) -> Option<String> {
if let Expr::Lit(expr_lit) = expr {
if let syn::Lit::Str(lit_str) = &expr_lit.lit {
return Some(lit_str.value());
}
}
None
}
fn extract_method_name(expr: &Expr) -> Option<String> {
if let Expr::Call(call) = expr {
if let Expr::Path(path) = &*call.func {
if let Some(ident) = path.path.get_ident() {
return Some(ident.to_string());
}
}
}
if let Expr::MethodCall(method_call) = expr {
return Some(method_call.method.to_string());
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_no_routes() {
let content = r#"
pub fn router() -> Router {
Router::new()
}
"#;
let routes = detect_existing_routes(content);
assert_eq!(routes.len(), 0);
}
#[test]
fn test_detect_single_route() {
let content = r#"
use axum::routing::get;
pub fn router() -> Router {
let mut router = Router::new();
router = router.route("/custom", get(custom_handler));
router
}
"#;
let routes = detect_existing_routes(content);
assert!(routes.contains(&("/custom".to_string(), "get".to_string())));
}
#[test]
fn test_ignore_generated_routes() {
let content = r#"
pub fn router() -> Router {
let mut router = Router::new();
router = router.route("/custom", get(custom_handler));
dynami::generate! {
router = router.route("/", get(get::handler))
}
router
}
"#;
let routes = detect_existing_routes(content);
assert_eq!(routes.len(), 1);
assert!(routes.contains(&("/custom".to_string(), "get".to_string())));
assert!(!routes.contains(&("/".to_string(), "get".to_string())));
}
#[test]
fn test_detect_multiple_methods_same_path() {
let content = r#"
pub fn router() -> Router {
Router::new()
.route("/api", get(get_handler).post(post_handler))
}
"#;
let routes = detect_existing_routes(content);
assert!(routes.len() >= 1);
}
}