openapi_from_source/
scanner.rs1use anyhow::Result;
2use log::warn;
3use std::path::PathBuf;
4use walkdir::WalkDir;
5
6pub struct FileScanner {
23 root_path: PathBuf,
24}
25
26pub struct ScanResult {
30 pub rust_files: Vec<PathBuf>,
32 pub warnings: Vec<String>,
34}
35
36impl FileScanner {
37 pub fn new(root_path: PathBuf) -> Self {
43 Self { root_path }
44 }
45
46 pub fn scan(&self) -> Result<ScanResult> {
64 let mut rust_files = Vec::new();
65 let mut warnings = Vec::new();
66
67 for entry in WalkDir::new(&self.root_path)
68 .into_iter()
69 .filter_entry(|e| {
70 if e.path() == self.root_path {
72 return true;
73 }
74
75 let file_name = e.file_name().to_string_lossy();
77 let is_hidden = file_name.starts_with('.');
78 let is_target = file_name == "target";
79
80 !is_hidden && !is_target
81 })
82 {
83 match entry {
84 Ok(entry) => {
85 let path = entry.path();
86
87 if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("rs") {
89 rust_files.push(path.to_path_buf());
90 }
91 }
92 Err(e) => {
93 let warning = format!("Failed to access path: {}", e);
95 warn!("{}", warning);
96 warnings.push(warning);
97 }
98 }
99 }
100
101 Ok(ScanResult {
102 rust_files,
103 warnings,
104 })
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use std::fs;
112 use tempfile::TempDir;
113
114 #[test]
115 fn test_scan_normal_directory() {
116 let temp_dir = TempDir::new().unwrap();
118 let root = temp_dir.path();
119
120 fs::write(root.join("main.rs"), "fn main() {}").unwrap();
122 fs::write(root.join("lib.rs"), "pub fn test() {}").unwrap();
123 fs::write(root.join("readme.md"), "# README").unwrap();
124
125 let scanner = FileScanner::new(root.to_path_buf());
127 let result = scanner.scan().unwrap();
128
129 assert_eq!(result.rust_files.len(), 2);
131 assert!(result.warnings.is_empty());
132
133 let file_names: Vec<String> = result
134 .rust_files
135 .iter()
136 .map(|p| p.file_name().unwrap().to_string_lossy().to_string())
137 .collect();
138
139 assert!(file_names.contains(&"main.rs".to_string()));
140 assert!(file_names.contains(&"lib.rs".to_string()));
141 }
142
143 #[test]
144 fn test_scan_empty_directory() {
145 let temp_dir = TempDir::new().unwrap();
147 let root = temp_dir.path();
148
149 let scanner = FileScanner::new(root.to_path_buf());
151 let result = scanner.scan().unwrap();
152
153 assert_eq!(result.rust_files.len(), 0);
155 assert!(result.warnings.is_empty());
156 }
157
158 #[test]
159 fn test_scan_nested_directories() {
160 let temp_dir = TempDir::new().unwrap();
162 let root = temp_dir.path();
163
164 fs::create_dir(root.join("src")).unwrap();
166 fs::create_dir(root.join("src/models")).unwrap();
167 fs::create_dir(root.join("tests")).unwrap();
168
169 fs::write(root.join("main.rs"), "fn main() {}").unwrap();
171 fs::write(root.join("src/lib.rs"), "pub fn test() {}").unwrap();
172 fs::write(root.join("src/models/user.rs"), "struct User {}").unwrap();
173 fs::write(root.join("tests/integration.rs"), "#[test] fn test() {}").unwrap();
174
175 let scanner = FileScanner::new(root.to_path_buf());
177 let result = scanner.scan().unwrap();
178
179 assert_eq!(result.rust_files.len(), 4);
181 assert!(result.warnings.is_empty());
182 }
183
184 #[test]
185 fn test_scan_skips_target_directory() {
186 let temp_dir = TempDir::new().unwrap();
188 let root = temp_dir.path();
189
190 fs::create_dir(root.join("target")).unwrap();
192 fs::write(root.join("target/build.rs"), "fn main() {}").unwrap();
193
194 fs::write(root.join("main.rs"), "fn main() {}").unwrap();
196
197 let scanner = FileScanner::new(root.to_path_buf());
199 let result = scanner.scan().unwrap();
200
201 assert_eq!(result.rust_files.len(), 1);
203 assert!(result.warnings.is_empty());
204 assert_eq!(
205 result.rust_files[0].file_name().unwrap().to_string_lossy(),
206 "main.rs"
207 );
208 }
209
210 #[test]
211 fn test_scan_skips_hidden_directories() {
212 let temp_dir = TempDir::new().unwrap();
214 let root = temp_dir.path();
215
216 fs::create_dir(root.join(".git")).unwrap();
218 fs::write(root.join(".git/config.rs"), "// config").unwrap();
219
220 fs::write(root.join("main.rs"), "fn main() {}").unwrap();
222
223 let scanner = FileScanner::new(root.to_path_buf());
225 let result = scanner.scan().unwrap();
226
227 assert_eq!(result.rust_files.len(), 1);
229 assert!(result.warnings.is_empty());
230 assert_eq!(
231 result.rust_files[0].file_name().unwrap().to_string_lossy(),
232 "main.rs"
233 );
234 }
235
236 #[test]
237 fn test_scan_filters_non_rust_files() {
238 let temp_dir = TempDir::new().unwrap();
240 let root = temp_dir.path();
241
242 fs::write(root.join("main.rs"), "fn main() {}").unwrap();
244 fs::write(root.join("readme.md"), "# README").unwrap();
245 fs::write(root.join("config.toml"), "[package]").unwrap();
246 fs::write(root.join("script.sh"), "#!/bin/bash").unwrap();
247
248 let scanner = FileScanner::new(root.to_path_buf());
250 let result = scanner.scan().unwrap();
251
252 assert_eq!(result.rust_files.len(), 1);
254 assert!(result.warnings.is_empty());
255 assert_eq!(
256 result.rust_files[0].file_name().unwrap().to_string_lossy(),
257 "main.rs"
258 );
259 }
260}