Skip to main content

sz_rust_core/
routing.rs

1//! 三层路由机制 — 属性宏 / 配置式 / 约定式
2//!
3//! 对齐 PHP `think-route` + `config/route.php` + `auto_multi_app`,提供三种渐进式路由注册方式。
4//!
5//! ## 三层架构
6//!
7//! | 层级 | 机制 | PHP 对齐 | 启用方式 | 适用场景 |
8//! |------|------|---------|---------|---------|
9//! | Layer 1 | 属性宏路由 | `#[Route]` 注解 | `#[controller]` + `#[get]` | 控制器内嵌路由声明 |
10//! | Layer 2 | 配置式路由 | `config/route.php` | YAML/JSON 配置文件 | 路由与代码解耦 |
11//! | Layer 3 | 约定式路由 | `auto_multi_app` + `app/controller/action` | `parse_path` 自动映射 | 快速原型 / 内部 API |
12//!
13//! ## 设计原则
14//!
15//! 1. **三层独立**:每层可独立使用,也可组合使用
16//! 2. **优先级递减**:Layer 1 > Layer 2 > Layer 3(前层覆盖后层)
17//! 3. **类型安全**:Layer 1 在编译期检查;Layer 2/3 在加载期检查
18//! 4. **渐进迁移**:从 Layer 3 起步,逐步迁移到 Layer 2/1
19//!
20//! ## 用法示例
21//!
22//! ### Layer 2 - 配置式路由(推荐生产使用)
23//!
24//! ```ignore
25//! use sz_rust_core::routing::{RouteConfig, RouteRule, HttpMethod, load_routes_from_yaml_str};
26//! use sz_rust_core::router::RouterBuilder;
27//!
28//! let yaml = r#"
29//! routes:
30//!   - method: GET
31//!     path: /users
32//!     handler: User@list
33//!   - method: POST
34//!     path: /users
35//!     handler: User@create
36//! "#;
37//! let config = load_routes_from_yaml_str(yaml).unwrap();
38//! // config.routes 包含解析后的路由规则
39//! ```
40//!
41//! ### Layer 3 - 约定式路由(已在 `multi_app::parse_path` 中实现)
42//!
43//! ```ignore
44//! use sz_rust_core::router::parse_path;
45//!
46//! let p = parse_path("/oapc/customer/index");
47//! assert_eq!(p.app, "oapc");
48//! assert_eq!(p.controller, "Customer");
49//! assert_eq!(p.action, "index");
50//! ```
51
52use std::collections::HashMap;
53
54use serde::{Deserialize, Serialize};
55
56use crate::router::ParsedPath;
57
58// ============================================================================
59// 公共数据结构
60// ============================================================================
61
62/// HTTP 方法枚举
63///
64/// 对齐 PHP `think\Route::$method`,仅包含 RESTful 5 大方法 + OPTIONS。
65#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
66#[serde(rename_all = "UPPERCASE")]
67pub enum HttpMethod {
68    /// GET 方法
69    GET,
70    /// POST 方法
71    POST,
72    /// PUT 方法
73    PUT,
74    /// DELETE 方法
75    DELETE,
76    /// PATCH 方法
77    PATCH,
78    /// OPTIONS 方法(CORS 预检)
79    OPTIONS,
80}
81
82impl HttpMethod {
83    /// 从字符串解析 HTTP 方法(大小写不敏感)
84    ///
85    /// ```
86    /// use sz_rust_core::routing::HttpMethod;
87    ///
88    /// assert_eq!(HttpMethod::parse("get").unwrap(), HttpMethod::GET);
89    /// assert_eq!(HttpMethod::parse("POST").unwrap(), HttpMethod::POST);
90    /// assert!(HttpMethod::parse("invalid").is_err());
91    /// ```
92    pub fn parse(s: &str) -> Result<Self, RouteConfigError> {
93        match s.to_uppercase().as_str() {
94            "GET" => Ok(HttpMethod::GET),
95            "POST" => Ok(HttpMethod::POST),
96            "PUT" => Ok(HttpMethod::PUT),
97            "DELETE" => Ok(HttpMethod::DELETE),
98            "PATCH" => Ok(HttpMethod::PATCH),
99            "OPTIONS" => Ok(HttpMethod::OPTIONS),
100            other => Err(RouteConfigError::InvalidMethod(other.to_string())),
101        }
102    }
103
104    /// 转换为 axum::http::Method
105    pub fn to_axum_method(&self) -> axum::http::Method {
106        match self {
107            HttpMethod::GET => axum::http::Method::GET,
108            HttpMethod::POST => axum::http::Method::POST,
109            HttpMethod::PUT => axum::http::Method::PUT,
110            HttpMethod::DELETE => axum::http::Method::DELETE,
111            HttpMethod::PATCH => axum::http::Method::PATCH,
112            HttpMethod::OPTIONS => axum::http::Method::OPTIONS,
113        }
114    }
115}
116
117impl std::fmt::Display for HttpMethod {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            HttpMethod::GET => write!(f, "GET"),
121            HttpMethod::POST => write!(f, "POST"),
122            HttpMethod::PUT => write!(f, "PUT"),
123            HttpMethod::DELETE => write!(f, "DELETE"),
124            HttpMethod::PATCH => write!(f, "PATCH"),
125            HttpMethod::OPTIONS => write!(f, "OPTIONS"),
126        }
127    }
128}
129
130/// Handler 引用 — `"Controller@action"` 格式
131///
132/// 对齐 PHP `Route::rule('path', 'Controller/action')` 的字符串引用方式。
133///
134/// ## 解析规则
135///
136/// - `"User@list"` → `HandlerRef { controller: "User", action: "list" }`
137/// - `"User/list"` → 同上(兼容 PHP `/` 分隔符)
138/// - `"User"` → `HandlerRef { controller: "User", action: "index" }`(默认 action)
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140pub struct HandlerRef {
141    /// 控制器名(首字母大写,如 `User`)
142    pub controller: String,
143    /// 操作名(小驼峰,如 `list` / `index`)
144    pub action: String,
145}
146
147impl HandlerRef {
148    /// 从 `"Controller@action"` 或 `"Controller/action"` 字符串解析
149    ///
150    /// ## 校验规则
151    ///
152    /// - controller / action 必须匹配 `[A-Za-z_][A-Za-z0-9_]*`(PHP 标识符规则)
153    /// - 拒绝 `../`、空格、`@`、`/` 等可能引发路径穿越或解析歧义的字符
154    ///
155    /// ```
156    /// use sz_rust_core::routing::HandlerRef;
157    ///
158    /// let h = HandlerRef::parse("User@list").unwrap();
159    /// assert_eq!(h.controller, "User");
160    /// assert_eq!(h.action, "list");
161    ///
162    /// let h = HandlerRef::parse("User").unwrap();
163    /// assert_eq!(h.controller, "User");
164    /// assert_eq!(h.action, "index");
165    /// ```
166    pub fn parse(s: &str) -> Result<Self, RouteConfigError> {
167        let s = s.trim();
168        if s.is_empty() {
169            return Err(RouteConfigError::EmptyHandler);
170        }
171
172        // 优先按 '@' 分隔,其次按 '/' 分隔
173        let (controller, action) = if let Some((c, a)) = s.split_once('@') {
174            (c, a)
175        } else if let Some((c, a)) = s.split_once('/') {
176            (c, a)
177        } else {
178            (s, crate::router::DEFAULT_ACTION)
179        };
180
181        let controller = controller.trim();
182        let action = action.trim();
183
184        if controller.is_empty() {
185            return Err(RouteConfigError::EmptyController);
186        }
187        if action.is_empty() {
188            return Err(RouteConfigError::EmptyAction);
189        }
190        if !is_valid_identifier(controller) {
191            return Err(RouteConfigError::InvalidController(controller.to_string()));
192        }
193        if !is_valid_identifier(action) {
194            return Err(RouteConfigError::InvalidAction(action.to_string()));
195        }
196
197        Ok(Self {
198            controller: controller.to_string(),
199            action: action.to_string(),
200        })
201    }
202
203    /// 转换为 `"Controller@action"` 字符串
204    pub fn to_handler_string(&self) -> String {
205        format!("{}@{}", self.controller, self.action)
206    }
207}
208
209/// 校验是否为合法的 PHP 风格标识符
210///
211/// 规则:首字符必须是字母或下划线,其余字符必须是字母/数字/下划线。
212/// 用于阻止 `../Secret`、`User@list@extra`、`path/to/file` 等注入字符。
213fn is_valid_identifier(s: &str) -> bool {
214    let mut chars = s.chars();
215    match chars.next() {
216        Some(first) if first.is_ascii_alphabetic() || first == '_' => {}
217        _ => return false,
218    }
219    chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
220}
221
222impl std::fmt::Display for HandlerRef {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        write!(f, "{}@{}", self.controller, self.action)
225    }
226}
227
228// ============================================================================
229// Layer 2 - 配置式路由
230// ============================================================================
231
232/// 路由配置错误
233#[derive(Debug, thiserror::Error)]
234pub enum RouteConfigError {
235    /// YAML 解析失败
236    #[error("YAML parse error: {0}")]
237    YamlParse(#[from] serde_yml::Error),
238
239    /// JSON 解析失败
240    #[error("JSON parse error: {0}")]
241    JsonParse(#[from] serde_json::Error),
242
243    /// 无效的 HTTP 方法
244    #[error("invalid HTTP method: {0}")]
245    InvalidMethod(String),
246
247    /// 空的 handler 字符串
248    #[error("empty handler string")]
249    EmptyHandler,
250
251    /// 空的控制器名
252    #[error("empty controller name in handler")]
253    EmptyController,
254
255    /// 空的 action 名
256    #[error("empty action name in handler")]
257    EmptyAction,
258
259    /// 无效的控制器名(包含非 `[A-Za-z0-9_]` 字符或首字符不是字母/下划线)
260    #[error("invalid controller name: {0}")]
261    InvalidController(String),
262
263    /// 无效的 action 名(包含非 `[A-Za-z0-9_]` 字符或首字符不是字母/下划线)
264    #[error("invalid action name: {0}")]
265    InvalidAction(String),
266
267    /// handler 解析失败
268    #[error("handler parse error: {0}")]
269    HandlerParse(String),
270
271    /// 路由冲突(同 method+path 重复)
272    #[error("route conflict: {method} {path} already registered")]
273    Conflict {
274        /// 冲突的 HTTP 方法
275        method: String,
276        /// 冲突的路径
277        path: String,
278    },
279
280    /// 文件读取失败
281    #[error("failed to read route config file: {0}")]
282    FileRead(#[source] std::io::Error),
283}
284
285/// 单条路由规则
286///
287/// 对齐 PHP `think\Route::rule($rule, $route, $method)` 的单条规则结构。
288#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
289pub struct RouteRule {
290    /// HTTP 方法
291    pub method: HttpMethod,
292    /// 路径模板,如 `/users/{id}`(对齐 axum 0.8 路由语法)
293    pub path: String,
294    /// Handler 引用,如 `User@list`
295    pub handler: String,
296    /// 中间件列表(按名称引用,运行时通过 middleware registry 解析)
297    #[serde(default)]
298    pub middleware: Vec<String>,
299    /// 路由名称(可选,用于反向 URL 生成)
300    #[serde(default)]
301    pub name: Option<String>,
302}
303
304impl RouteRule {
305    /// 创建新路由规则
306    pub fn new(method: HttpMethod, path: impl Into<String>, handler: impl Into<String>) -> Self {
307        Self {
308            method,
309            path: path.into(),
310            handler: handler.into(),
311            middleware: Vec::new(),
312            name: None,
313        }
314    }
315
316    /// 解析 handler 字符串为 [`HandlerRef`]
317    pub fn handler_ref(&self) -> Result<HandlerRef, RouteConfigError> {
318        HandlerRef::parse(&self.handler)
319    }
320
321    /// 添加中间件
322    pub fn with_middleware(mut self, name: impl Into<String>) -> Self {
323        self.middleware.push(name.into());
324        self
325    }
326
327    /// 设置路由名称
328    pub fn with_name(mut self, name: impl Into<String>) -> Self {
329        self.name = Some(name.into());
330        self
331    }
332}
333
334/// 路由配置文件
335///
336/// 对齐 PHP `config/route.php` 的整体配置结构。
337#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
338pub struct RouteConfig {
339    /// 路由规则列表
340    #[serde(default)]
341    pub routes: Vec<RouteRule>,
342    /// 路由分组(每组有自己的前缀和中间件)
343    #[serde(default)]
344    pub groups: Vec<RouteGroup>,
345}
346
347impl RouteConfig {
348    /// 创建空配置
349    pub fn new() -> Self {
350        Self::default()
351    }
352
353    /// 添加单条路由
354    pub fn add_route(&mut self, rule: RouteRule) {
355        self.routes.push(rule);
356    }
357
358    /// 添加路由分组
359    pub fn add_group(&mut self, group: RouteGroup) {
360        self.groups.push(group);
361    }
362
363    /// 展开所有 group,返回扁平化后的路由列表
364    ///
365    /// group 内路由的最终 path = `{group.prefix}/{rule.path}`(去重 `/`)
366    /// group 内路由继承 group 的中间件(追加在 rule.middleware 之前)
367    pub fn flatten(&self) -> Vec<RouteRule> {
368        let mut result = self.routes.clone();
369        for group in &self.groups {
370            for rule in &group.routes {
371                let mut flattened = rule.clone();
372                flattened.path = join_path(&group.prefix, &flattened.path);
373                // group 中间件前置
374                let mut mw = group.middleware.clone();
375                mw.extend(flattened.middleware);
376                flattened.middleware = mw;
377                result.push(flattened);
378            }
379        }
380        result
381    }
382
383    /// 检查路由冲突(同 method+path 重复)
384    ///
385    /// 返回冲突列表(空表示无冲突)
386    pub fn find_conflicts(&self) -> Vec<(RouteRule, RouteRule)> {
387        let flattened = self.flatten();
388        let mut seen: HashMap<(String, String), usize> = HashMap::new();
389        let mut conflicts = Vec::new();
390
391        for (i, rule) in flattened.iter().enumerate() {
392            let key = (rule.method.to_string(), rule.path.clone());
393            if let Some(&prev_idx) = seen.get(&key) {
394                conflicts.push((flattened[prev_idx].clone(), flattened[i].clone()));
395            } else {
396                seen.insert(key, i);
397            }
398        }
399
400        conflicts
401    }
402}
403
404/// 路由分组
405///
406/// 对齐 PHP `Route::group($prefix, $callback)`,给组内所有路由添加统一前缀和中间件。
407#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
408pub struct RouteGroup {
409    /// 路径前缀,如 `/api/v1`
410    pub prefix: String,
411    /// 组内路由规则
412    #[serde(default)]
413    pub routes: Vec<RouteRule>,
414    /// 组级中间件(应用到组内所有路由)
415    #[serde(default)]
416    pub middleware: Vec<String>,
417}
418
419impl RouteGroup {
420    /// 创建新路由分组
421    pub fn new(prefix: impl Into<String>) -> Self {
422        Self {
423            prefix: prefix.into(),
424            routes: Vec::new(),
425            middleware: Vec::new(),
426        }
427    }
428
429    /// 添加路由
430    pub fn add_route(&mut self, rule: RouteRule) -> &mut Self {
431        self.routes.push(rule);
432        self
433    }
434
435    /// 添加中间件
436    pub fn with_middleware(mut self, name: impl Into<String>) -> Self {
437        self.middleware.push(name.into());
438        self
439    }
440}
441
442/// 拼接两个路径片段,正确处理 `/` 分隔
443fn join_path(prefix: &str, path: &str) -> String {
444    let prefix = prefix.trim_end_matches('/');
445    let path = path.trim_start_matches('/');
446    if path.is_empty() {
447        prefix.to_string()
448    } else if prefix.is_empty() {
449        format!("/{path}")
450    } else {
451        format!("{prefix}/{path}")
452    }
453}
454
455// ============================================================================
456// Layer 2 - 配置加载函数
457// ============================================================================
458
459/// 从 YAML 字符串加载路由配置
460///
461/// ## YAML 格式
462///
463/// ```yaml
464/// routes:
465///   - method: GET
466///     path: /users
467///     handler: User@list
468///   - method: POST
469///     path: /users
470///     handler: User@create
471/// groups:
472///   - prefix: /api/v1
473///     middleware: [auth, log]
474///     routes:
475///       - method: GET
476///         path: /items
477///         handler: Item@list
478/// ```
479#[tracing::instrument]
480pub fn load_routes_from_yaml_str(yaml: &str) -> Result<RouteConfig, RouteConfigError> {
481    let config: RouteConfig = serde_yml::from_str(yaml)?;
482    Ok(config)
483}
484
485/// 从 JSON 字符串加载路由配置
486#[tracing::instrument]
487pub fn load_routes_from_json_str(json: &str) -> Result<RouteConfig, RouteConfigError> {
488    let config: RouteConfig = serde_json::from_str(json)?;
489    Ok(config)
490}
491
492/// 从 YAML 文件加载路由配置
493#[tracing::instrument(skip(path))]
494pub async fn load_routes_from_yaml_file(
495    path: impl AsRef<std::path::Path>,
496) -> Result<RouteConfig, RouteConfigError> {
497    let content = tokio::fs::read_to_string(path)
498        .await
499        .map_err(RouteConfigError::FileRead)?;
500    load_routes_from_yaml_str(&content)
501}
502
503/// 从 JSON 文件加载路由配置
504#[tracing::instrument(skip(path))]
505pub async fn load_routes_from_json_file(
506    path: impl AsRef<std::path::Path>,
507) -> Result<RouteConfig, RouteConfigError> {
508    let content = tokio::fs::read_to_string(path)
509        .await
510        .map_err(RouteConfigError::FileRead)?;
511    load_routes_from_json_str(&content)
512}
513
514// ============================================================================
515// Layer 1 - 属性宏路由契约(接口定义,实现见 controller_registry 模块)
516// ============================================================================
517
518/// 控制器路由契约
519///
520/// 由 `#[controller]` 属性宏自动实现,声明控制器内的所有路由。
521///
522/// ## 用法(属性宏实现后)
523///
524/// ```ignore
525/// use sz_rust_core::routing::ControllerRouter;
526///
527/// #[controller(prefix = "/users")]
528/// struct UserController;
529///
530/// impl UserController {
531///     #[get("/{id}")]
532///     fn show(&self, id: i64) -> String { ... }
533/// }
534///
535/// // ControllerRouter 由 #[controller] 自动实现
536/// let routes = UserController.router_rules();
537/// ```
538pub trait ControllerRouter {
539    /// 返回控制器内所有路由规则(已包含 prefix)
540    fn router_rules(&self) -> Vec<RouteRule>;
541
542    /// 控制器的路径前缀(如 `/users`)
543    fn router_prefix(&self) -> &str {
544        ""
545    }
546
547    /// 控制器级中间件(应用到控制器内所有路由)
548    fn router_middleware(&self) -> Vec<String> {
549        Vec::new()
550    }
551}
552
553// ============================================================================
554// Layer 3 - 约定式路由元数据
555// ============================================================================
556
557/// 约定式路由元数据
558///
559/// 基于 `parse_path` 的 `(app, controller, action)` 三元组生成。
560/// 实际路由注册需要 ControllerRegistry(在 controller_registry 模块实现)。
561#[derive(Debug, Clone, PartialEq, Eq)]
562pub struct ConventionRoute {
563    /// 应用名
564    pub app: String,
565    /// 控制器名(首字母大写)
566    pub controller: String,
567    /// 操作名(小驼峰)
568    pub action: String,
569    /// HTTP 方法(约定式默认 GET,POST 也常见)
570    pub method: HttpMethod,
571    /// 生成的路径(如 `/oapc/customer/index`)
572    pub path: String,
573}
574
575impl ConventionRoute {
576    /// 从 URI 生成约定式路由元数据
577    ///
578    /// ```ignore
579    /// use sz_rust_core::routing::ConventionRoute;
580    ///
581    /// let r = ConventionRoute::from_uri("/oapc/customer/index").unwrap();
582    /// assert_eq!(r.app, "oapc");
583    /// assert_eq!(r.controller, "Customer");
584    /// assert_eq!(r.action, "index");
585    /// assert_eq!(r.path, "/oapc/customer/index");
586    /// ```
587    pub fn from_uri(uri: &str) -> Option<Self> {
588        let parsed = crate::router::parse_path(uri);
589        // 默认应用和默认控制器+action 的情况不生成约定式路由
590        if parsed.app == crate::router::DEFAULT_APP
591            && parsed.controller == crate::router::DEFAULT_CONTROLLER
592            && parsed.action == crate::router::DEFAULT_ACTION
593        {
594            return None;
595        }
596        let path = format!(
597            "/{}/{}/{}",
598            parsed.app,
599            parsed.controller.to_lowercase(),
600            parsed.action
601        );
602        Some(Self {
603            app: parsed.app,
604            controller: parsed.controller,
605            action: parsed.action,
606            method: HttpMethod::GET,
607            path,
608        })
609    }
610
611    /// 从 ParsedPath 构造
612    pub fn from_parsed(parsed: ParsedPath) -> Option<Self> {
613        let uri = format!(
614            "/{}/{}/{}",
615            parsed.app,
616            parsed.controller.to_lowercase(),
617            parsed.action
618        );
619        Self::from_uri(&uri)
620    }
621}
622
623// ============================================================================
624// RouteRegistry - 三层路由汇总
625// ============================================================================
626
627/// 三层路由注册表
628///
629/// 收集三层路由的元数据,提供统一的查询和冲突检测接口。
630///
631/// ## 注意
632///
633/// 实际将路由注册到 `axum::Router` 需要 ControllerRegistry(在 controller_registry 模块实现)。
634/// 本模块仅提供元数据管理和冲突检测。
635#[derive(Debug, Clone, Default)]
636pub struct RouteRegistry {
637    /// Layer 1 - 属性宏路由
638    pub attribute_routes: Vec<RouteRule>,
639    /// Layer 2 - 配置式路由
640    pub config_routes: Vec<RouteRule>,
641    /// Layer 3 - 约定式路由
642    pub convention_routes: Vec<ConventionRoute>,
643}
644
645impl RouteRegistry {
646    /// 创建空注册表
647    pub fn new() -> Self {
648        Self::default()
649    }
650
651    /// 添加属性宏路由
652    pub fn add_attribute_route(&mut self, rule: RouteRule) -> &mut Self {
653        self.attribute_routes.push(rule);
654        self
655    }
656
657    /// 批量添加属性宏路由
658    pub fn add_attribute_routes(
659        &mut self,
660        rules: impl IntoIterator<Item = RouteRule>,
661    ) -> &mut Self {
662        self.attribute_routes.extend(rules);
663        self
664    }
665
666    /// 添加配置式路由
667    pub fn add_config_routes(&mut self, config: &RouteConfig) -> &mut Self {
668        self.config_routes.extend(config.flatten());
669        self
670    }
671
672    /// 添加约定式路由
673    pub fn add_convention_route(&mut self, route: ConventionRoute) -> &mut Self {
674        self.convention_routes.push(route);
675        self
676    }
677
678    /// 转换约定式路由为 RouteRule 列表
679    ///
680    /// 约定式路由的 handler 字段为 `Controller@action` 格式。
681    #[tracing::instrument(skip(self))]
682    pub fn convention_as_rules(&self) -> Vec<RouteRule> {
683        self.convention_routes
684            .iter()
685            .map(|c| RouteRule {
686                method: c.method.clone(),
687                path: c.path.clone(),
688                handler: format!("{}@{}", c.controller, c.action),
689                middleware: Vec::new(),
690                name: Some(format!(
691                    "convention.{}.{}.{}",
692                    c.app, c.controller, c.action
693                )),
694            })
695            .collect()
696    }
697
698    /// 合并所有三层的路由规则(按优先级:attribute > config > convention)
699    ///
700    /// 冲突时优先级高的覆盖低的,返回最终路由列表。
701    #[tracing::instrument(skip(self))]
702    pub fn merged_rules(&self) -> Vec<RouteRule> {
703        let mut seen: HashMap<(String, String), RouteRule> = HashMap::new();
704
705        // 优先级低 → 高(后插入覆盖前插入)
706        for rule in self.convention_as_rules() {
707            let key = (rule.method.to_string(), rule.path.clone());
708            seen.insert(key, rule);
709        }
710        for rule in &self.config_routes {
711            let key = (rule.method.to_string(), rule.path.clone());
712            seen.insert(key, rule.clone());
713        }
714        for rule in &self.attribute_routes {
715            let key = (rule.method.to_string(), rule.path.clone());
716            seen.insert(key, rule.clone());
717        }
718
719        seen.into_values().collect()
720    }
721
722    /// 检测属性宏层内部的冲突
723    pub fn attribute_conflicts(&self) -> Vec<(RouteRule, RouteRule)> {
724        find_conflicts_in(&self.attribute_routes)
725    }
726
727    /// 检测配置式层内部的冲突
728    pub fn config_conflicts(&self) -> Vec<(RouteRule, RouteRule)> {
729        find_conflicts_in(&self.config_routes)
730    }
731
732    /// 路由总数
733    pub fn total_count(&self) -> usize {
734        self.attribute_routes.len() + self.config_routes.len() + self.convention_routes.len()
735    }
736}
737
738/// 在路由规则列表中检测冲突
739fn find_conflicts_in(rules: &[RouteRule]) -> Vec<(RouteRule, RouteRule)> {
740    let mut seen: HashMap<(String, String), usize> = HashMap::new();
741    let mut conflicts = Vec::new();
742
743    for (i, rule) in rules.iter().enumerate() {
744        let key = (rule.method.to_string(), rule.path.clone());
745        if let Some(&prev_idx) = seen.get(&key) {
746            conflicts.push((rules[prev_idx].clone(), rules[i].clone()));
747        } else {
748            seen.insert(key, i);
749        }
750    }
751
752    conflicts
753}
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758
759    // ====================================================================
760    // HttpMethod
761    // ====================================================================
762
763    #[test]
764    fn test_http_method_parse_uppercase() {
765        assert_eq!(HttpMethod::parse("GET").unwrap(), HttpMethod::GET);
766        assert_eq!(HttpMethod::parse("POST").unwrap(), HttpMethod::POST);
767        assert_eq!(HttpMethod::parse("PUT").unwrap(), HttpMethod::PUT);
768        assert_eq!(HttpMethod::parse("DELETE").unwrap(), HttpMethod::DELETE);
769        assert_eq!(HttpMethod::parse("PATCH").unwrap(), HttpMethod::PATCH);
770        assert_eq!(HttpMethod::parse("OPTIONS").unwrap(), HttpMethod::OPTIONS);
771    }
772
773    #[test]
774    fn test_http_method_parse_lowercase() {
775        assert_eq!(HttpMethod::parse("get").unwrap(), HttpMethod::GET);
776        assert_eq!(HttpMethod::parse("post").unwrap(), HttpMethod::POST);
777    }
778
779    #[test]
780    fn test_http_method_parse_mixed_case() {
781        assert_eq!(HttpMethod::parse("Get").unwrap(), HttpMethod::GET);
782        assert_eq!(HttpMethod::parse("pOsT").unwrap(), HttpMethod::POST);
783    }
784
785    #[test]
786    fn test_http_method_parse_invalid() {
787        assert!(HttpMethod::parse("invalid").is_err());
788        assert!(HttpMethod::parse("").is_err());
789        assert!(HttpMethod::parse("CONNECT").is_err());
790        assert!(HttpMethod::parse("TRACE").is_err());
791    }
792
793    #[test]
794    fn test_http_method_to_axum() {
795        assert_eq!(HttpMethod::GET.to_axum_method(), axum::http::Method::GET);
796        assert_eq!(HttpMethod::POST.to_axum_method(), axum::http::Method::POST);
797        assert_eq!(HttpMethod::PUT.to_axum_method(), axum::http::Method::PUT);
798        assert_eq!(
799            HttpMethod::DELETE.to_axum_method(),
800            axum::http::Method::DELETE
801        );
802        assert_eq!(
803            HttpMethod::PATCH.to_axum_method(),
804            axum::http::Method::PATCH
805        );
806        assert_eq!(
807            HttpMethod::OPTIONS.to_axum_method(),
808            axum::http::Method::OPTIONS
809        );
810    }
811
812    #[test]
813    fn test_http_method_display() {
814        assert_eq!(HttpMethod::GET.to_string(), "GET");
815        assert_eq!(HttpMethod::POST.to_string(), "POST");
816        assert_eq!(HttpMethod::PUT.to_string(), "PUT");
817    }
818
819    #[test]
820    fn test_http_method_serde() {
821        let json = serde_json::to_string(&HttpMethod::GET).unwrap();
822        assert_eq!(json, "\"GET\"");
823
824        let m: HttpMethod = serde_json::from_str("\"POST\"").unwrap();
825        assert_eq!(m, HttpMethod::POST);
826    }
827
828    // ====================================================================
829    // HandlerRef
830    // ====================================================================
831
832    #[test]
833    fn test_handler_ref_parse_at_separator() {
834        let h = HandlerRef::parse("User@list").unwrap();
835        assert_eq!(h.controller, "User");
836        assert_eq!(h.action, "list");
837    }
838
839    #[test]
840    fn test_handler_ref_parse_slash_separator() {
841        let h = HandlerRef::parse("User/list").unwrap();
842        assert_eq!(h.controller, "User");
843        assert_eq!(h.action, "list");
844    }
845
846    #[test]
847    fn test_handler_ref_parse_only_controller() {
848        let h = HandlerRef::parse("User").unwrap();
849        assert_eq!(h.controller, "User");
850        assert_eq!(h.action, "index"); // 默认 action
851    }
852
853    #[test]
854    fn test_handler_ref_parse_with_whitespace() {
855        let h = HandlerRef::parse("  User  @  list  ").unwrap();
856        assert_eq!(h.controller, "User");
857        assert_eq!(h.action, "list");
858    }
859
860    #[test]
861    fn test_handler_ref_parse_empty() {
862        assert!(HandlerRef::parse("").is_err());
863        assert!(HandlerRef::parse("   ").is_err());
864    }
865
866    #[test]
867    fn test_handler_ref_parse_empty_controller() {
868        assert!(HandlerRef::parse("@list").is_err());
869        assert!(HandlerRef::parse("/list").is_err());
870    }
871
872    #[test]
873    fn test_handler_ref_parse_empty_action() {
874        assert!(HandlerRef::parse("User@").is_err());
875        assert!(HandlerRef::parse("User/").is_err());
876    }
877
878    #[test]
879    fn test_handler_ref_to_string() {
880        let h = HandlerRef {
881            controller: "User".to_string(),
882            action: "list".to_string(),
883        };
884        assert_eq!(h.to_string(), "User@list");
885    }
886
887    // ====================================================================
888    // S-1 回归测试:HandlerRef 注入字符校验
889    // ====================================================================
890
891    #[test]
892    fn test_handler_ref_parse_rejects_path_traversal() {
893        // `../Secret@admin` → controller="../Secret" → InvalidController
894        assert!(matches!(
895            HandlerRef::parse("../Secret@admin"),
896            Err(RouteConfigError::InvalidController(_))
897        ));
898        // `..@admin` → controller=".." → InvalidController
899        assert!(matches!(
900            HandlerRef::parse("..@admin"),
901            Err(RouteConfigError::InvalidController(_))
902        ));
903        // `User@../evil` → action="../evil" → InvalidAction
904        assert!(matches!(
905            HandlerRef::parse("User@../evil"),
906            Err(RouteConfigError::InvalidAction(_))
907        ));
908    }
909
910    #[test]
911    fn test_handler_ref_parse_rejects_double_at() {
912        // `User@list@extra` → split_once('@') 得 controller="User", action="list@extra"
913        // action 包含 '@' → InvalidAction
914        assert!(matches!(
915            HandlerRef::parse("User@list@extra"),
916            Err(RouteConfigError::InvalidAction(_))
917        ));
918    }
919
920    #[test]
921    fn test_handler_ref_parse_rejects_space_injection() {
922        // 内部空格不应被允许(trim 仅处理首尾)
923        assert!(matches!(
924            HandlerRef::parse("Us er@list"),
925            Err(RouteConfigError::InvalidController(_))
926        ));
927        assert!(matches!(
928            HandlerRef::parse("User@li st"),
929            Err(RouteConfigError::InvalidAction(_))
930        ));
931    }
932
933    #[test]
934    fn test_handler_ref_parse_rejects_leading_digit() {
935        // PHP 标识符首字符不能是数字
936        assert!(matches!(
937            HandlerRef::parse("1User@list"),
938            Err(RouteConfigError::InvalidController(_))
939        ));
940        assert!(matches!(
941            HandlerRef::parse("User@1list"),
942            Err(RouteConfigError::InvalidAction(_))
943        ));
944    }
945
946    #[test]
947    fn test_handler_ref_parse_accepts_underscore_and_alphanumeric() {
948        let h = HandlerRef::parse("_Private@_index").unwrap();
949        assert_eq!(h.controller, "_Private");
950        assert_eq!(h.action, "_index");
951
952        let h = HandlerRef::parse("User@action_1").unwrap();
953        assert_eq!(h.controller, "User");
954        assert_eq!(h.action, "action_1");
955
956        // CamelCase 也合法
957        let h = HandlerRef::parse("CustomerList@getListById").unwrap();
958        assert_eq!(h.controller, "CustomerList");
959        assert_eq!(h.action, "getListById");
960    }
961
962    #[test]
963    fn test_handler_ref_parse_rejects_special_chars() {
964        // 冒号、分号、反斜杠等都不允许
965        assert!(HandlerRef::parse("User:list@action").is_err());
966        assert!(HandlerRef::parse("User;list@action").is_err());
967        assert!(HandlerRef::parse(r"User\list@action").is_err());
968        assert!(HandlerRef::parse("User@act\nion").is_err());
969    }
970
971    // ====================================================================
972    // RouteRule
973    // ====================================================================
974
975    #[test]
976    fn test_route_rule_new() {
977        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list");
978        assert_eq!(rule.method, HttpMethod::GET);
979        assert_eq!(rule.path, "/users");
980        assert_eq!(rule.handler, "User@list");
981        assert!(rule.middleware.is_empty());
982        assert!(rule.name.is_none());
983    }
984
985    #[test]
986    fn test_route_rule_handler_ref() {
987        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list");
988        let h = rule.handler_ref().unwrap();
989        assert_eq!(h.controller, "User");
990        assert_eq!(h.action, "list");
991    }
992
993    #[test]
994    fn test_route_rule_with_middleware() {
995        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list")
996            .with_middleware("auth")
997            .with_middleware("log");
998        assert_eq!(rule.middleware, vec!["auth", "log"]);
999    }
1000
1001    #[test]
1002    fn test_route_rule_with_name() {
1003        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list").with_name("user.list");
1004        assert_eq!(rule.name, Some("user.list".to_string()));
1005    }
1006
1007    // ====================================================================
1008    // RouteGroup
1009    // ====================================================================
1010
1011    #[test]
1012    fn test_route_group_new() {
1013        let g = RouteGroup::new("/api/v1");
1014        assert_eq!(g.prefix, "/api/v1");
1015        assert!(g.routes.is_empty());
1016        assert!(g.middleware.is_empty());
1017    }
1018
1019    #[test]
1020    fn test_route_group_add_route() {
1021        let mut g = RouteGroup::new("/api");
1022        g.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1023        assert_eq!(g.routes.len(), 1);
1024    }
1025
1026    #[test]
1027    fn test_route_group_with_middleware() {
1028        let g = RouteGroup::new("/api")
1029            .with_middleware("auth")
1030            .with_middleware("log");
1031        assert_eq!(g.middleware, vec!["auth", "log"]);
1032    }
1033
1034    // ====================================================================
1035    // RouteConfig::flatten + join_path
1036    // ====================================================================
1037
1038    #[test]
1039    fn test_join_path_basic() {
1040        assert_eq!(join_path("/api", "/users"), "/api/users");
1041        assert_eq!(join_path("/api/", "/users"), "/api/users");
1042        assert_eq!(join_path("/api", "users"), "/api/users");
1043        assert_eq!(join_path("/api/", "users"), "/api/users");
1044    }
1045
1046    #[test]
1047    fn test_join_path_empty_prefix() {
1048        assert_eq!(join_path("", "/users"), "/users");
1049        assert_eq!(join_path("", "users"), "/users");
1050    }
1051
1052    #[test]
1053    fn test_join_path_empty_path() {
1054        assert_eq!(join_path("/api", ""), "/api");
1055        assert_eq!(join_path("/api/", ""), "/api");
1056    }
1057
1058    #[test]
1059    fn test_join_path_both_empty() {
1060        assert_eq!(join_path("", ""), "");
1061    }
1062
1063    #[test]
1064    fn test_route_config_flatten_no_groups() {
1065        let mut config = RouteConfig::new();
1066        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1067        config.add_route(RouteRule::new(HttpMethod::POST, "/users", "User@create"));
1068
1069        let flat = config.flatten();
1070        assert_eq!(flat.len(), 2);
1071        assert_eq!(flat[0].path, "/users");
1072        assert_eq!(flat[1].path, "/users");
1073    }
1074
1075    #[test]
1076    fn test_route_config_flatten_with_group() {
1077        let mut config = RouteConfig::new();
1078        let mut group = RouteGroup::new("/api/v1");
1079        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1080        group.add_route(RouteRule::new(HttpMethod::POST, "/items", "Item@create"));
1081        config.add_group(group);
1082
1083        let flat = config.flatten();
1084        assert_eq!(flat.len(), 2);
1085        assert_eq!(flat[0].path, "/api/v1/items");
1086        assert_eq!(flat[1].path, "/api/v1/items");
1087    }
1088
1089    #[test]
1090    fn test_route_config_flatten_group_middleware_prepended() {
1091        let mut config = RouteConfig::new();
1092        let mut group = RouteGroup::new("/api");
1093        group.middleware = vec!["auth".to_string(), "log".to_string()];
1094        let mut rule = RouteRule::new(HttpMethod::GET, "/items", "Item@list");
1095        rule.middleware = vec!["cache".to_string()];
1096        group.routes.push(rule);
1097        config.add_group(group);
1098
1099        let flat = config.flatten();
1100        assert_eq!(flat[0].middleware, vec!["auth", "log", "cache"]);
1101    }
1102
1103    #[test]
1104    fn test_route_config_flatten_mixed() {
1105        let mut config = RouteConfig::new();
1106        config.add_route(RouteRule::new(HttpMethod::GET, "/health", "Health@check"));
1107        let mut group = RouteGroup::new("/api");
1108        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1109        config.add_group(group);
1110
1111        let flat = config.flatten();
1112        assert_eq!(flat.len(), 2);
1113        assert!(flat.iter().any(|r| r.path == "/health"));
1114        assert!(flat.iter().any(|r| r.path == "/api/items"));
1115    }
1116
1117    // ====================================================================
1118    // RouteConfig::find_conflicts
1119    // ====================================================================
1120
1121    #[test]
1122    fn test_route_config_no_conflicts() {
1123        let mut config = RouteConfig::new();
1124        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1125        config.add_route(RouteRule::new(HttpMethod::POST, "/users", "User@create"));
1126        assert!(config.find_conflicts().is_empty());
1127    }
1128
1129    #[test]
1130    fn test_route_config_conflict_same_method_path() {
1131        let mut config = RouteConfig::new();
1132        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1133        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@all"));
1134
1135        let conflicts = config.find_conflicts();
1136        assert_eq!(conflicts.len(), 1);
1137        let (a, b) = &conflicts[0];
1138        assert_eq!(a.handler, "User@list");
1139        assert_eq!(b.handler, "User@all");
1140    }
1141
1142    #[test]
1143    fn test_route_config_no_conflict_different_method() {
1144        let mut config = RouteConfig::new();
1145        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1146        config.add_route(RouteRule::new(HttpMethod::DELETE, "/users", "User@delete"));
1147        assert!(config.find_conflicts().is_empty());
1148    }
1149
1150    #[test]
1151    fn test_route_config_conflict_in_group() {
1152        let mut config = RouteConfig::new();
1153        let mut group = RouteGroup::new("/api");
1154        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1155        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@all"));
1156        config.add_group(group);
1157
1158        let conflicts = config.find_conflicts();
1159        assert_eq!(conflicts.len(), 1);
1160    }
1161
1162    #[test]
1163    fn test_route_config_conflict_between_top_and_group() {
1164        let mut config = RouteConfig::new();
1165        // 顶层 /api/items
1166        config.add_route(RouteRule::new(HttpMethod::GET, "/api/items", "Item@list"));
1167        // group prefix /api + /items = /api/items
1168        let mut group = RouteGroup::new("/api");
1169        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@all"));
1170        config.add_group(group);
1171
1172        let conflicts = config.find_conflicts();
1173        assert_eq!(conflicts.len(), 1);
1174    }
1175
1176    // ====================================================================
1177    // YAML 加载
1178    // ====================================================================
1179
1180    #[test]
1181    fn test_load_routes_from_yaml_str_simple() {
1182        let yaml = r#"
1183routes:
1184  - method: GET
1185    path: /users
1186    handler: User@list
1187  - method: POST
1188    path: /users
1189    handler: User@create
1190"#;
1191        let config = load_routes_from_yaml_str(yaml).unwrap();
1192        assert_eq!(config.routes.len(), 2);
1193        assert_eq!(config.routes[0].method, HttpMethod::GET);
1194        assert_eq!(config.routes[0].path, "/users");
1195        assert_eq!(config.routes[0].handler, "User@list");
1196        assert_eq!(config.routes[1].method, HttpMethod::POST);
1197    }
1198
1199    #[test]
1200    fn test_load_routes_from_yaml_str_with_groups() {
1201        let yaml = r#"
1202routes:
1203  - method: GET
1204    path: /health
1205    handler: Health@check
1206groups:
1207  - prefix: /api/v1
1208    middleware: [auth, log]
1209    routes:
1210      - method: GET
1211        path: /items
1212        handler: Item@list
1213      - method: POST
1214        path: /items
1215        handler: Item@create
1216"#;
1217        let config = load_routes_from_yaml_str(yaml).unwrap();
1218        assert_eq!(config.routes.len(), 1);
1219        assert_eq!(config.groups.len(), 1);
1220        assert_eq!(config.groups[0].prefix, "/api/v1");
1221        assert_eq!(config.groups[0].middleware, vec!["auth", "log"]);
1222        assert_eq!(config.groups[0].routes.len(), 2);
1223
1224        let flat = config.flatten();
1225        assert_eq!(flat.len(), 3);
1226        assert!(flat.iter().any(|r| r.path == "/health"));
1227        assert!(flat.iter().any(|r| r.path == "/api/v1/items"));
1228    }
1229
1230    #[test]
1231    fn test_load_routes_from_yaml_str_with_name_and_middleware() {
1232        let yaml = r#"
1233routes:
1234  - method: GET
1235    path: /users/{id}
1236    handler: User@show
1237    middleware: [auth, cache]
1238    name: user.show
1239"#;
1240        let config = load_routes_from_yaml_str(yaml).unwrap();
1241        assert_eq!(config.routes.len(), 1);
1242        let rule = &config.routes[0];
1243        assert_eq!(rule.middleware, vec!["auth", "cache"]);
1244        assert_eq!(rule.name, Some("user.show".to_string()));
1245    }
1246
1247    #[test]
1248    fn test_load_routes_from_yaml_str_empty() {
1249        let yaml = "";
1250        let config = load_routes_from_yaml_str(yaml).unwrap();
1251        assert_eq!(config.routes.len(), 0);
1252        assert_eq!(config.groups.len(), 0);
1253    }
1254
1255    #[test]
1256    fn test_load_routes_from_yaml_str_invalid_method() {
1257        let yaml = r#"
1258routes:
1259  - method: INVALID
1260    path: /users
1261    handler: User@list
1262"#;
1263        let result = load_routes_from_yaml_str(yaml);
1264        // serde_yml 会因为 INVALID 无法反序列化为 HttpMethod 而失败
1265        assert!(result.is_err());
1266    }
1267
1268    #[test]
1269    fn test_load_routes_from_yaml_str_invalid_yaml() {
1270        let yaml = "not: valid: yaml: at: all";
1271        let result = load_routes_from_yaml_str(yaml);
1272        assert!(result.is_err());
1273    }
1274
1275    // ====================================================================
1276    // JSON 加载
1277    // ====================================================================
1278
1279    #[test]
1280    fn test_load_routes_from_json_str_simple() {
1281        let json = r#"{
1282  "routes": [
1283    {"method": "GET", "path": "/users", "handler": "User@list"},
1284    {"method": "POST", "path": "/users", "handler": "User@create"}
1285  ]
1286}"#;
1287        let config = load_routes_from_json_str(json).unwrap();
1288        assert_eq!(config.routes.len(), 2);
1289        assert_eq!(config.routes[0].method, HttpMethod::GET);
1290        assert_eq!(config.routes[1].method, HttpMethod::POST);
1291    }
1292
1293    #[test]
1294    fn test_load_routes_from_json_str_with_groups() {
1295        let json = r#"{
1296  "routes": [
1297    {"method": "GET", "path": "/health", "handler": "Health@check"}
1298  ],
1299  "groups": [
1300    {
1301      "prefix": "/api",
1302      "middleware": ["auth"],
1303      "routes": [
1304        {"method": "GET", "path": "/items", "handler": "Item@list"}
1305      ]
1306    }
1307  ]
1308}"#;
1309        let config = load_routes_from_json_str(json).unwrap();
1310        assert_eq!(config.routes.len(), 1);
1311        assert_eq!(config.groups.len(), 1);
1312        assert_eq!(config.groups[0].prefix, "/api");
1313    }
1314
1315    #[test]
1316    fn test_load_routes_from_json_str_empty() {
1317        let json = "{}";
1318        let config = load_routes_from_json_str(json).unwrap();
1319        assert_eq!(config.routes.len(), 0);
1320        assert_eq!(config.groups.len(), 0);
1321    }
1322
1323    #[test]
1324    fn test_load_routes_from_json_str_invalid() {
1325        let json = "{not valid json";
1326        let result = load_routes_from_json_str(json);
1327        assert!(result.is_err());
1328    }
1329
1330    // ====================================================================
1331    // ConventionRoute
1332    // ====================================================================
1333
1334    #[test]
1335    fn test_convention_route_from_uri_with_app() {
1336        let r = ConventionRoute::from_uri("/oapc/customer/index").unwrap();
1337        assert_eq!(r.app, "oapc");
1338        assert_eq!(r.controller, "Customer");
1339        assert_eq!(r.action, "index");
1340        assert_eq!(r.path, "/oapc/customer/index");
1341        assert_eq!(r.method, HttpMethod::GET);
1342    }
1343
1344    #[test]
1345    fn test_convention_route_from_uri_admin_app() {
1346        let r = ConventionRoute::from_uri("/admin/login/index").unwrap();
1347        assert_eq!(r.app, "admin");
1348        assert_eq!(r.controller, "Login");
1349        assert_eq!(r.action, "index");
1350    }
1351
1352    #[test]
1353    fn test_convention_route_from_uri_root_returns_none() {
1354        // 根路径 → 默认应用+控制器+action → 不生成约定式路由
1355        assert!(ConventionRoute::from_uri("/").is_none());
1356        assert!(ConventionRoute::from_uri("").is_none());
1357    }
1358
1359    #[test]
1360    fn test_convention_route_from_uri_single_segment() {
1361        // /foo → (index, Foo, index)
1362        // 不在 app_map,所以 app=index,但 controller=Foo,action=index
1363        // 这个 case 会生成约定式路由吗?看实现:app=index, controller=Foo, action=index
1364        // 由于 action=index 是默认值,但 controller=Foo 不是默认值,所以应生成
1365        let r = ConventionRoute::from_uri("/customer").unwrap();
1366        assert_eq!(r.app, "index");
1367        assert_eq!(r.controller, "Customer");
1368        assert_eq!(r.action, "index");
1369    }
1370
1371    #[test]
1372    fn test_convention_route_from_parsed() {
1373        let parsed = ParsedPath::new("api", "User", "list");
1374        let r = ConventionRoute::from_parsed(parsed).unwrap();
1375        assert_eq!(r.app, "api");
1376        assert_eq!(r.controller, "User");
1377        assert_eq!(r.action, "list");
1378    }
1379
1380    // ====================================================================
1381    // RouteRegistry
1382    // ====================================================================
1383
1384    #[test]
1385    fn test_route_registry_new() {
1386        let r = RouteRegistry::new();
1387        assert!(r.attribute_routes.is_empty());
1388        assert!(r.config_routes.is_empty());
1389        assert!(r.convention_routes.is_empty());
1390        assert_eq!(r.total_count(), 0);
1391    }
1392
1393    #[test]
1394    fn test_route_registry_add_attribute_route() {
1395        let mut r = RouteRegistry::new();
1396        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1397        assert_eq!(r.attribute_routes.len(), 1);
1398        assert_eq!(r.total_count(), 1);
1399    }
1400
1401    #[test]
1402    fn test_route_registry_add_attribute_routes_batch() {
1403        let mut r = RouteRegistry::new();
1404        r.add_attribute_routes(vec![
1405            RouteRule::new(HttpMethod::GET, "/users", "User@list"),
1406            RouteRule::new(HttpMethod::POST, "/users", "User@create"),
1407        ]);
1408        assert_eq!(r.attribute_routes.len(), 2);
1409    }
1410
1411    #[test]
1412    fn test_route_registry_add_config_routes() {
1413        let mut r = RouteRegistry::new();
1414        let mut config = RouteConfig::new();
1415        config.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1416        config.add_route(RouteRule::new(HttpMethod::POST, "/items", "Item@create"));
1417        r.add_config_routes(&config);
1418        assert_eq!(r.config_routes.len(), 2);
1419    }
1420
1421    #[test]
1422    fn test_route_registry_add_convention_route() {
1423        let mut r = RouteRegistry::new();
1424        let cr = ConventionRoute::from_uri("/oapc/customer/index").unwrap();
1425        r.add_convention_route(cr);
1426        assert_eq!(r.convention_routes.len(), 1);
1427    }
1428
1429    #[test]
1430    fn test_route_registry_convention_as_rules() {
1431        let mut r = RouteRegistry::new();
1432        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1433        r.add_convention_route(ConventionRoute::from_uri("/admin/login/index").unwrap());
1434
1435        let rules = r.convention_as_rules();
1436        assert_eq!(rules.len(), 2);
1437        assert_eq!(rules[0].handler, "Customer@index");
1438        assert_eq!(rules[1].handler, "Login@index");
1439        assert_eq!(
1440            rules[0].name,
1441            Some("convention.oapc.Customer.index".to_string())
1442        );
1443    }
1444
1445    #[test]
1446    fn test_route_registry_merged_rules_attribute_overrides_config() {
1447        let mut r = RouteRegistry::new();
1448        // Layer 2 - config
1449        r.add_config_routes(&RouteConfig {
1450            routes: vec![RouteRule::new(HttpMethod::GET, "/users", "User@old")],
1451            groups: vec![],
1452        });
1453        // Layer 1 - attribute (优先级更高,覆盖 config)
1454        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@new"));
1455
1456        let merged = r.merged_rules();
1457        assert_eq!(merged.len(), 1);
1458        assert_eq!(merged[0].handler, "User@new");
1459    }
1460
1461    #[test]
1462    fn test_route_registry_merged_rules_config_overrides_convention() {
1463        let mut r = RouteRegistry::new();
1464        // Layer 3 - convention
1465        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1466        // Layer 2 - config (优先级更高,覆盖 convention)
1467        r.add_config_routes(&RouteConfig {
1468            routes: vec![RouteRule::new(
1469                HttpMethod::GET,
1470                "/oapc/customer/index",
1471                "Customer@custom",
1472            )],
1473            groups: vec![],
1474        });
1475
1476        let merged = r.merged_rules();
1477        assert_eq!(merged.len(), 1);
1478        assert_eq!(merged[0].handler, "Customer@custom");
1479    }
1480
1481    #[test]
1482    fn test_route_registry_merged_rules_different_paths_no_override() {
1483        let mut r = RouteRegistry::new();
1484        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1485        r.add_config_routes(&RouteConfig {
1486            routes: vec![RouteRule::new(HttpMethod::GET, "/items", "Item@list")],
1487            groups: vec![],
1488        });
1489        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1490
1491        let merged = r.merged_rules();
1492        assert_eq!(merged.len(), 3);
1493    }
1494
1495    #[test]
1496    fn test_route_registry_attribute_conflicts() {
1497        let mut r = RouteRegistry::new();
1498        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1499        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@all"));
1500
1501        let conflicts = r.attribute_conflicts();
1502        assert_eq!(conflicts.len(), 1);
1503    }
1504
1505    #[test]
1506    fn test_route_registry_config_conflicts() {
1507        let mut r = RouteRegistry::new();
1508        r.add_config_routes(&RouteConfig {
1509            routes: vec![
1510                RouteRule::new(HttpMethod::GET, "/users", "User@list"),
1511                RouteRule::new(HttpMethod::GET, "/users", "User@all"),
1512            ],
1513            groups: vec![],
1514        });
1515
1516        let conflicts = r.config_conflicts();
1517        assert_eq!(conflicts.len(), 1);
1518    }
1519
1520    #[test]
1521    fn test_route_registry_no_conflicts() {
1522        let mut r = RouteRegistry::new();
1523        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1524        r.add_attribute_route(RouteRule::new(HttpMethod::POST, "/users", "User@create"));
1525
1526        assert!(r.attribute_conflicts().is_empty());
1527    }
1528
1529    #[test]
1530    fn test_route_registry_total_count() {
1531        let mut r = RouteRegistry::new();
1532        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/a", "A@index"));
1533        r.add_config_routes(&RouteConfig {
1534            routes: vec![RouteRule::new(HttpMethod::GET, "/b", "B@index")],
1535            groups: vec![],
1536        });
1537        r.add_convention_route(ConventionRoute::from_uri("/oapc/c/d").unwrap());
1538
1539        assert_eq!(r.total_count(), 3);
1540    }
1541
1542    // ====================================================================
1543    // 集成测试 - 完整三层路由流程
1544    // ====================================================================
1545
1546    #[test]
1547    fn test_integration_three_layer_routing() {
1548        // Layer 1 - 属性宏路由
1549        let mut r = RouteRegistry::new();
1550        r.add_attribute_routes(vec![
1551            RouteRule::new(HttpMethod::GET, "/users", "User@list"),
1552            RouteRule::new(HttpMethod::POST, "/users", "User@create"),
1553            RouteRule::new(HttpMethod::GET, "/users/{id}", "User@show"),
1554        ]);
1555
1556        // Layer 2 - 配置式路由
1557        let yaml = r#"
1558routes:
1559  - method: GET
1560    path: /items
1561    handler: Item@list
1562  - method: POST
1563    path: /items
1564    handler: Item@create
1565groups:
1566  - prefix: /api/v1
1567    middleware: [auth]
1568    routes:
1569      - method: GET
1570        path: /orders
1571        handler: Order@list
1572"#;
1573        let config = load_routes_from_yaml_str(yaml).unwrap();
1574        r.add_config_routes(&config);
1575
1576        // Layer 3 - 约定式路由
1577        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1578        r.add_convention_route(ConventionRoute::from_uri("/admin/login/index").unwrap());
1579
1580        // 验证总数
1581        assert_eq!(r.attribute_routes.len(), 3);
1582        assert_eq!(r.config_routes.len(), 3); // 2 顶层 + 1 group
1583        assert_eq!(r.convention_routes.len(), 2);
1584        assert_eq!(r.total_count(), 8);
1585
1586        // 合并后应无冲突(各层路径不重复)
1587        let merged = r.merged_rules();
1588        assert_eq!(merged.len(), 8);
1589
1590        // 验证各层无内部冲突
1591        assert!(r.attribute_conflicts().is_empty());
1592        assert!(r.config_conflicts().is_empty());
1593    }
1594
1595    #[test]
1596    fn test_integration_layer_override_priority() {
1597        // 三层都注册同一路径,attribute 应覆盖
1598        let mut r = RouteRegistry::new();
1599
1600        // Layer 3 - convention
1601        r.add_convention_route(ConventionRoute {
1602            app: "index".to_string(),
1603            controller: "User".to_string(),
1604            action: "list".to_string(),
1605            method: HttpMethod::GET,
1606            path: "/users".to_string(),
1607        });
1608
1609        // Layer 2 - config
1610        r.add_config_routes(&RouteConfig {
1611            routes: vec![RouteRule::new(HttpMethod::GET, "/users", "User@config")],
1612            groups: vec![],
1613        });
1614
1615        // Layer 1 - attribute (优先级最高)
1616        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@attribute"));
1617
1618        let merged = r.merged_rules();
1619        assert_eq!(merged.len(), 1);
1620        assert_eq!(merged[0].handler, "User@attribute");
1621    }
1622}