openapi_from_source/
detector.rs1use crate::cli::Framework;
2use crate::parser::ParsedFile;
3use log::debug;
4use std::collections::HashSet;
5use syn::{Item, UseTree};
6
7pub struct FrameworkDetector;
17
18pub struct DetectionResult {
22 pub frameworks: Vec<Framework>,
24}
25
26impl FrameworkDetector {
27 pub fn detect(parsed_files: &[ParsedFile]) -> DetectionResult {
53 debug!("Detecting frameworks in {} files", parsed_files.len());
54
55 let mut detected_frameworks = HashSet::new();
56
57 for parsed_file in parsed_files {
58 for item in &parsed_file.syntax_tree.items {
60 if let Item::Use(use_item) = item {
61 Self::check_use_tree(&use_item.tree, &mut detected_frameworks);
62 }
63 }
64 }
65
66 let frameworks: Vec<Framework> = detected_frameworks.into_iter().collect();
67 debug!("Detected frameworks: {:?}", frameworks);
68
69 DetectionResult { frameworks }
70 }
71
72 fn check_use_tree(tree: &UseTree, detected: &mut HashSet<Framework>) {
74 match tree {
75 UseTree::Path(path) => {
76 let ident = path.ident.to_string();
77
78 if ident == "axum" {
80 detected.insert(Framework::Axum);
81 }
82
83 if ident == "actix_web" {
85 detected.insert(Framework::ActixWeb);
86 }
87
88 Self::check_use_tree(&path.tree, detected);
90 }
91 UseTree::Group(group) => {
92 for item in &group.items {
94 Self::check_use_tree(item, detected);
95 }
96 }
97 UseTree::Rename(rename) => {
98 let ident = rename.ident.to_string();
100 if ident == "axum" {
101 detected.insert(Framework::Axum);
102 }
103 if ident == "actix_web" {
104 detected.insert(Framework::ActixWeb);
105 }
106 }
107 UseTree::Name(name) => {
108 let ident = name.ident.to_string();
110 if ident == "axum" {
111 detected.insert(Framework::Axum);
112 }
113 if ident == "actix_web" {
114 detected.insert(Framework::ActixWeb);
115 }
116 }
117 UseTree::Glob(_) => {
118 }
120 }
121 }
122}
123
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::parser::AstParser;
129 use std::fs;
130 use std::io::Write;
131 use tempfile::TempDir;
132
133 fn create_temp_file(dir: &TempDir, name: &str, content: &str) -> std::path::PathBuf {
135 let file_path = dir.path().join(name);
136 let mut file = fs::File::create(&file_path).unwrap();
137 file.write_all(content.as_bytes()).unwrap();
138 file_path
139 }
140
141 fn parse_test_file(dir: &TempDir, name: &str, content: &str) -> ParsedFile {
143 let file_path = create_temp_file(dir, name, content);
144 AstParser::parse_file(&file_path).unwrap()
145 }
146
147 #[test]
148 fn test_detect_axum_framework() {
149 let temp_dir = TempDir::new().unwrap();
150
151 let axum_code = r#"
152 use axum::{Router, routing::get};
153 use axum::extract::Path;
154
155 pub async fn hello() -> &'static str {
156 "Hello, World!"
157 }
158
159 pub fn app() -> Router {
160 Router::new().route("/", get(hello))
161 }
162 "#;
163
164 let parsed = parse_test_file(&temp_dir, "axum.rs", axum_code);
165 let result = FrameworkDetector::detect(&[parsed]);
166
167 assert_eq!(result.frameworks.len(), 1);
168 assert!(result.frameworks.contains(&Framework::Axum));
169 }
170
171 #[test]
172 fn test_detect_actix_web_framework() {
173 let temp_dir = TempDir::new().unwrap();
174
175 let actix_code = r#"
176 use actix_web::{web, App, HttpResponse, HttpServer};
177 use actix_web::middleware::Logger;
178
179 #[actix_web::get("/")]
180 async fn hello() -> HttpResponse {
181 HttpResponse::Ok().body("Hello, World!")
182 }
183
184 pub fn main() {
185 HttpServer::new(|| {
186 App::new().service(hello)
187 })
188 }
189 "#;
190
191 let parsed = parse_test_file(&temp_dir, "actix.rs", actix_code);
192 let result = FrameworkDetector::detect(&[parsed]);
193
194 assert_eq!(result.frameworks.len(), 1);
195 assert!(result.frameworks.contains(&Framework::ActixWeb));
196 }
197
198 #[test]
199 fn test_detect_mixed_frameworks() {
200 let temp_dir = TempDir::new().unwrap();
201
202 let axum_code = r#"
203 use axum::Router;
204
205 pub fn axum_app() -> Router {
206 Router::new()
207 }
208 "#;
209
210 let actix_code = r#"
211 use actix_web::{web, App};
212
213 pub fn actix_app() -> App {
214 App::new()
215 }
216 "#;
217
218 let parsed_axum = parse_test_file(&temp_dir, "axum.rs", axum_code);
219 let parsed_actix = parse_test_file(&temp_dir, "actix.rs", actix_code);
220
221 let result = FrameworkDetector::detect(&[parsed_axum, parsed_actix]);
222
223 assert_eq!(result.frameworks.len(), 2);
224 assert!(result.frameworks.contains(&Framework::Axum));
225 assert!(result.frameworks.contains(&Framework::ActixWeb));
226 }
227
228 #[test]
229 fn test_detect_no_framework() {
230 let temp_dir = TempDir::new().unwrap();
231
232 let plain_code = r#"
233 use std::collections::HashMap;
234 use serde::{Serialize, Deserialize};
235
236 #[derive(Serialize, Deserialize)]
237 pub struct User {
238 pub id: u32,
239 pub name: String,
240 }
241
242 pub fn process_user(user: User) -> User {
243 user
244 }
245 "#;
246
247 let parsed = parse_test_file(&temp_dir, "plain.rs", plain_code);
248 let result = FrameworkDetector::detect(&[parsed]);
249
250 assert_eq!(result.frameworks.len(), 0);
251 }
252
253 #[test]
254 fn test_detect_with_grouped_imports() {
255 let temp_dir = TempDir::new().unwrap();
256
257 let grouped_code = r#"
258 use axum::{
259 Router,
260 routing::{get, post},
261 extract::{Path, Query},
262 };
263
264 pub fn app() -> Router {
265 Router::new()
266 }
267 "#;
268
269 let parsed = parse_test_file(&temp_dir, "grouped.rs", grouped_code);
270 let result = FrameworkDetector::detect(&[parsed]);
271
272 assert_eq!(result.frameworks.len(), 1);
273 assert!(result.frameworks.contains(&Framework::Axum));
274 }
275
276 #[test]
277 fn test_detect_with_renamed_import() {
278 let temp_dir = TempDir::new().unwrap();
279
280 let renamed_code = r#"
281 use actix_web as actix;
282 use actix::web;
283
284 pub fn handler() {}
285 "#;
286
287 let parsed = parse_test_file(&temp_dir, "renamed.rs", renamed_code);
288 let result = FrameworkDetector::detect(&[parsed]);
289
290 assert_eq!(result.frameworks.len(), 1);
291 assert!(result.frameworks.contains(&Framework::ActixWeb));
292 }
293
294 #[test]
295 fn test_detect_multiple_files_same_framework() {
296 let temp_dir = TempDir::new().unwrap();
297
298 let code1 = r#"
299 use axum::Router;
300 "#;
301
302 let code2 = r#"
303 use axum::routing::get;
304 "#;
305
306 let code3 = r#"
307 use axum::extract::Path;
308 "#;
309
310 let parsed1 = parse_test_file(&temp_dir, "file1.rs", code1);
311 let parsed2 = parse_test_file(&temp_dir, "file2.rs", code2);
312 let parsed3 = parse_test_file(&temp_dir, "file3.rs", code3);
313
314 let result = FrameworkDetector::detect(&[parsed1, parsed2, parsed3]);
315
316 assert_eq!(result.frameworks.len(), 1);
318 assert!(result.frameworks.contains(&Framework::Axum));
319 }
320
321 #[test]
322 fn test_detect_empty_file_list() {
323 let result = FrameworkDetector::detect(&[]);
324
325 assert_eq!(result.frameworks.len(), 0);
326 }
327
328 #[test]
329 fn test_detect_with_nested_use_paths() {
330 let temp_dir = TempDir::new().unwrap();
331
332 let nested_code = r#"
333 use actix_web::web::Json;
334 use actix_web::middleware::Logger;
335
336 pub fn handler() {}
337 "#;
338
339 let parsed = parse_test_file(&temp_dir, "nested.rs", nested_code);
340 let result = FrameworkDetector::detect(&[parsed]);
341
342 assert_eq!(result.frameworks.len(), 1);
343 assert!(result.frameworks.contains(&Framework::ActixWeb));
344 }
345
346 #[test]
347 fn test_detect_with_glob_imports() {
348 let temp_dir = TempDir::new().unwrap();
349
350 let glob_code = r#"
351 use axum::*;
352 use axum::routing::*;
353
354 pub fn app() {}
355 "#;
356
357 let parsed = parse_test_file(&temp_dir, "glob.rs", glob_code);
358 let result = FrameworkDetector::detect(&[parsed]);
359
360 assert_eq!(result.frameworks.len(), 1);
361 assert!(result.frameworks.contains(&Framework::Axum));
362 }
363}