Skip to main content

spring_lsp/
route.rs

1//! 路由导航模块
2//!
3//! 提供路由识别、索引和导航功能
4
5use crate::macro_analyzer::HttpMethod;
6use lsp_types::Location;
7use std::collections::HashMap;
8
9/// 路由导航器
10///
11/// 负责识别、索引和导航项目中的所有路由
12#[derive(Debug, Clone)]
13pub struct RouteNavigator {
14    /// 路由索引
15    pub index: RouteIndex,
16}
17
18impl RouteNavigator {
19    /// 创建新的路由导航器
20    pub fn new() -> Self {
21        Self {
22            index: RouteIndex::new(),
23        }
24    }
25
26    /// 构建路由索引
27    ///
28    /// 从 Rust 文档列表中提取所有路由信息,构建路由索引
29    ///
30    /// # Arguments
31    ///
32    /// * `documents` - Rust 文档列表,包含已解析的宏信息
33    ///
34    /// # Requirements
35    ///
36    /// - 8.1: 识别所有路由宏标注的函数
37    /// - 8.2: 解析路径参数
38    /// - 8.3: 处理多方法路由
39    /// - 8.4: 解析路由前缀
40    pub fn build_index(&mut self, documents: &[crate::macro_analyzer::RustDocument]) {
41        // 清空现有索引
42        self.index.routes.clear();
43        self.index.path_map.clear();
44
45        // 遍历所有文档
46        for doc in documents {
47            // 遍历文档中的所有宏
48            for spring_macro in &doc.macros {
49                // 只处理路由宏
50                if let crate::macro_analyzer::SpringMacro::Route(route_macro) = spring_macro {
51                    // 为每个 HTTP 方法创建独立的路由条目(需求 8.3)
52                    for method in &route_macro.methods {
53                        // 解析路径参数(需求 8.2)
54                        let path_params = self.parse_path_parameters(&route_macro.path);
55
56                        // 提取处理器函数参数
57                        // 注意:当前实现中,我们从 route_macro 中没有函数参数信息
58                        // 这需要在 MacroAnalyzer 中扩展以提取函数签名
59                        let parameters = path_params
60                            .iter()
61                            .map(|param_name| Parameter {
62                                name: param_name.clone(),
63                                type_name: "Unknown".to_string(), // 需要从函数签名中提取
64                            })
65                            .collect();
66
67                        // 创建路由信息
68                        let route_info = RouteInfo {
69                            path: route_macro.path.clone(),
70                            methods: vec![method.clone()],
71                            handler: HandlerInfo {
72                                function_name: route_macro.handler_name.clone(),
73                                parameters,
74                            },
75                            location: Location {
76                                uri: doc.uri.clone(),
77                                range: route_macro.range,
78                            },
79                        };
80
81                        // 添加到索引
82                        let route_index = self.index.routes.len();
83                        self.index.routes.push(route_info);
84
85                        // 更新路径映射
86                        self.index
87                            .path_map
88                            .entry(route_macro.path.clone())
89                            .or_default()
90                            .push(route_index);
91                    }
92                }
93            }
94        }
95    }
96
97    /// 查找路由
98    ///
99    /// 支持模糊匹配和正则表达式搜索路由
100    ///
101    /// # Arguments
102    ///
103    /// * `pattern` - 搜索模式,可以是:
104    ///   - 普通字符串:进行模糊匹配(路径包含该字符串)
105    ///   - 正则表达式:以 "regex:" 开头,如 "regex:^/api/.*"
106    ///
107    /// # Returns
108    ///
109    /// 返回匹配的路由信息列表
110    ///
111    /// # Requirements
112    ///
113    /// - 9.4: 支持模糊匹配和正则表达式搜索
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use spring_lsp::route::RouteNavigator;
119    ///
120    /// let navigator = RouteNavigator::new();
121    /// // 模糊匹配
122    /// let routes = navigator.find_routes("users");
123    /// // 正则表达式匹配
124    /// let routes = navigator.find_routes("regex:^/api/v[0-9]+/.*");
125    /// ```
126    pub fn find_routes(&self, pattern: &str) -> Vec<&RouteInfo> {
127        if pattern.is_empty() {
128            return Vec::new();
129        }
130
131        // 检查是否是正则表达式模式
132        if let Some(regex_pattern) = pattern.strip_prefix("regex:") {
133            // 使用正则表达式匹配
134            if let Ok(re) = regex::Regex::new(regex_pattern) {
135                self.index
136                    .routes
137                    .iter()
138                    .filter(|route| re.is_match(&route.path))
139                    .collect()
140            } else {
141                // 正则表达式无效,返回空列表
142                Vec::new()
143            }
144        } else {
145            // 使用模糊匹配(路径包含搜索字符串)
146            self.index
147                .routes
148                .iter()
149                .filter(|route| route.path.contains(pattern))
150                .collect()
151        }
152    }
153
154    /// 获取所有路由
155    ///
156    /// 返回项目中所有已识别的路由
157    ///
158    /// # Returns
159    ///
160    /// 返回所有路由信息的引用
161    ///
162    /// # Requirements
163    ///
164    /// - 9.1: 返回项目中所有路由的列表
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use spring_lsp::route::RouteNavigator;
170    ///
171    /// let navigator = RouteNavigator::new();
172    /// let all_routes = navigator.get_all_routes();
173    /// println!("Total routes: {}", all_routes.len());
174    /// ```
175    pub fn get_all_routes(&self) -> &[RouteInfo] {
176        &self.index.routes
177    }
178
179    /// 根据处理器函数名查找路由
180    ///
181    /// 实现处理器路由反查功能,根据处理器函数名查找对应的所有路由
182    ///
183    /// # Arguments
184    ///
185    /// * `handler_name` - 处理器函数名称
186    ///
187    /// # Returns
188    ///
189    /// 返回该处理器对应的所有路由信息
190    ///
191    /// # Requirements
192    ///
193    /// - 9.3: 实现处理器路由反查
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use spring_lsp::route::RouteNavigator;
199    ///
200    /// let navigator = RouteNavigator::new();
201    /// let routes = navigator.find_routes_by_handler("get_user");
202    /// for route in routes {
203    ///     println!("Handler 'get_user' handles: {:?} {}", route.methods[0], route.path);
204    /// }
205    /// ```
206    pub fn find_routes_by_handler(&self, handler_name: &str) -> Vec<&RouteInfo> {
207        self.index
208            .routes
209            .iter()
210            .filter(|route| route.handler.function_name == handler_name)
211            .collect()
212    }
213
214    /// 验证路由
215    ///
216    /// 验证所有路由的正确性,包括:
217    /// - 路径字符验证
218    /// - 路径参数语法验证
219    /// - 路径参数类型匹配验证
220    /// - RESTful 风格检查
221    ///
222    /// # Returns
223    ///
224    /// 返回诊断信息列表
225    ///
226    /// # Requirements
227    ///
228    /// - 10.1: 路径字符验证
229    /// - 10.2: 路径参数语法验证
230    /// - 10.3: 路径参数类型匹配验证
231    /// - 10.5: RESTful 风格检查
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// use spring_lsp::route::RouteNavigator;
237    ///
238    /// let navigator = RouteNavigator::new();
239    /// let diagnostics = navigator.validate_routes();
240    /// for diagnostic in diagnostics {
241    ///     println!("Validation error: {}", diagnostic.message);
242    /// }
243    /// ```
244    pub fn validate_routes(&self) -> Vec<lsp_types::Diagnostic> {
245        let mut diagnostics = Vec::new();
246
247        for route in &self.index.routes {
248            // 验证路径字符(需求 10.1)
249            diagnostics.extend(self.validate_path_characters(&route.path, &route.location));
250
251            // 验证路径参数语法(需求 10.2)
252            diagnostics.extend(self.validate_path_parameter_syntax(&route.path, &route.location));
253
254            // 验证路径参数类型匹配(需求 10.3)
255            diagnostics.extend(self.validate_path_parameter_types(route));
256
257            // RESTful 风格检查(需求 10.5)
258            diagnostics.extend(self.validate_restful_style(route));
259        }
260
261        diagnostics
262    }
263
264    /// 检测路由冲突
265    ///
266    /// 检测具有相同路径和 HTTP 方法的路由冲突
267    ///
268    /// # Returns
269    ///
270    /// 返回路由冲突列表
271    ///
272    /// # Requirements
273    ///
274    /// - 9.5: 检测路由冲突
275    /// - 10.4: 路由路径冲突检测
276    ///
277    /// # Examples
278    ///
279    /// ```
280    /// use spring_lsp::route::RouteNavigator;
281    ///
282    /// let navigator = RouteNavigator::new();
283    /// let conflicts = navigator.detect_conflicts();
284    /// for conflict in conflicts {
285    ///     println!("Route conflict detected between routes {} and {}",
286    ///              conflict.index1, conflict.index2);
287    /// }
288    /// ```
289    pub fn detect_conflicts(&self) -> Vec<RouteConflict> {
290        let mut conflicts = Vec::new();
291
292        // 遍历所有路由对,检测冲突
293        for i in 0..self.index.routes.len() {
294            for j in (i + 1)..self.index.routes.len() {
295                let route1 = &self.index.routes[i];
296                let route2 = &self.index.routes[j];
297
298                // 检查路径是否相同
299                if route1.path == route2.path {
300                    // 检查是否有相同的 HTTP 方法
301                    for method1 in &route1.methods {
302                        if route2.methods.contains(method1) {
303                            conflicts.push(RouteConflict {
304                                index1: i,
305                                index2: j,
306                                path: route1.path.clone(),
307                                method: method1.clone(),
308                                location1: route1.location.clone(),
309                                location2: route2.location.clone(),
310                            });
311                        }
312                    }
313                }
314            }
315        }
316
317        conflicts
318    }
319
320    /// 验证路径字符
321    ///
322    /// 检查路径是否包含 URL 规范不允许的字符
323    ///
324    /// # Requirements
325    ///
326    /// - 10.1: 路径字符验证
327    fn validate_path_characters(
328        &self,
329        path: &str,
330        location: &Location,
331    ) -> Vec<lsp_types::Diagnostic> {
332        let mut diagnostics = Vec::new();
333
334        // URL 路径允许的字符:字母、数字、-_.~:/?#[]@!$&'()*+,;=
335        // 以及路径参数的 {}
336        for (i, ch) in path.chars().enumerate() {
337            if !ch.is_ascii_alphanumeric()
338                && !matches!(
339                    ch,
340                    '-' | '_'
341                        | '.'
342                        | '~'
343                        | ':'
344                        | '/'
345                        | '?'
346                        | '#'
347                        | '['
348                        | ']'
349                        | '@'
350                        | '!'
351                        | '$'
352                        | '&'
353                        | '\''
354                        | '('
355                        | ')'
356                        | '*'
357                        | '+'
358                        | ','
359                        | ';'
360                        | '='
361                        | '{'
362                        | '}'
363                )
364            {
365                diagnostics.push(lsp_types::Diagnostic {
366                    range: location.range,
367                    severity: Some(lsp_types::DiagnosticSeverity::ERROR),
368                    code: Some(lsp_types::NumberOrString::String("invalid-path-char".to_string())),
369                    message: format!(
370                        "路径包含无效字符 '{}' (位置 {})。URL 路径只能包含字母、数字和特定的特殊字符。",
371                        ch, i
372                    ),
373                    source: Some("spring-lsp".to_string()),
374                    ..Default::default()
375                });
376            }
377        }
378
379        diagnostics
380    }
381
382    /// 验证路径参数语法
383    ///
384    /// 检查路径参数是否符合 {param} 格式
385    ///
386    /// # Requirements
387    ///
388    /// - 10.2: 路径参数语法验证
389    fn validate_path_parameter_syntax(
390        &self,
391        path: &str,
392        location: &Location,
393    ) -> Vec<lsp_types::Diagnostic> {
394        let mut diagnostics = Vec::new();
395        let mut brace_count = 0;
396        let mut last_open_brace = None;
397
398        for (i, ch) in path.chars().enumerate() {
399            match ch {
400                '{' => {
401                    if brace_count > 0 {
402                        // 嵌套的大括号
403                        diagnostics.push(lsp_types::Diagnostic {
404                            range: location.range,
405                            severity: Some(lsp_types::DiagnosticSeverity::ERROR),
406                            code: Some(lsp_types::NumberOrString::String(
407                                "nested-path-param".to_string(),
408                            )),
409                            message: format!("路径参数不能嵌套 (位置 {})。正确格式:{{param}}", i),
410                            source: Some("spring-lsp".to_string()),
411                            ..Default::default()
412                        });
413                    }
414                    brace_count += 1;
415                    last_open_brace = Some(i);
416                }
417                '}' => {
418                    if brace_count == 0 {
419                        // 没有匹配的开括号
420                        diagnostics.push(lsp_types::Diagnostic {
421                            range: location.range,
422                            severity: Some(lsp_types::DiagnosticSeverity::ERROR),
423                            code: Some(lsp_types::NumberOrString::String(
424                                "unmatched-closing-brace".to_string(),
425                            )),
426                            message: format!(
427                                "路径参数缺少开括号 '{{' (位置 {})。正确格式:{{param}}",
428                                i
429                            ),
430                            source: Some("spring-lsp".to_string()),
431                            ..Default::default()
432                        });
433                    } else {
434                        // 检查参数名是否为空
435                        if let Some(open_pos) = last_open_brace {
436                            if i == open_pos + 1 {
437                                diagnostics.push(lsp_types::Diagnostic {
438                                    range: location.range,
439                                    severity: Some(lsp_types::DiagnosticSeverity::ERROR),
440                                    code: Some(lsp_types::NumberOrString::String(
441                                        "empty-path-param".to_string(),
442                                    )),
443                                    message: format!(
444                                        "路径参数名称不能为空 (位置 {})。正确格式:{{param}}",
445                                        open_pos
446                                    ),
447                                    source: Some("spring-lsp".to_string()),
448                                    ..Default::default()
449                                });
450                            }
451                        }
452                        brace_count -= 1;
453                    }
454                }
455                _ => {}
456            }
457        }
458
459        // 检查是否有未闭合的括号
460        if brace_count > 0 {
461            if let Some(open_pos) = last_open_brace {
462                diagnostics.push(lsp_types::Diagnostic {
463                    range: location.range,
464                    severity: Some(lsp_types::DiagnosticSeverity::ERROR),
465                    code: Some(lsp_types::NumberOrString::String(
466                        "unclosed-path-param".to_string(),
467                    )),
468                    message: format!(
469                        "路径参数缺少闭括号 '}}' (位置 {})。正确格式:{{param}}",
470                        open_pos
471                    ),
472                    source: Some("spring-lsp".to_string()),
473                    ..Default::default()
474                });
475            }
476        }
477
478        diagnostics
479    }
480
481    /// 验证路径参数类型匹配
482    ///
483    /// 检查路径参数类型与处理器函数参数类型是否兼容
484    ///
485    /// # Requirements
486    ///
487    /// - 10.3: 路径参数类型匹配验证
488    fn validate_path_parameter_types(&self, route: &RouteInfo) -> Vec<lsp_types::Diagnostic> {
489        let mut diagnostics = Vec::new();
490
491        // 提取路径参数
492        let path_params = self.parse_path_parameters(&route.path);
493
494        // 检查每个路径参数是否在处理器参数中有对应的类型
495        for path_param in &path_params {
496            // 查找处理器参数中是否有匹配的参数
497            let found = route
498                .handler
499                .parameters
500                .iter()
501                .any(|p| p.name == *path_param);
502
503            if !found {
504                diagnostics.push(lsp_types::Diagnostic {
505                    range: route.location.range,
506                    severity: Some(lsp_types::DiagnosticSeverity::WARNING),
507                    code: Some(lsp_types::NumberOrString::String(
508                        "missing-path-param".to_string(),
509                    )),
510                    message: format!(
511                        "路径参数 '{}' 在处理器函数 '{}' 的参数列表中未找到。\
512                         请确保函数参数中包含 Path<T> 类型的参数来接收此路径参数。",
513                        path_param, route.handler.function_name
514                    ),
515                    source: Some("spring-lsp".to_string()),
516                    ..Default::default()
517                });
518            }
519        }
520
521        // 检查类型兼容性(如果类型信息可用)
522        for param in &route.handler.parameters {
523            if path_params.contains(&param.name) {
524                // 检查参数类型是否是 Path<T> 或兼容类型
525                if !param.type_name.contains("Path")
526                    && !param.type_name.contains("Unknown")
527                    && !param.type_name.is_empty()
528                {
529                    diagnostics.push(lsp_types::Diagnostic {
530                        range: route.location.range,
531                        severity: Some(lsp_types::DiagnosticSeverity::WARNING),
532                        code: Some(lsp_types::NumberOrString::String(
533                            "incompatible-path-param-type".to_string(),
534                        )),
535                        message: format!(
536                            "路径参数 '{}' 的类型 '{}' 可能不兼容。\
537                             路径参数通常应该使用 Path<T> 类型。",
538                            param.name, param.type_name
539                        ),
540                        source: Some("spring-lsp".to_string()),
541                        ..Default::default()
542                    });
543                }
544            }
545        }
546
547        diagnostics
548    }
549
550    /// 验证 RESTful 风格
551    ///
552    /// 检查路由路径是否符合 RESTful 命名规范
553    ///
554    /// # Requirements
555    ///
556    /// - 10.5: RESTful 风格检查
557    fn validate_restful_style(&self, route: &RouteInfo) -> Vec<lsp_types::Diagnostic> {
558        let mut diagnostics = Vec::new();
559
560        // 分割路径为段
561        let segments: Vec<&str> = route.path.split('/').filter(|s| !s.is_empty()).collect();
562
563        for segment in &segments {
564            // 跳过路径参数
565            if segment.starts_with('{') && segment.ends_with('}') {
566                continue;
567            }
568
569            // 检查是否使用了动词(RESTful 应该使用名词)
570            let verbs = [
571                "get", "post", "put", "delete", "patch", "create", "update", "remove", "add",
572                "list", "fetch", "retrieve", "save", "destroy",
573            ];
574
575            let segment_lower = segment.to_lowercase();
576            for verb in &verbs {
577                // 只匹配完整的单词或以动词开头的驼峰命名/连字符命名
578                // 例如:匹配 "get", "getUsers", "get-users"
579                // 但不匹配 "posts" (虽然包含 "post")
580                let is_verb_match = if segment_lower == *verb {
581                    // 完全匹配
582                    true
583                } else if segment_lower.starts_with(verb) && segment_lower.len() > verb.len() {
584                    // 检查动词后面的字符
585                    let next_char = segment.chars().nth(verb.len()).unwrap();
586                    // 驼峰命名(getUsers)或连字符/下划线命名(get-users, get_users)
587                    next_char.is_uppercase() || next_char == '-' || next_char == '_'
588                } else {
589                    false
590                };
591
592                if is_verb_match {
593                    diagnostics.push(lsp_types::Diagnostic {
594                        range: route.location.range,
595                        severity: Some(lsp_types::DiagnosticSeverity::INFORMATION),
596                        code: Some(lsp_types::NumberOrString::String(
597                            "restful-style-verb".to_string(),
598                        )),
599                        message: format!(
600                            "路径段 '{}' 包含动词 '{}'。RESTful API 建议使用名词而非动词,\
601                             通过 HTTP 方法(GET、POST、PUT、DELETE)来表示操作。\
602                             例如:使用 'GET /users' 而非 'GET /getUsers'。",
603                            segment, verb
604                        ),
605                        source: Some("spring-lsp".to_string()),
606                        ..Default::default()
607                    });
608                    break;
609                }
610            }
611
612            // 检查是否使用了驼峰命名(RESTful 建议使用小写和连字符)
613            if segment.chars().any(|c| c.is_uppercase()) {
614                diagnostics.push(lsp_types::Diagnostic {
615                    range: route.location.range,
616                    severity: Some(lsp_types::DiagnosticSeverity::INFORMATION),
617                    code: Some(lsp_types::NumberOrString::String(
618                        "restful-style-case".to_string(),
619                    )),
620                    message: format!(
621                        "路径段 '{}' 使用了大写字母。RESTful API 建议使用小写字母和连字符。\
622                         例如:使用 '/user-profiles' 而非 '/userProfiles'。",
623                        segment
624                    ),
625                    source: Some("spring-lsp".to_string()),
626                    ..Default::default()
627                });
628            }
629        }
630
631        diagnostics
632    }
633
634    /// 解析路径参数
635    ///
636    /// 从路由路径中提取所有参数名称(如 "/users/{id}" 中的 "id")
637    ///
638    /// # Arguments
639    ///
640    /// * `path` - 路由路径
641    ///
642    /// # Returns
643    ///
644    /// 返回参数名称列表
645    ///
646    /// # Requirements
647    ///
648    /// - 8.2: 正确解析路径参数
649    fn parse_path_parameters(&self, path: &str) -> Vec<String> {
650        let mut parameters = Vec::new();
651        let mut in_param = false;
652        let mut param_start = 0;
653
654        for (i, ch) in path.char_indices() {
655            match ch {
656                '{' => {
657                    in_param = true;
658                    param_start = i + 1;
659                }
660                '}' => {
661                    if in_param {
662                        let param_name = &path[param_start..i];
663                        if !param_name.is_empty() {
664                            parameters.push(param_name.to_string());
665                        }
666                        in_param = false;
667                    }
668                }
669                _ => {}
670            }
671        }
672
673        parameters
674    }
675}
676
677impl Default for RouteNavigator {
678    fn default() -> Self {
679        Self::new()
680    }
681}
682
683/// 路由索引
684///
685/// 存储所有路由信息,提供快速查找功能
686#[derive(Debug, Clone)]
687pub struct RouteIndex {
688    /// 所有路由列表
689    pub routes: Vec<RouteInfo>,
690    /// 路径到路由索引的映射(用于快速查找)
691    /// Key: 路由路径, Value: routes 数组中的索引列表
692    pub path_map: HashMap<String, Vec<usize>>,
693}
694
695impl RouteIndex {
696    /// 创建新的路由索引
697    pub fn new() -> Self {
698        Self {
699            routes: Vec::new(),
700            path_map: HashMap::new(),
701        }
702    }
703}
704
705impl Default for RouteIndex {
706    fn default() -> Self {
707        Self::new()
708    }
709}
710
711/// 路由信息
712///
713/// 描述单个路由的完整信息
714#[derive(Debug, Clone)]
715pub struct RouteInfo {
716    /// 路由路径(如 "/users/{id}")
717    pub path: String,
718    /// HTTP 方法列表
719    pub methods: Vec<HttpMethod>,
720    /// 处理器函数信息
721    pub handler: HandlerInfo,
722    /// 路由在源代码中的位置
723    pub location: Location,
724}
725
726/// 处理器函数信息
727///
728/// 描述路由处理器函数的详细信息
729#[derive(Debug, Clone)]
730pub struct HandlerInfo {
731    /// 函数名称
732    pub function_name: String,
733    /// 函数参数列表
734    pub parameters: Vec<Parameter>,
735}
736
737/// 函数参数信息
738///
739/// 描述处理器函数的单个参数
740#[derive(Debug, Clone)]
741pub struct Parameter {
742    /// 参数名称
743    pub name: String,
744    /// 参数类型
745    pub type_name: String,
746}
747
748/// 路由冲突信息
749///
750/// 描述两个路由之间的冲突
751#[derive(Debug, Clone)]
752pub struct RouteConflict {
753    /// 第一个路由的索引
754    pub index1: usize,
755    /// 第二个路由的索引
756    pub index2: usize,
757    /// 冲突的路径
758    pub path: String,
759    /// 冲突的 HTTP 方法
760    pub method: HttpMethod,
761    /// 第一个路由的位置
762    pub location1: Location,
763    /// 第二个路由的位置
764    pub location2: Location,
765}
766
767#[cfg(test)]
768mod tests {
769    use super::*;
770    use lsp_types::{Position, Range, Url};
771
772    #[test]
773    fn test_route_navigator_new() {
774        let navigator = RouteNavigator::new();
775        assert_eq!(navigator.index.routes.len(), 0);
776        assert_eq!(navigator.index.path_map.len(), 0);
777    }
778
779    #[test]
780    fn test_route_navigator_default() {
781        let navigator = RouteNavigator::default();
782        assert_eq!(navigator.index.routes.len(), 0);
783        assert_eq!(navigator.index.path_map.len(), 0);
784    }
785
786    #[test]
787    fn test_route_index_new() {
788        let index = RouteIndex::new();
789        assert_eq!(index.routes.len(), 0);
790        assert_eq!(index.path_map.len(), 0);
791    }
792
793    #[test]
794    fn test_route_index_default() {
795        let index = RouteIndex::default();
796        assert_eq!(index.routes.len(), 0);
797        assert_eq!(index.path_map.len(), 0);
798    }
799
800    #[test]
801    fn test_route_info_creation() {
802        let route = RouteInfo {
803            path: "/users/{id}".to_string(),
804            methods: vec![HttpMethod::Get],
805            handler: HandlerInfo {
806                function_name: "get_user".to_string(),
807                parameters: vec![Parameter {
808                    name: "id".to_string(),
809                    type_name: "i64".to_string(),
810                }],
811            },
812            location: Location {
813                uri: Url::parse("file:///test.rs").unwrap(),
814                range: Range {
815                    start: Position {
816                        line: 10,
817                        character: 0,
818                    },
819                    end: Position {
820                        line: 15,
821                        character: 0,
822                    },
823                },
824            },
825        };
826
827        assert_eq!(route.path, "/users/{id}");
828        assert_eq!(route.methods.len(), 1);
829        assert_eq!(route.methods[0], HttpMethod::Get);
830        assert_eq!(route.handler.function_name, "get_user");
831        assert_eq!(route.handler.parameters.len(), 1);
832        assert_eq!(route.handler.parameters[0].name, "id");
833        assert_eq!(route.handler.parameters[0].type_name, "i64");
834    }
835
836    #[test]
837    fn test_handler_info_creation() {
838        let handler = HandlerInfo {
839            function_name: "create_user".to_string(),
840            parameters: vec![
841                Parameter {
842                    name: "body".to_string(),
843                    type_name: "Json<CreateUserRequest>".to_string(),
844                },
845                Parameter {
846                    name: "db".to_string(),
847                    type_name: "Component<ConnectPool>".to_string(),
848                },
849            ],
850        };
851
852        assert_eq!(handler.function_name, "create_user");
853        assert_eq!(handler.parameters.len(), 2);
854        assert_eq!(handler.parameters[0].name, "body");
855        assert_eq!(handler.parameters[1].name, "db");
856    }
857
858    #[test]
859    fn test_parameter_creation() {
860        let param = Parameter {
861            name: "user_id".to_string(),
862            type_name: "Path<i64>".to_string(),
863        };
864
865        assert_eq!(param.name, "user_id");
866        assert_eq!(param.type_name, "Path<i64>");
867    }
868
869    #[test]
870    fn test_route_info_with_multiple_methods() {
871        let route = RouteInfo {
872            path: "/users".to_string(),
873            methods: vec![HttpMethod::Get, HttpMethod::Post],
874            handler: HandlerInfo {
875                function_name: "handle_users".to_string(),
876                parameters: vec![],
877            },
878            location: Location {
879                uri: Url::parse("file:///test.rs").unwrap(),
880                range: Range {
881                    start: Position {
882                        line: 0,
883                        character: 0,
884                    },
885                    end: Position {
886                        line: 0,
887                        character: 0,
888                    },
889                },
890            },
891        };
892
893        assert_eq!(route.methods.len(), 2);
894        assert!(route.methods.contains(&HttpMethod::Get));
895        assert!(route.methods.contains(&HttpMethod::Post));
896    }
897
898    #[test]
899    fn test_route_info_clone() {
900        let route = RouteInfo {
901            path: "/test".to_string(),
902            methods: vec![HttpMethod::Get],
903            handler: HandlerInfo {
904                function_name: "test_handler".to_string(),
905                parameters: vec![],
906            },
907            location: Location {
908                uri: Url::parse("file:///test.rs").unwrap(),
909                range: Range {
910                    start: Position {
911                        line: 0,
912                        character: 0,
913                    },
914                    end: Position {
915                        line: 0,
916                        character: 0,
917                    },
918                },
919            },
920        };
921
922        let cloned = route.clone();
923        assert_eq!(route.path, cloned.path);
924        assert_eq!(route.handler.function_name, cloned.handler.function_name);
925    }
926
927    #[test]
928    fn test_route_index_with_routes() {
929        let mut index = RouteIndex::new();
930
931        // 添加路由
932        let route1 = RouteInfo {
933            path: "/users".to_string(),
934            methods: vec![HttpMethod::Get],
935            handler: HandlerInfo {
936                function_name: "list_users".to_string(),
937                parameters: vec![],
938            },
939            location: Location {
940                uri: Url::parse("file:///test.rs").unwrap(),
941                range: Range {
942                    start: Position {
943                        line: 0,
944                        character: 0,
945                    },
946                    end: Position {
947                        line: 0,
948                        character: 0,
949                    },
950                },
951            },
952        };
953
954        let route2 = RouteInfo {
955            path: "/users/{id}".to_string(),
956            methods: vec![HttpMethod::Get],
957            handler: HandlerInfo {
958                function_name: "get_user".to_string(),
959                parameters: vec![],
960            },
961            location: Location {
962                uri: Url::parse("file:///test.rs").unwrap(),
963                range: Range {
964                    start: Position {
965                        line: 0,
966                        character: 0,
967                    },
968                    end: Position {
969                        line: 0,
970                        character: 0,
971                    },
972                },
973            },
974        };
975
976        index.routes.push(route1);
977        index.routes.push(route2);
978
979        // 构建路径映射
980        index.path_map.insert("/users".to_string(), vec![0]);
981        index.path_map.insert("/users/{id}".to_string(), vec![1]);
982
983        assert_eq!(index.routes.len(), 2);
984        assert_eq!(index.path_map.len(), 2);
985        assert_eq!(index.path_map.get("/users"), Some(&vec![0]));
986        assert_eq!(index.path_map.get("/users/{id}"), Some(&vec![1]));
987    }
988
989    #[test]
990    fn test_build_index_empty_documents() {
991        let mut navigator = RouteNavigator::new();
992        let documents = vec![];
993
994        navigator.build_index(&documents);
995
996        assert_eq!(navigator.index.routes.len(), 0);
997        assert_eq!(navigator.index.path_map.len(), 0);
998    }
999
1000    #[test]
1001    fn test_build_index_single_route() {
1002        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1003
1004        let mut navigator = RouteNavigator::new();
1005
1006        let route_macro = RouteMacro {
1007            path: "/users".to_string(),
1008            methods: vec![HttpMethod::Get],
1009            middlewares: vec![],
1010            handler_name: "list_users".to_string(),
1011            range: Range {
1012                start: Position {
1013                    line: 10,
1014                    character: 0,
1015                },
1016                end: Position {
1017                    line: 15,
1018                    character: 0,
1019                },
1020            },
1021        };
1022
1023        let doc = RustDocument {
1024            uri: Url::parse("file:///test.rs").unwrap(),
1025            content: String::new(),
1026            macros: vec![SpringMacro::Route(route_macro)],
1027        };
1028
1029        navigator.build_index(&[doc]);
1030
1031        assert_eq!(navigator.index.routes.len(), 1);
1032        assert_eq!(navigator.index.routes[0].path, "/users");
1033        assert_eq!(navigator.index.routes[0].methods.len(), 1);
1034        assert_eq!(navigator.index.routes[0].methods[0], HttpMethod::Get);
1035        assert_eq!(
1036            navigator.index.routes[0].handler.function_name,
1037            "list_users"
1038        );
1039        assert_eq!(navigator.index.path_map.len(), 1);
1040        assert_eq!(navigator.index.path_map.get("/users"), Some(&vec![0]));
1041    }
1042
1043    #[test]
1044    fn test_build_index_with_path_parameters() {
1045        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1046
1047        let mut navigator = RouteNavigator::new();
1048
1049        let route_macro = RouteMacro {
1050            path: "/users/{id}".to_string(),
1051            methods: vec![HttpMethod::Get],
1052            middlewares: vec![],
1053            handler_name: "get_user".to_string(),
1054            range: Range {
1055                start: Position {
1056                    line: 10,
1057                    character: 0,
1058                },
1059                end: Position {
1060                    line: 15,
1061                    character: 0,
1062                },
1063            },
1064        };
1065
1066        let doc = RustDocument {
1067            uri: Url::parse("file:///test.rs").unwrap(),
1068            content: String::new(),
1069            macros: vec![SpringMacro::Route(route_macro)],
1070        };
1071
1072        navigator.build_index(&[doc]);
1073
1074        assert_eq!(navigator.index.routes.len(), 1);
1075        assert_eq!(navigator.index.routes[0].path, "/users/{id}");
1076        assert_eq!(navigator.index.routes[0].handler.parameters.len(), 1);
1077        assert_eq!(navigator.index.routes[0].handler.parameters[0].name, "id");
1078    }
1079
1080    #[test]
1081    fn test_build_index_multiple_path_parameters() {
1082        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1083
1084        let mut navigator = RouteNavigator::new();
1085
1086        let route_macro = RouteMacro {
1087            path: "/users/{user_id}/posts/{post_id}".to_string(),
1088            methods: vec![HttpMethod::Get],
1089            middlewares: vec![],
1090            handler_name: "get_user_post".to_string(),
1091            range: Range {
1092                start: Position {
1093                    line: 10,
1094                    character: 0,
1095                },
1096                end: Position {
1097                    line: 15,
1098                    character: 0,
1099                },
1100            },
1101        };
1102
1103        let doc = RustDocument {
1104            uri: Url::parse("file:///test.rs").unwrap(),
1105            content: String::new(),
1106            macros: vec![SpringMacro::Route(route_macro)],
1107        };
1108
1109        navigator.build_index(&[doc]);
1110
1111        assert_eq!(navigator.index.routes.len(), 1);
1112        assert_eq!(navigator.index.routes[0].handler.parameters.len(), 2);
1113        assert_eq!(
1114            navigator.index.routes[0].handler.parameters[0].name,
1115            "user_id"
1116        );
1117        assert_eq!(
1118            navigator.index.routes[0].handler.parameters[1].name,
1119            "post_id"
1120        );
1121    }
1122
1123    #[test]
1124    fn test_build_index_multi_method_route() {
1125        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1126
1127        let mut navigator = RouteNavigator::new();
1128
1129        // 路由宏包含多个 HTTP 方法
1130        let route_macro = RouteMacro {
1131            path: "/users".to_string(),
1132            methods: vec![HttpMethod::Get, HttpMethod::Post],
1133            middlewares: vec![],
1134            handler_name: "handle_users".to_string(),
1135            range: Range {
1136                start: Position {
1137                    line: 10,
1138                    character: 0,
1139                },
1140                end: Position {
1141                    line: 15,
1142                    character: 0,
1143                },
1144            },
1145        };
1146
1147        let doc = RustDocument {
1148            uri: Url::parse("file:///test.rs").unwrap(),
1149            content: String::new(),
1150            macros: vec![SpringMacro::Route(route_macro)],
1151        };
1152
1153        navigator.build_index(&[doc]);
1154
1155        // 应该为每个方法创建独立的路由条目
1156        assert_eq!(navigator.index.routes.len(), 2);
1157        assert_eq!(navigator.index.routes[0].methods.len(), 1);
1158        assert_eq!(navigator.index.routes[0].methods[0], HttpMethod::Get);
1159        assert_eq!(navigator.index.routes[1].methods.len(), 1);
1160        assert_eq!(navigator.index.routes[1].methods[0], HttpMethod::Post);
1161
1162        // 两个路由应该映射到同一个路径
1163        assert_eq!(navigator.index.path_map.get("/users"), Some(&vec![0, 1]));
1164    }
1165
1166    #[test]
1167    fn test_build_index_multiple_routes() {
1168        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1169
1170        let mut navigator = RouteNavigator::new();
1171
1172        let route1 = RouteMacro {
1173            path: "/users".to_string(),
1174            methods: vec![HttpMethod::Get],
1175            middlewares: vec![],
1176            handler_name: "list_users".to_string(),
1177            range: Range {
1178                start: Position {
1179                    line: 10,
1180                    character: 0,
1181                },
1182                end: Position {
1183                    line: 15,
1184                    character: 0,
1185                },
1186            },
1187        };
1188
1189        let route2 = RouteMacro {
1190            path: "/users/{id}".to_string(),
1191            methods: vec![HttpMethod::Get],
1192            middlewares: vec![],
1193            handler_name: "get_user".to_string(),
1194            range: Range {
1195                start: Position {
1196                    line: 20,
1197                    character: 0,
1198                },
1199                end: Position {
1200                    line: 25,
1201                    character: 0,
1202                },
1203            },
1204        };
1205
1206        let route3 = RouteMacro {
1207            path: "/posts".to_string(),
1208            methods: vec![HttpMethod::Get, HttpMethod::Post],
1209            middlewares: vec![],
1210            handler_name: "handle_posts".to_string(),
1211            range: Range {
1212                start: Position {
1213                    line: 30,
1214                    character: 0,
1215                },
1216                end: Position {
1217                    line: 35,
1218                    character: 0,
1219                },
1220            },
1221        };
1222
1223        let doc = RustDocument {
1224            uri: Url::parse("file:///test.rs").unwrap(),
1225            content: String::new(),
1226            macros: vec![
1227                SpringMacro::Route(route1),
1228                SpringMacro::Route(route2),
1229                SpringMacro::Route(route3),
1230            ],
1231        };
1232
1233        navigator.build_index(&[doc]);
1234
1235        // 应该有 4 个路由条目(route3 有 2 个方法)
1236        assert_eq!(navigator.index.routes.len(), 4);
1237        assert_eq!(navigator.index.path_map.len(), 3);
1238        assert_eq!(navigator.index.path_map.get("/users"), Some(&vec![0]));
1239        assert_eq!(navigator.index.path_map.get("/users/{id}"), Some(&vec![1]));
1240        assert_eq!(navigator.index.path_map.get("/posts"), Some(&vec![2, 3]));
1241    }
1242
1243    #[test]
1244    fn test_build_index_multiple_documents() {
1245        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1246
1247        let mut navigator = RouteNavigator::new();
1248
1249        let route1 = RouteMacro {
1250            path: "/users".to_string(),
1251            methods: vec![HttpMethod::Get],
1252            middlewares: vec![],
1253            handler_name: "list_users".to_string(),
1254            range: Range {
1255                start: Position {
1256                    line: 10,
1257                    character: 0,
1258                },
1259                end: Position {
1260                    line: 15,
1261                    character: 0,
1262                },
1263            },
1264        };
1265
1266        let doc1 = RustDocument {
1267            uri: Url::parse("file:///users.rs").unwrap(),
1268            content: String::new(),
1269            macros: vec![SpringMacro::Route(route1)],
1270        };
1271
1272        let route2 = RouteMacro {
1273            path: "/posts".to_string(),
1274            methods: vec![HttpMethod::Get],
1275            middlewares: vec![],
1276            handler_name: "list_posts".to_string(),
1277            range: Range {
1278                start: Position {
1279                    line: 10,
1280                    character: 0,
1281                },
1282                end: Position {
1283                    line: 15,
1284                    character: 0,
1285                },
1286            },
1287        };
1288
1289        let doc2 = RustDocument {
1290            uri: Url::parse("file:///posts.rs").unwrap(),
1291            content: String::new(),
1292            macros: vec![SpringMacro::Route(route2)],
1293        };
1294
1295        navigator.build_index(&[doc1, doc2]);
1296
1297        assert_eq!(navigator.index.routes.len(), 2);
1298        assert_eq!(navigator.index.path_map.len(), 2);
1299    }
1300
1301    #[test]
1302    fn test_build_index_rebuild_clears_old_index() {
1303        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1304
1305        let mut navigator = RouteNavigator::new();
1306
1307        // 第一次构建
1308        let route1 = RouteMacro {
1309            path: "/users".to_string(),
1310            methods: vec![HttpMethod::Get],
1311            middlewares: vec![],
1312            handler_name: "list_users".to_string(),
1313            range: Range {
1314                start: Position {
1315                    line: 10,
1316                    character: 0,
1317                },
1318                end: Position {
1319                    line: 15,
1320                    character: 0,
1321                },
1322            },
1323        };
1324
1325        let doc1 = RustDocument {
1326            uri: Url::parse("file:///test.rs").unwrap(),
1327            content: String::new(),
1328            macros: vec![SpringMacro::Route(route1)],
1329        };
1330
1331        navigator.build_index(&[doc1]);
1332        assert_eq!(navigator.index.routes.len(), 1);
1333
1334        // 第二次构建(应该清空旧索引)
1335        let route2 = RouteMacro {
1336            path: "/posts".to_string(),
1337            methods: vec![HttpMethod::Get],
1338            middlewares: vec![],
1339            handler_name: "list_posts".to_string(),
1340            range: Range {
1341                start: Position {
1342                    line: 10,
1343                    character: 0,
1344                },
1345                end: Position {
1346                    line: 15,
1347                    character: 0,
1348                },
1349            },
1350        };
1351
1352        let doc2 = RustDocument {
1353            uri: Url::parse("file:///test.rs").unwrap(),
1354            content: String::new(),
1355            macros: vec![SpringMacro::Route(route2)],
1356        };
1357
1358        navigator.build_index(&[doc2]);
1359
1360        // 应该只有新的路由
1361        assert_eq!(navigator.index.routes.len(), 1);
1362        assert_eq!(navigator.index.routes[0].path, "/posts");
1363        assert_eq!(navigator.index.path_map.len(), 1);
1364        assert!(navigator.index.path_map.contains_key("/posts"));
1365        assert!(!navigator.index.path_map.contains_key("/users"));
1366    }
1367
1368    #[test]
1369    fn test_parse_path_parameters_no_params() {
1370        let navigator = RouteNavigator::new();
1371        let params = navigator.parse_path_parameters("/users");
1372        assert_eq!(params.len(), 0);
1373    }
1374
1375    #[test]
1376    fn test_parse_path_parameters_single_param() {
1377        let navigator = RouteNavigator::new();
1378        let params = navigator.parse_path_parameters("/users/{id}");
1379        assert_eq!(params.len(), 1);
1380        assert_eq!(params[0], "id");
1381    }
1382
1383    #[test]
1384    fn test_parse_path_parameters_multiple_params() {
1385        let navigator = RouteNavigator::new();
1386        let params = navigator.parse_path_parameters("/users/{user_id}/posts/{post_id}");
1387        assert_eq!(params.len(), 2);
1388        assert_eq!(params[0], "user_id");
1389        assert_eq!(params[1], "post_id");
1390    }
1391
1392    #[test]
1393    fn test_parse_path_parameters_empty_param() {
1394        let navigator = RouteNavigator::new();
1395        let params = navigator.parse_path_parameters("/users/{}");
1396        // 空参数名应该被忽略
1397        assert_eq!(params.len(), 0);
1398    }
1399
1400    #[test]
1401    fn test_parse_path_parameters_complex_path() {
1402        let navigator = RouteNavigator::new();
1403        let params = navigator
1404            .parse_path_parameters("/api/v1/users/{user_id}/posts/{post_id}/comments/{comment_id}");
1405        assert_eq!(params.len(), 3);
1406        assert_eq!(params[0], "user_id");
1407        assert_eq!(params[1], "post_id");
1408        assert_eq!(params[2], "comment_id");
1409    }
1410
1411    // ============================================================================
1412    // 路由查找功能测试
1413    // ============================================================================
1414
1415    #[test]
1416    fn test_get_all_routes_empty() {
1417        let navigator = RouteNavigator::new();
1418        let routes = navigator.get_all_routes();
1419        assert_eq!(routes.len(), 0);
1420    }
1421
1422    #[test]
1423    fn test_get_all_routes() {
1424        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1425
1426        let mut navigator = RouteNavigator::new();
1427
1428        let route1 = RouteMacro {
1429            path: "/users".to_string(),
1430            methods: vec![HttpMethod::Get],
1431            middlewares: vec![],
1432            handler_name: "list_users".to_string(),
1433            range: Range {
1434                start: Position {
1435                    line: 10,
1436                    character: 0,
1437                },
1438                end: Position {
1439                    line: 15,
1440                    character: 0,
1441                },
1442            },
1443        };
1444
1445        let route2 = RouteMacro {
1446            path: "/posts".to_string(),
1447            methods: vec![HttpMethod::Get],
1448            middlewares: vec![],
1449            handler_name: "list_posts".to_string(),
1450            range: Range {
1451                start: Position {
1452                    line: 20,
1453                    character: 0,
1454                },
1455                end: Position {
1456                    line: 25,
1457                    character: 0,
1458                },
1459            },
1460        };
1461
1462        let doc = RustDocument {
1463            uri: Url::parse("file:///test.rs").unwrap(),
1464            content: String::new(),
1465            macros: vec![SpringMacro::Route(route1), SpringMacro::Route(route2)],
1466        };
1467
1468        navigator.build_index(&[doc]);
1469
1470        let routes = navigator.get_all_routes();
1471        assert_eq!(routes.len(), 2);
1472        assert_eq!(routes[0].path, "/users");
1473        assert_eq!(routes[1].path, "/posts");
1474    }
1475
1476    #[test]
1477    fn test_find_routes_empty_pattern() {
1478        let navigator = RouteNavigator::new();
1479        let routes = navigator.find_routes("");
1480        assert_eq!(routes.len(), 0);
1481    }
1482
1483    #[test]
1484    fn test_find_routes_fuzzy_match() {
1485        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1486
1487        let mut navigator = RouteNavigator::new();
1488
1489        let routes_data = vec![
1490            ("/users", "list_users"),
1491            ("/users/{id}", "get_user"),
1492            ("/posts", "list_posts"),
1493            ("/api/users", "api_list_users"),
1494        ];
1495
1496        let macros: Vec<_> = routes_data
1497            .into_iter()
1498            .map(|(path, handler)| {
1499                SpringMacro::Route(RouteMacro {
1500                    path: path.to_string(),
1501                    methods: vec![HttpMethod::Get],
1502                    middlewares: vec![],
1503                    handler_name: handler.to_string(),
1504                    range: Range {
1505                        start: Position {
1506                            line: 0,
1507                            character: 0,
1508                        },
1509                        end: Position {
1510                            line: 0,
1511                            character: 0,
1512                        },
1513                    },
1514                })
1515            })
1516            .collect();
1517
1518        let doc = RustDocument {
1519            uri: Url::parse("file:///test.rs").unwrap(),
1520            content: String::new(),
1521            macros,
1522        };
1523
1524        navigator.build_index(&[doc]);
1525
1526        // 模糊匹配 "users"
1527        let routes = navigator.find_routes("users");
1528        assert_eq!(routes.len(), 3); // /users, /users/{id}, /api/users
1529
1530        // 模糊匹配 "posts"
1531        let routes = navigator.find_routes("posts");
1532        assert_eq!(routes.len(), 1); // /posts
1533
1534        // 模糊匹配 "/api"
1535        let routes = navigator.find_routes("/api");
1536        assert_eq!(routes.len(), 1); // /api/users
1537    }
1538
1539    #[test]
1540    fn test_find_routes_regex_match() {
1541        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1542
1543        let mut navigator = RouteNavigator::new();
1544
1545        let routes_data = vec![
1546            ("/users", "list_users"),
1547            ("/users/{id}", "get_user"),
1548            ("/posts", "list_posts"),
1549            ("/api/v1/users", "api_v1_users"),
1550            ("/api/v2/users", "api_v2_users"),
1551        ];
1552
1553        let macros: Vec<_> = routes_data
1554            .into_iter()
1555            .map(|(path, handler)| {
1556                SpringMacro::Route(RouteMacro {
1557                    path: path.to_string(),
1558                    methods: vec![HttpMethod::Get],
1559                    middlewares: vec![],
1560                    handler_name: handler.to_string(),
1561                    range: Range {
1562                        start: Position {
1563                            line: 0,
1564                            character: 0,
1565                        },
1566                        end: Position {
1567                            line: 0,
1568                            character: 0,
1569                        },
1570                    },
1571                })
1572            })
1573            .collect();
1574
1575        let doc = RustDocument {
1576            uri: Url::parse("file:///test.rs").unwrap(),
1577            content: String::new(),
1578            macros,
1579        };
1580
1581        navigator.build_index(&[doc]);
1582
1583        // 正则表达式匹配以 /api 开头的路由
1584        let routes = navigator.find_routes("regex:^/api/.*");
1585        assert_eq!(routes.len(), 2); // /api/v1/users, /api/v2/users
1586
1587        // 正则表达式匹配包含参数的路由
1588        let routes = navigator.find_routes("regex:.*\\{.*\\}.*");
1589        assert_eq!(routes.len(), 1); // /users/{id}
1590
1591        // 正则表达式匹配以 /users 开头的路由
1592        let routes = navigator.find_routes("regex:^/users");
1593        assert_eq!(routes.len(), 2); // /users, /users/{id}
1594    }
1595
1596    #[test]
1597    fn test_find_routes_regex_invalid() {
1598        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1599
1600        let mut navigator = RouteNavigator::new();
1601
1602        let route = RouteMacro {
1603            path: "/users".to_string(),
1604            methods: vec![HttpMethod::Get],
1605            middlewares: vec![],
1606            handler_name: "list_users".to_string(),
1607            range: Range {
1608                start: Position {
1609                    line: 0,
1610                    character: 0,
1611                },
1612                end: Position {
1613                    line: 0,
1614                    character: 0,
1615                },
1616            },
1617        };
1618
1619        let doc = RustDocument {
1620            uri: Url::parse("file:///test.rs").unwrap(),
1621            content: String::new(),
1622            macros: vec![SpringMacro::Route(route)],
1623        };
1624
1625        navigator.build_index(&[doc]);
1626
1627        // 无效的正则表达式应该返回空列表
1628        let routes = navigator.find_routes("regex:[invalid");
1629        assert_eq!(routes.len(), 0);
1630    }
1631
1632    #[test]
1633    fn test_find_routes_no_match() {
1634        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1635
1636        let mut navigator = RouteNavigator::new();
1637
1638        let route = RouteMacro {
1639            path: "/users".to_string(),
1640            methods: vec![HttpMethod::Get],
1641            middlewares: vec![],
1642            handler_name: "list_users".to_string(),
1643            range: Range {
1644                start: Position {
1645                    line: 0,
1646                    character: 0,
1647                },
1648                end: Position {
1649                    line: 0,
1650                    character: 0,
1651                },
1652            },
1653        };
1654
1655        let doc = RustDocument {
1656            uri: Url::parse("file:///test.rs").unwrap(),
1657            content: String::new(),
1658            macros: vec![SpringMacro::Route(route)],
1659        };
1660
1661        navigator.build_index(&[doc]);
1662
1663        // 不匹配的模式应该返回空列表
1664        let routes = navigator.find_routes("posts");
1665        assert_eq!(routes.len(), 0);
1666
1667        let routes = navigator.find_routes("regex:^/api/.*");
1668        assert_eq!(routes.len(), 0);
1669    }
1670
1671    #[test]
1672    fn test_find_routes_by_handler_empty() {
1673        let navigator = RouteNavigator::new();
1674        let routes = navigator.find_routes_by_handler("get_user");
1675        assert_eq!(routes.len(), 0);
1676    }
1677
1678    #[test]
1679    fn test_find_routes_by_handler_single() {
1680        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1681
1682        let mut navigator = RouteNavigator::new();
1683
1684        let route = RouteMacro {
1685            path: "/users/{id}".to_string(),
1686            methods: vec![HttpMethod::Get],
1687            middlewares: vec![],
1688            handler_name: "get_user".to_string(),
1689            range: Range {
1690                start: Position {
1691                    line: 10,
1692                    character: 0,
1693                },
1694                end: Position {
1695                    line: 15,
1696                    character: 0,
1697                },
1698            },
1699        };
1700
1701        let doc = RustDocument {
1702            uri: Url::parse("file:///test.rs").unwrap(),
1703            content: String::new(),
1704            macros: vec![SpringMacro::Route(route)],
1705        };
1706
1707        navigator.build_index(&[doc]);
1708
1709        let routes = navigator.find_routes_by_handler("get_user");
1710        assert_eq!(routes.len(), 1);
1711        assert_eq!(routes[0].path, "/users/{id}");
1712        assert_eq!(routes[0].handler.function_name, "get_user");
1713    }
1714
1715    #[test]
1716    fn test_find_routes_by_handler_multiple() {
1717        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1718
1719        let mut navigator = RouteNavigator::new();
1720
1721        // 同一个处理器处理多个路由
1722        let route1 = RouteMacro {
1723            path: "/users".to_string(),
1724            methods: vec![HttpMethod::Get, HttpMethod::Post],
1725            middlewares: vec![],
1726            handler_name: "handle_users".to_string(),
1727            range: Range {
1728                start: Position {
1729                    line: 10,
1730                    character: 0,
1731                },
1732                end: Position {
1733                    line: 15,
1734                    character: 0,
1735                },
1736            },
1737        };
1738
1739        let doc = RustDocument {
1740            uri: Url::parse("file:///test.rs").unwrap(),
1741            content: String::new(),
1742            macros: vec![SpringMacro::Route(route1)],
1743        };
1744
1745        navigator.build_index(&[doc]);
1746
1747        let routes = navigator.find_routes_by_handler("handle_users");
1748        assert_eq!(routes.len(), 2); // GET 和 POST 各一个
1749
1750        for route in routes {
1751            assert_eq!(route.path, "/users");
1752            assert_eq!(route.handler.function_name, "handle_users");
1753        }
1754    }
1755
1756    #[test]
1757    fn test_find_routes_by_handler_not_found() {
1758        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1759
1760        let mut navigator = RouteNavigator::new();
1761
1762        let route = RouteMacro {
1763            path: "/users".to_string(),
1764            methods: vec![HttpMethod::Get],
1765            middlewares: vec![],
1766            handler_name: "list_users".to_string(),
1767            range: Range {
1768                start: Position {
1769                    line: 10,
1770                    character: 0,
1771                },
1772                end: Position {
1773                    line: 15,
1774                    character: 0,
1775                },
1776            },
1777        };
1778
1779        let doc = RustDocument {
1780            uri: Url::parse("file:///test.rs").unwrap(),
1781            content: String::new(),
1782            macros: vec![SpringMacro::Route(route)],
1783        };
1784
1785        navigator.build_index(&[doc]);
1786
1787        let routes = navigator.find_routes_by_handler("get_user");
1788        assert_eq!(routes.len(), 0);
1789    }
1790
1791    #[test]
1792    fn test_route_location_for_jump() {
1793        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1794
1795        let mut navigator = RouteNavigator::new();
1796
1797        let route = RouteMacro {
1798            path: "/users/{id}".to_string(),
1799            methods: vec![HttpMethod::Get],
1800            middlewares: vec![],
1801            handler_name: "get_user".to_string(),
1802            range: Range {
1803                start: Position {
1804                    line: 42,
1805                    character: 5,
1806                },
1807                end: Position {
1808                    line: 50,
1809                    character: 10,
1810                },
1811            },
1812        };
1813
1814        let uri = Url::parse("file:///src/handlers/users.rs").unwrap();
1815
1816        let doc = RustDocument {
1817            uri: uri.clone(),
1818            content: String::new(),
1819            macros: vec![SpringMacro::Route(route)],
1820        };
1821
1822        navigator.build_index(&[doc]);
1823
1824        let routes = navigator.find_routes("users");
1825        assert_eq!(routes.len(), 1);
1826
1827        // 验证位置信息可用于跳转
1828        let location = &routes[0].location;
1829        assert_eq!(location.uri, uri);
1830        assert_eq!(location.range.start.line, 42);
1831        assert_eq!(location.range.start.character, 5);
1832        assert_eq!(location.range.end.line, 50);
1833        assert_eq!(location.range.end.character, 10);
1834    }
1835
1836    // ============================================================================
1837    // 路由验证功能测试
1838    // ============================================================================
1839
1840    #[test]
1841    fn test_validate_routes_empty() {
1842        let navigator = RouteNavigator::new();
1843        let diagnostics = navigator.validate_routes();
1844        assert_eq!(diagnostics.len(), 0);
1845    }
1846
1847    #[test]
1848    fn test_validate_path_characters_valid() {
1849        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1850
1851        let mut navigator = RouteNavigator::new();
1852
1853        let route = RouteMacro {
1854            path: "/api/v1/users/{id}/posts".to_string(),
1855            methods: vec![HttpMethod::Get],
1856            middlewares: vec![],
1857            handler_name: "get_user_posts".to_string(),
1858            range: Range {
1859                start: Position {
1860                    line: 10,
1861                    character: 0,
1862                },
1863                end: Position {
1864                    line: 15,
1865                    character: 0,
1866                },
1867            },
1868        };
1869
1870        let doc = RustDocument {
1871            uri: Url::parse("file:///test.rs").unwrap(),
1872            content: String::new(),
1873            macros: vec![SpringMacro::Route(route)],
1874        };
1875
1876        navigator.build_index(&[doc]);
1877
1878        let diagnostics = navigator.validate_routes();
1879        // 不应该有路径字符错误
1880        assert!(!diagnostics.iter().any(|d| d
1881            .code
1882            .as_ref()
1883            .and_then(|c| match c {
1884                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
1885                _ => None,
1886            })
1887            .map(|s| s == "invalid-path-char")
1888            .unwrap_or(false)));
1889    }
1890
1891    #[test]
1892    fn test_validate_path_characters_invalid() {
1893        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1894
1895        let mut navigator = RouteNavigator::new();
1896
1897        let route = RouteMacro {
1898            path: "/users/<id>".to_string(), // < 和 > 是无效字符
1899            methods: vec![HttpMethod::Get],
1900            middlewares: vec![],
1901            handler_name: "get_user".to_string(),
1902            range: Range {
1903                start: Position {
1904                    line: 10,
1905                    character: 0,
1906                },
1907                end: Position {
1908                    line: 15,
1909                    character: 0,
1910                },
1911            },
1912        };
1913
1914        let doc = RustDocument {
1915            uri: Url::parse("file:///test.rs").unwrap(),
1916            content: String::new(),
1917            macros: vec![SpringMacro::Route(route)],
1918        };
1919
1920        navigator.build_index(&[doc]);
1921
1922        let diagnostics = navigator.validate_routes();
1923        // 应该有路径字符错误
1924        assert!(diagnostics.iter().any(|d| d
1925            .code
1926            .as_ref()
1927            .and_then(|c| match c {
1928                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
1929                _ => None,
1930            })
1931            .map(|s| s == "invalid-path-char")
1932            .unwrap_or(false)));
1933    }
1934
1935    #[test]
1936    fn test_validate_path_parameter_syntax_valid() {
1937        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1938
1939        let mut navigator = RouteNavigator::new();
1940
1941        let route = RouteMacro {
1942            path: "/users/{id}/posts/{post_id}".to_string(),
1943            methods: vec![HttpMethod::Get],
1944            middlewares: vec![],
1945            handler_name: "get_user_post".to_string(),
1946            range: Range {
1947                start: Position {
1948                    line: 10,
1949                    character: 0,
1950                },
1951                end: Position {
1952                    line: 15,
1953                    character: 0,
1954                },
1955            },
1956        };
1957
1958        let doc = RustDocument {
1959            uri: Url::parse("file:///test.rs").unwrap(),
1960            content: String::new(),
1961            macros: vec![SpringMacro::Route(route)],
1962        };
1963
1964        navigator.build_index(&[doc]);
1965
1966        let diagnostics = navigator.validate_routes();
1967        // 不应该有参数语法错误
1968        assert!(!diagnostics.iter().any(|d| {
1969            if let Some(lsp_types::NumberOrString::String(code)) = &d.code {
1970                code.contains("path-param") || code.contains("brace")
1971            } else {
1972                false
1973            }
1974        }));
1975    }
1976
1977    #[test]
1978    fn test_validate_path_parameter_syntax_empty_param() {
1979        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
1980
1981        let mut navigator = RouteNavigator::new();
1982
1983        let route = RouteMacro {
1984            path: "/users/{}".to_string(),
1985            methods: vec![HttpMethod::Get],
1986            middlewares: vec![],
1987            handler_name: "get_user".to_string(),
1988            range: Range {
1989                start: Position {
1990                    line: 10,
1991                    character: 0,
1992                },
1993                end: Position {
1994                    line: 15,
1995                    character: 0,
1996                },
1997            },
1998        };
1999
2000        let doc = RustDocument {
2001            uri: Url::parse("file:///test.rs").unwrap(),
2002            content: String::new(),
2003            macros: vec![SpringMacro::Route(route)],
2004        };
2005
2006        navigator.build_index(&[doc]);
2007
2008        let diagnostics = navigator.validate_routes();
2009        // 应该有空参数名错误
2010        assert!(diagnostics.iter().any(|d| d
2011            .code
2012            .as_ref()
2013            .and_then(|c| match c {
2014                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
2015                _ => None,
2016            })
2017            .map(|s| s == "empty-path-param")
2018            .unwrap_or(false)));
2019    }
2020
2021    #[test]
2022    fn test_validate_path_parameter_syntax_unclosed() {
2023        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2024
2025        let mut navigator = RouteNavigator::new();
2026
2027        let route = RouteMacro {
2028            path: "/users/{id".to_string(),
2029            methods: vec![HttpMethod::Get],
2030            middlewares: vec![],
2031            handler_name: "get_user".to_string(),
2032            range: Range {
2033                start: Position {
2034                    line: 10,
2035                    character: 0,
2036                },
2037                end: Position {
2038                    line: 15,
2039                    character: 0,
2040                },
2041            },
2042        };
2043
2044        let doc = RustDocument {
2045            uri: Url::parse("file:///test.rs").unwrap(),
2046            content: String::new(),
2047            macros: vec![SpringMacro::Route(route)],
2048        };
2049
2050        navigator.build_index(&[doc]);
2051
2052        let diagnostics = navigator.validate_routes();
2053        // 应该有未闭合括号错误
2054        assert!(diagnostics.iter().any(|d| d
2055            .code
2056            .as_ref()
2057            .and_then(|c| match c {
2058                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
2059                _ => None,
2060            })
2061            .map(|s| s == "unclosed-path-param")
2062            .unwrap_or(false)));
2063    }
2064
2065    #[test]
2066    fn test_validate_path_parameter_syntax_unmatched_closing() {
2067        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2068
2069        let mut navigator = RouteNavigator::new();
2070
2071        let route = RouteMacro {
2072            path: "/users/id}".to_string(),
2073            methods: vec![HttpMethod::Get],
2074            middlewares: vec![],
2075            handler_name: "get_user".to_string(),
2076            range: Range {
2077                start: Position {
2078                    line: 10,
2079                    character: 0,
2080                },
2081                end: Position {
2082                    line: 15,
2083                    character: 0,
2084                },
2085            },
2086        };
2087
2088        let doc = RustDocument {
2089            uri: Url::parse("file:///test.rs").unwrap(),
2090            content: String::new(),
2091            macros: vec![SpringMacro::Route(route)],
2092        };
2093
2094        navigator.build_index(&[doc]);
2095
2096        let diagnostics = navigator.validate_routes();
2097        // 应该有未匹配的闭括号错误
2098        assert!(diagnostics.iter().any(|d| d
2099            .code
2100            .as_ref()
2101            .and_then(|c| match c {
2102                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
2103                _ => None,
2104            })
2105            .map(|s| s == "unmatched-closing-brace")
2106            .unwrap_or(false)));
2107    }
2108
2109    #[test]
2110    fn test_validate_path_parameter_syntax_nested() {
2111        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2112
2113        let mut navigator = RouteNavigator::new();
2114
2115        let route = RouteMacro {
2116            path: "/users/{{id}}".to_string(),
2117            methods: vec![HttpMethod::Get],
2118            middlewares: vec![],
2119            handler_name: "get_user".to_string(),
2120            range: Range {
2121                start: Position {
2122                    line: 10,
2123                    character: 0,
2124                },
2125                end: Position {
2126                    line: 15,
2127                    character: 0,
2128                },
2129            },
2130        };
2131
2132        let doc = RustDocument {
2133            uri: Url::parse("file:///test.rs").unwrap(),
2134            content: String::new(),
2135            macros: vec![SpringMacro::Route(route)],
2136        };
2137
2138        navigator.build_index(&[doc]);
2139
2140        let diagnostics = navigator.validate_routes();
2141        // 应该有嵌套括号错误
2142        assert!(diagnostics.iter().any(|d| d
2143            .code
2144            .as_ref()
2145            .and_then(|c| match c {
2146                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
2147                _ => None,
2148            })
2149            .map(|s| s == "nested-path-param")
2150            .unwrap_or(false)));
2151    }
2152
2153    #[test]
2154    fn test_validate_restful_style_valid() {
2155        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2156
2157        let mut navigator = RouteNavigator::new();
2158
2159        let route = RouteMacro {
2160            path: "/api/v1/users/{id}/posts".to_string(),
2161            methods: vec![HttpMethod::Get],
2162            middlewares: vec![],
2163            handler_name: "get_user_posts".to_string(),
2164            range: Range {
2165                start: Position {
2166                    line: 10,
2167                    character: 0,
2168                },
2169                end: Position {
2170                    line: 15,
2171                    character: 0,
2172                },
2173            },
2174        };
2175
2176        let doc = RustDocument {
2177            uri: Url::parse("file:///test.rs").unwrap(),
2178            content: String::new(),
2179            macros: vec![SpringMacro::Route(route)],
2180        };
2181
2182        navigator.build_index(&[doc]);
2183
2184        let diagnostics = navigator.validate_routes();
2185        // 不应该有 RESTful 风格警告
2186        assert!(!diagnostics.iter().any(|d| {
2187            if let Some(lsp_types::NumberOrString::String(code)) = &d.code {
2188                code.starts_with("restful-style")
2189            } else {
2190                false
2191            }
2192        }));
2193    }
2194
2195    #[test]
2196    fn test_validate_restful_style_verb() {
2197        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2198
2199        let mut navigator = RouteNavigator::new();
2200
2201        let route = RouteMacro {
2202            path: "/getUsers".to_string(),
2203            methods: vec![HttpMethod::Get],
2204            middlewares: vec![],
2205            handler_name: "get_users".to_string(),
2206            range: Range {
2207                start: Position {
2208                    line: 10,
2209                    character: 0,
2210                },
2211                end: Position {
2212                    line: 15,
2213                    character: 0,
2214                },
2215            },
2216        };
2217
2218        let doc = RustDocument {
2219            uri: Url::parse("file:///test.rs").unwrap(),
2220            content: String::new(),
2221            macros: vec![SpringMacro::Route(route)],
2222        };
2223
2224        navigator.build_index(&[doc]);
2225
2226        let diagnostics = navigator.validate_routes();
2227        // 应该有动词使用警告
2228        assert!(diagnostics.iter().any(|d| d
2229            .code
2230            .as_ref()
2231            .and_then(|c| match c {
2232                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
2233                _ => None,
2234            })
2235            .map(|s| s == "restful-style-verb")
2236            .unwrap_or(false)));
2237    }
2238
2239    #[test]
2240    fn test_validate_restful_style_case() {
2241        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2242
2243        let mut navigator = RouteNavigator::new();
2244
2245        let route = RouteMacro {
2246            path: "/userProfiles".to_string(),
2247            methods: vec![HttpMethod::Get],
2248            middlewares: vec![],
2249            handler_name: "get_user_profiles".to_string(),
2250            range: Range {
2251                start: Position {
2252                    line: 10,
2253                    character: 0,
2254                },
2255                end: Position {
2256                    line: 15,
2257                    character: 0,
2258                },
2259            },
2260        };
2261
2262        let doc = RustDocument {
2263            uri: Url::parse("file:///test.rs").unwrap(),
2264            content: String::new(),
2265            macros: vec![SpringMacro::Route(route)],
2266        };
2267
2268        navigator.build_index(&[doc]);
2269
2270        let diagnostics = navigator.validate_routes();
2271        // 应该有大写字母使用警告
2272        assert!(diagnostics.iter().any(|d| d
2273            .code
2274            .as_ref()
2275            .and_then(|c| match c {
2276                lsp_types::NumberOrString::String(s) => Some(s.as_str()),
2277                _ => None,
2278            })
2279            .map(|s| s == "restful-style-case")
2280            .unwrap_or(false)));
2281    }
2282
2283    #[test]
2284    fn test_detect_conflicts_empty() {
2285        let navigator = RouteNavigator::new();
2286        let conflicts = navigator.detect_conflicts();
2287        assert_eq!(conflicts.len(), 0);
2288    }
2289
2290    #[test]
2291    fn test_detect_conflicts_no_conflict() {
2292        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2293
2294        let mut navigator = RouteNavigator::new();
2295
2296        let route1 = RouteMacro {
2297            path: "/users".to_string(),
2298            methods: vec![HttpMethod::Get],
2299            middlewares: vec![],
2300            handler_name: "list_users".to_string(),
2301            range: Range {
2302                start: Position {
2303                    line: 10,
2304                    character: 0,
2305                },
2306                end: Position {
2307                    line: 15,
2308                    character: 0,
2309                },
2310            },
2311        };
2312
2313        let route2 = RouteMacro {
2314            path: "/users".to_string(),
2315            methods: vec![HttpMethod::Post],
2316            middlewares: vec![],
2317            handler_name: "create_user".to_string(),
2318            range: Range {
2319                start: Position {
2320                    line: 20,
2321                    character: 0,
2322                },
2323                end: Position {
2324                    line: 25,
2325                    character: 0,
2326                },
2327            },
2328        };
2329
2330        let doc = RustDocument {
2331            uri: Url::parse("file:///test.rs").unwrap(),
2332            content: String::new(),
2333            macros: vec![SpringMacro::Route(route1), SpringMacro::Route(route2)],
2334        };
2335
2336        navigator.build_index(&[doc]);
2337
2338        let conflicts = navigator.detect_conflicts();
2339        // 不同的 HTTP 方法,不应该有冲突
2340        assert_eq!(conflicts.len(), 0);
2341    }
2342
2343    #[test]
2344    fn test_detect_conflicts_same_path_and_method() {
2345        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2346
2347        let mut navigator = RouteNavigator::new();
2348
2349        let route1 = RouteMacro {
2350            path: "/users".to_string(),
2351            methods: vec![HttpMethod::Get],
2352            middlewares: vec![],
2353            handler_name: "list_users".to_string(),
2354            range: Range {
2355                start: Position {
2356                    line: 10,
2357                    character: 0,
2358                },
2359                end: Position {
2360                    line: 15,
2361                    character: 0,
2362                },
2363            },
2364        };
2365
2366        let route2 = RouteMacro {
2367            path: "/users".to_string(),
2368            methods: vec![HttpMethod::Get],
2369            middlewares: vec![],
2370            handler_name: "get_users".to_string(),
2371            range: Range {
2372                start: Position {
2373                    line: 20,
2374                    character: 0,
2375                },
2376                end: Position {
2377                    line: 25,
2378                    character: 0,
2379                },
2380            },
2381        };
2382
2383        let doc = RustDocument {
2384            uri: Url::parse("file:///test.rs").unwrap(),
2385            content: String::new(),
2386            macros: vec![SpringMacro::Route(route1), SpringMacro::Route(route2)],
2387        };
2388
2389        navigator.build_index(&[doc]);
2390
2391        let conflicts = navigator.detect_conflicts();
2392        // 相同的路径和方法,应该有冲突
2393        assert_eq!(conflicts.len(), 1);
2394        assert_eq!(conflicts[0].path, "/users");
2395        assert_eq!(conflicts[0].method, HttpMethod::Get);
2396    }
2397
2398    #[test]
2399    fn test_detect_conflicts_multiple() {
2400        use crate::macro_analyzer::{RouteMacro, RustDocument, SpringMacro};
2401
2402        let mut navigator = RouteNavigator::new();
2403
2404        // 三个路由,都是 GET /users
2405        let route1 = RouteMacro {
2406            path: "/users".to_string(),
2407            methods: vec![HttpMethod::Get],
2408            middlewares: vec![],
2409            handler_name: "handler1".to_string(),
2410            range: Range {
2411                start: Position {
2412                    line: 10,
2413                    character: 0,
2414                },
2415                end: Position {
2416                    line: 15,
2417                    character: 0,
2418                },
2419            },
2420        };
2421
2422        let route2 = RouteMacro {
2423            path: "/users".to_string(),
2424            methods: vec![HttpMethod::Get],
2425            middlewares: vec![],
2426            handler_name: "handler2".to_string(),
2427            range: Range {
2428                start: Position {
2429                    line: 20,
2430                    character: 0,
2431                },
2432                end: Position {
2433                    line: 25,
2434                    character: 0,
2435                },
2436            },
2437        };
2438
2439        let route3 = RouteMacro {
2440            path: "/users".to_string(),
2441            methods: vec![HttpMethod::Get],
2442            middlewares: vec![],
2443            handler_name: "handler3".to_string(),
2444            range: Range {
2445                start: Position {
2446                    line: 30,
2447                    character: 0,
2448                },
2449                end: Position {
2450                    line: 35,
2451                    character: 0,
2452                },
2453            },
2454        };
2455
2456        let doc = RustDocument {
2457            uri: Url::parse("file:///test.rs").unwrap(),
2458            content: String::new(),
2459            macros: vec![
2460                SpringMacro::Route(route1),
2461                SpringMacro::Route(route2),
2462                SpringMacro::Route(route3),
2463            ],
2464        };
2465
2466        navigator.build_index(&[doc]);
2467
2468        let conflicts = navigator.detect_conflicts();
2469        // 应该有 3 个冲突:(0,1), (0,2), (1,2)
2470        assert_eq!(conflicts.len(), 3);
2471    }
2472
2473    #[test]
2474    fn test_route_conflict_creation() {
2475        let conflict = RouteConflict {
2476            index1: 0,
2477            index2: 1,
2478            path: "/users".to_string(),
2479            method: HttpMethod::Get,
2480            location1: Location {
2481                uri: Url::parse("file:///test.rs").unwrap(),
2482                range: Range {
2483                    start: Position {
2484                        line: 10,
2485                        character: 0,
2486                    },
2487                    end: Position {
2488                        line: 15,
2489                        character: 0,
2490                    },
2491                },
2492            },
2493            location2: Location {
2494                uri: Url::parse("file:///test.rs").unwrap(),
2495                range: Range {
2496                    start: Position {
2497                        line: 20,
2498                        character: 0,
2499                    },
2500                    end: Position {
2501                        line: 25,
2502                        character: 0,
2503                    },
2504                },
2505            },
2506        };
2507
2508        assert_eq!(conflict.index1, 0);
2509        assert_eq!(conflict.index2, 1);
2510        assert_eq!(conflict.path, "/users");
2511        assert_eq!(conflict.method, HttpMethod::Get);
2512    }
2513}