use anyhow::{Context, Result};
use std::fs;
use std::path::{Path, PathBuf};
use crate::parser;
#[derive(Debug, Clone)]
pub struct RouteNode {
pub path: PathBuf,
pub route_segment: String,
pub method_files: Vec<String>,
#[allow(dead_code)]
pub has_mod_rs: bool,
pub children: Vec<RouteNode>,
}
pub fn scan_routes(root: &Path) -> Result<RouteNode> {
let root = root
.canonicalize()
.with_context(|| format!("Failed to canonicalize path: {}", root.display()))?;
scan_directory(&root, true)
}
fn scan_directory(dir: &Path, is_root: bool) -> Result<RouteNode> {
let folder_name = if is_root {
""
} else {
dir.file_name().and_then(|n| n.to_str()).unwrap_or("")
};
let route_segment = if is_root {
"/".to_string()
} else {
parser::parse_route_segment(folder_name)
};
let mod_rs_path = dir.join("mod.rs");
let has_mod_rs = mod_rs_path.exists();
let mut method_files = Vec::new();
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
if let Ok(file_type) = entry.file_type() {
if file_type.is_file() {
if let Some(name) = entry.file_name().to_str() {
if parser::extract_method_from_filename(name).is_some() {
method_files.push(name.to_string());
}
}
}
}
}
}
let mut children = Vec::new();
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
if let Ok(file_type) = entry.file_type() {
if file_type.is_dir() {
let subdir = entry.path();
if subdir.join("mod.rs").exists() || is_empty_or_will_need_modrs(&subdir)? {
if let Ok(child_node) = scan_directory(&subdir, false) {
children.push(child_node);
}
}
}
}
}
}
Ok(RouteNode {
path: dir.to_path_buf(),
route_segment,
method_files,
has_mod_rs,
children,
})
}
fn is_empty_or_will_need_modrs(dir: &Path) -> Result<bool> {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
if let Ok(file_type) = entry.file_type() {
if file_type.is_file() {
if let Some(name) = entry.file_name().to_str() {
if parser::extract_method_from_filename(name).is_some() {
return Ok(true);
}
}
} else if file_type.is_dir() {
return Ok(true);
}
}
}
}
Ok(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_scan_simple_structure() -> Result<()> {
let temp = TempDir::new()?;
let root = temp.path();
fs::write(root.join("mod.rs"), "")?;
fs::write(root.join("get.rs"), "")?;
fs::write(root.join("post.rs"), "")?;
let node = scan_routes(root)?;
assert_eq!(node.route_segment, "/");
assert!(node.has_mod_rs);
assert_eq!(node.method_files.len(), 2);
assert!(node.method_files.contains(&"get.rs".to_string()));
assert!(node.method_files.contains(&"post.rs".to_string()));
Ok(())
}
#[test]
fn test_scan_nested_structure() -> Result<()> {
let temp = TempDir::new()?;
let root = temp.path();
fs::write(root.join("mod.rs"), "")?;
fs::write(root.join("get.rs"), "")?;
let api_dir = root.join("api");
fs::create_dir(&api_dir)?;
fs::write(api_dir.join("mod.rs"), "")?;
fs::write(api_dir.join("get.rs"), "")?;
let node = scan_routes(root)?;
assert_eq!(node.children.len(), 1);
assert_eq!(node.children[0].route_segment, "/api");
Ok(())
}
}