Skip to main content

sz_rust_router_facade/
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_router_facade::routing::{RouteConfig, RouteRule, HttpMethod, load_routes_from_yaml_str};
26//! use sz_rust_router_facade::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_router_facade::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_router_facade::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_router_facade::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// HandlerRefRef — 零拷贝借用版本(P3 优化)
230// ============================================================================
231
232/// Handler 引用(零拷贝借用版本)
233///
234/// 与 [`HandlerRef`] 语义完全一致,但使用 `&'a str` 切片而非 `String`,
235/// 解析时零堆分配。适用于热路径(路由匹配)中临时解析 handler 字符串。
236///
237/// ## 用法
238///
239/// ```rust,ignore
240/// use sz_rust_router_facade::routing::HandlerRefRef;
241///
242/// let h = HandlerRefRef::parse("User@list").unwrap();
243/// assert_eq!(h.controller, "User");
244/// assert_eq!(h.action, "list");
245///
246/// // 转为 owned 版本(1 次分配)
247/// let owned: HandlerRef = h.to_owned();
248/// ```
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub struct HandlerRefRef<'a> {
251    /// 控制器名(首字母大写,如 `User`)
252    pub controller: &'a str,
253    /// 操作名(小驼峰,如 `list` / `index`)
254    pub action: &'a str,
255}
256
257impl<'a> HandlerRefRef<'a> {
258    /// 从 `"Controller@action"` 或 `"Controller/action"` 字符串解析(零分配)
259    ///
260    /// 使用 `split_once` 返回 `&str` 切片,不调用 `to_string()`。
261    /// 校验规则与 [`HandlerRef::parse`] 完全一致。
262    pub fn parse(s: &'a str) -> Result<Self, RouteConfigError> {
263        let s = s.trim();
264        if s.is_empty() {
265            return Err(RouteConfigError::EmptyHandler);
266        }
267
268        // 优先按 '@' 分隔,其次按 '/' 分隔
269        let (controller, action) = if let Some((c, a)) = s.split_once('@') {
270            (c, a)
271        } else if let Some((c, a)) = s.split_once('/') {
272            (c, a)
273        } else {
274            (s, crate::router::DEFAULT_ACTION)
275        };
276
277        let controller = controller.trim();
278        let action = action.trim();
279
280        if controller.is_empty() {
281            return Err(RouteConfigError::EmptyController);
282        }
283        if action.is_empty() {
284            return Err(RouteConfigError::EmptyAction);
285        }
286        if !is_valid_identifier(controller) {
287            return Err(RouteConfigError::InvalidController(controller.to_string()));
288        }
289        if !is_valid_identifier(action) {
290            return Err(RouteConfigError::InvalidAction(action.to_string()));
291        }
292
293        Ok(Self { controller, action })
294    }
295
296    /// 转为 owned 版本(1 次分配)
297    pub fn to_owned(&self) -> HandlerRef {
298        HandlerRef {
299            controller: self.controller.to_string(),
300            action: self.action.to_string(),
301        }
302    }
303
304    /// 转换为 `"Controller@action"` 字符串
305    pub fn to_handler_string(&self) -> String {
306        format!("{}@{}", self.controller, self.action)
307    }
308}
309
310impl<'a> From<HandlerRefRef<'a>> for HandlerRef {
311    fn from(h: HandlerRefRef<'a>) -> Self {
312        h.to_owned()
313    }
314}
315
316impl<'a> std::fmt::Display for HandlerRefRef<'a> {
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        write!(f, "{}@{}", self.controller, self.action)
319    }
320}
321
322// ============================================================================
323// Layer 2 - 配置式路由
324// ============================================================================
325
326/// 路由配置错误
327#[derive(Debug, thiserror::Error)]
328pub enum RouteConfigError {
329    /// YAML 解析失败
330    #[error("YAML parse error: {0}")]
331    YamlParse(#[from] serde_yaml::Error),
332
333    /// JSON 解析失败
334    #[error("JSON parse error: {0}")]
335    JsonParse(#[from] serde_json::Error),
336
337    /// 无效的 HTTP 方法
338    #[error("invalid HTTP method: {0}")]
339    InvalidMethod(String),
340
341    /// 空的 handler 字符串
342    #[error("empty handler string")]
343    EmptyHandler,
344
345    /// 空的控制器名
346    #[error("empty controller name in handler")]
347    EmptyController,
348
349    /// 空的 action 名
350    #[error("empty action name in handler")]
351    EmptyAction,
352
353    /// 无效的控制器名(包含非 `[A-Za-z0-9_]` 字符或首字符不是字母/下划线)
354    #[error("invalid controller name: {0}")]
355    InvalidController(String),
356
357    /// 无效的 action 名(包含非 `[A-Za-z0-9_]` 字符或首字符不是字母/下划线)
358    #[error("invalid action name: {0}")]
359    InvalidAction(String),
360
361    /// handler 解析失败
362    #[error("handler parse error: {0}")]
363    HandlerParse(String),
364
365    /// 路由冲突(同 method+path 重复)
366    #[error("route conflict: {method} {path} already registered")]
367    Conflict {
368        /// 冲突的 HTTP 方法
369        method: String,
370        /// 冲突的路径
371        path: String,
372    },
373
374    /// 文件读取失败
375    #[error("failed to read route config file: {0}")]
376    FileRead(#[source] std::io::Error),
377}
378
379/// 单条路由规则
380///
381/// 对齐 PHP `think\Route::rule($rule, $route, $method)` 的单条规则结构。
382#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
383pub struct RouteRule {
384    /// HTTP 方法
385    pub method: HttpMethod,
386    /// 路径模板,如 `/users/{id}`(对齐 axum 0.8 路由语法)
387    pub path: String,
388    /// Handler 引用,如 `User@list`
389    pub handler: String,
390    /// 中间件列表(按名称引用,运行时通过 middleware registry 解析)
391    #[serde(default)]
392    pub middleware: Vec<String>,
393    /// 路由名称(可选,用于反向 URL 生成)
394    #[serde(default)]
395    pub name: Option<String>,
396}
397
398impl RouteRule {
399    /// 创建新路由规则
400    pub fn new(method: HttpMethod, path: impl Into<String>, handler: impl Into<String>) -> Self {
401        Self {
402            method,
403            path: path.into(),
404            handler: handler.into(),
405            middleware: Vec::new(),
406            name: None,
407        }
408    }
409
410    /// 解析 handler 字符串为 [`HandlerRef`]
411    pub fn handler_ref(&self) -> Result<HandlerRef, RouteConfigError> {
412        HandlerRef::parse(&self.handler)
413    }
414
415    /// 添加中间件
416    pub fn with_middleware(mut self, name: impl Into<String>) -> Self {
417        self.middleware.push(name.into());
418        self
419    }
420
421    /// 设置路由名称
422    pub fn with_name(mut self, name: impl Into<String>) -> Self {
423        self.name = Some(name.into());
424        self
425    }
426}
427
428/// 路由配置文件
429///
430/// 对齐 PHP `config/route.php` 的整体配置结构。
431#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
432pub struct RouteConfig {
433    /// 路由规则列表
434    #[serde(default)]
435    pub routes: Vec<RouteRule>,
436    /// 路由分组(每组有自己的前缀和中间件)
437    #[serde(default)]
438    pub groups: Vec<RouteGroup>,
439}
440
441impl RouteConfig {
442    /// 创建空配置
443    pub fn new() -> Self {
444        Self::default()
445    }
446
447    /// 添加单条路由
448    pub fn add_route(&mut self, rule: RouteRule) {
449        self.routes.push(rule);
450    }
451
452    /// 添加路由分组
453    pub fn add_group(&mut self, group: RouteGroup) {
454        self.groups.push(group);
455    }
456
457    /// 展开所有 group,返回扁平化后的路由列表
458    ///
459    /// group 内路由的最终 path = `{group.prefix}/{rule.path}`(去重 `/`)
460    /// group 内路由继承 group 的中间件(追加在 rule.middleware 之前)
461    pub fn flatten(&self) -> Vec<RouteRule> {
462        let mut result = self.routes.clone();
463        for group in &self.groups {
464            for rule in &group.routes {
465                let mut flattened = rule.clone();
466                flattened.path = join_path(&group.prefix, &flattened.path);
467                // group 中间件前置
468                let mut mw = group.middleware.clone();
469                mw.extend(flattened.middleware);
470                flattened.middleware = mw;
471                result.push(flattened);
472            }
473        }
474        result
475    }
476
477    /// 检查路由冲突(同 method+path 重复)
478    ///
479    /// 返回冲突列表(空表示无冲突)
480    pub fn find_conflicts(&self) -> Vec<(RouteRule, RouteRule)> {
481        let flattened = self.flatten();
482        let mut seen: HashMap<(String, String), usize> = HashMap::new();
483        let mut conflicts = Vec::new();
484
485        for (i, rule) in flattened.iter().enumerate() {
486            let key = (rule.method.to_string(), rule.path.clone());
487            if let Some(&prev_idx) = seen.get(&key) {
488                conflicts.push((flattened[prev_idx].clone(), flattened[i].clone()));
489            } else {
490                seen.insert(key, i);
491            }
492        }
493
494        conflicts
495    }
496}
497
498/// 路由分组
499///
500/// 对齐 PHP `Route::group($prefix, $callback)`,给组内所有路由添加统一前缀和中间件。
501#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
502pub struct RouteGroup {
503    /// 路径前缀,如 `/api/v1`
504    pub prefix: String,
505    /// 组内路由规则
506    #[serde(default)]
507    pub routes: Vec<RouteRule>,
508    /// 组级中间件(应用到组内所有路由)
509    #[serde(default)]
510    pub middleware: Vec<String>,
511}
512
513impl RouteGroup {
514    /// 创建新路由分组
515    pub fn new(prefix: impl Into<String>) -> Self {
516        Self {
517            prefix: prefix.into(),
518            routes: Vec::new(),
519            middleware: Vec::new(),
520        }
521    }
522
523    /// 添加路由
524    pub fn add_route(&mut self, rule: RouteRule) -> &mut Self {
525        self.routes.push(rule);
526        self
527    }
528
529    /// 添加中间件
530    pub fn with_middleware(mut self, name: impl Into<String>) -> Self {
531        self.middleware.push(name.into());
532        self
533    }
534}
535
536/// 拼接两个路径片段,正确处理 `/` 分隔
537fn join_path(prefix: &str, path: &str) -> String {
538    let prefix = prefix.trim_end_matches('/');
539    let path = path.trim_start_matches('/');
540    if path.is_empty() {
541        prefix.to_string()
542    } else if prefix.is_empty() {
543        format!("/{path}")
544    } else {
545        format!("{prefix}/{path}")
546    }
547}
548
549// ============================================================================
550// Layer 2 - 配置加载函数
551// ============================================================================
552
553/// 从 YAML 字符串加载路由配置
554///
555/// ## YAML 格式
556///
557/// ```yaml
558/// routes:
559///   - method: GET
560///     path: /users
561///     handler: User@list
562///   - method: POST
563///     path: /users
564///     handler: User@create
565/// groups:
566///   - prefix: /api/v1
567///     middleware: [auth, log]
568///     routes:
569///       - method: GET
570///         path: /items
571///         handler: Item@list
572/// ```
573#[tracing::instrument]
574pub fn load_routes_from_yaml_str(yaml: &str) -> Result<RouteConfig, RouteConfigError> {
575    let config: RouteConfig = serde_yaml::from_str(yaml)?;
576    Ok(config)
577}
578
579/// 从 JSON 字符串加载路由配置
580#[tracing::instrument]
581pub fn load_routes_from_json_str(json: &str) -> Result<RouteConfig, RouteConfigError> {
582    let config: RouteConfig = serde_json::from_str(json)?;
583    Ok(config)
584}
585
586/// 从 YAML 文件加载路由配置
587#[tracing::instrument(skip(path))]
588pub async fn load_routes_from_yaml_file(
589    path: impl AsRef<std::path::Path>,
590) -> Result<RouteConfig, RouteConfigError> {
591    let content = tokio::fs::read_to_string(path)
592        .await
593        .map_err(RouteConfigError::FileRead)?;
594    load_routes_from_yaml_str(&content)
595}
596
597/// 从 JSON 文件加载路由配置
598#[tracing::instrument(skip(path))]
599pub async fn load_routes_from_json_file(
600    path: impl AsRef<std::path::Path>,
601) -> Result<RouteConfig, RouteConfigError> {
602    let content = tokio::fs::read_to_string(path)
603        .await
604        .map_err(RouteConfigError::FileRead)?;
605    load_routes_from_json_str(&content)
606}
607
608// ============================================================================
609// Layer 1 - 属性宏路由契约(接口定义,实现见 controller_registry 模块)
610// ============================================================================
611
612/// 控制器路由契约
613///
614/// 由 `#[controller]` 属性宏自动实现,声明控制器内的所有路由。
615///
616/// ## 用法(属性宏实现后)
617///
618/// ```ignore
619/// use sz_rust_router_facade::routing::ControllerRouter;
620///
621/// #[controller(prefix = "/users")]
622/// struct UserController;
623///
624/// impl UserController {
625///     #[get("/{id}")]
626///     fn show(&self, id: i64) -> String { ... }
627/// }
628///
629/// // ControllerRouter 由 #[controller] 自动实现
630/// let routes = UserController.router_rules();
631/// ```
632pub trait ControllerRouter {
633    /// 返回控制器内所有路由规则(已包含 prefix)
634    fn router_rules(&self) -> Vec<RouteRule>;
635
636    /// 控制器的路径前缀(如 `/users`)
637    fn router_prefix(&self) -> &str {
638        ""
639    }
640
641    /// 控制器级中间件(应用到控制器内所有路由)
642    fn router_middleware(&self) -> Vec<String> {
643        Vec::new()
644    }
645}
646
647// ============================================================================
648// Layer 3 - 约定式路由元数据
649// ============================================================================
650
651/// 约定式路由元数据
652///
653/// 基于 `parse_path` 的 `(app, controller, action)` 三元组生成。
654/// 实际路由注册需要 ControllerRegistry(在 controller_registry 模块实现)。
655#[derive(Debug, Clone, PartialEq, Eq)]
656pub struct ConventionRoute {
657    /// 应用名
658    pub app: String,
659    /// 控制器名(首字母大写)
660    pub controller: String,
661    /// 操作名(小驼峰)
662    pub action: String,
663    /// HTTP 方法(约定式默认 GET,POST 也常见)
664    pub method: HttpMethod,
665    /// 生成的路径(如 `/oapc/customer/index`)
666    pub path: String,
667}
668
669impl ConventionRoute {
670    /// 从 URI 生成约定式路由元数据
671    ///
672    /// ```ignore
673    /// use sz_rust_router_facade::routing::ConventionRoute;
674    ///
675    /// let r = ConventionRoute::from_uri("/oapc/customer/index").unwrap();
676    /// assert_eq!(r.app, "oapc");
677    /// assert_eq!(r.controller, "Customer");
678    /// assert_eq!(r.action, "index");
679    /// assert_eq!(r.path, "/oapc/customer/index");
680    /// ```
681    pub fn from_uri(uri: &str) -> Option<Self> {
682        let parsed = crate::router::parse_path(uri);
683        // 默认应用和默认控制器+action 的情况不生成约定式路由
684        if parsed.app == crate::router::DEFAULT_APP
685            && parsed.controller == crate::router::DEFAULT_CONTROLLER
686            && parsed.action == crate::router::DEFAULT_ACTION
687        {
688            return None;
689        }
690        let path = format!(
691            "/{}/{}/{}",
692            parsed.app,
693            parsed.controller.to_lowercase(),
694            parsed.action
695        );
696        Some(Self {
697            app: parsed.app.into_owned(),
698            controller: parsed.controller.into_owned(),
699            action: parsed.action.into_owned(),
700            method: HttpMethod::GET,
701            path,
702        })
703    }
704
705    /// 从 ParsedPath 构造
706    pub fn from_parsed<'a>(parsed: ParsedPath<'a>) -> Option<Self> {
707        let uri = format!(
708            "/{}/{}/{}",
709            parsed.app,
710            parsed.controller.to_lowercase(),
711            parsed.action
712        );
713        Self::from_uri(&uri)
714    }
715}
716
717// ============================================================================
718// RouteRegistry - 三层路由汇总
719// ============================================================================
720
721/// 三层路由注册表
722///
723/// 收集三层路由的元数据,提供统一的查询和冲突检测接口。
724///
725/// ## 注意
726///
727/// 实际将路由注册到 `axum::Router` 需要 ControllerRegistry(在 controller_registry 模块实现)。
728/// 本模块仅提供元数据管理和冲突检测。
729#[derive(Debug, Clone, Default)]
730pub struct RouteRegistry {
731    /// Layer 1 - 属性宏路由
732    pub attribute_routes: Vec<RouteRule>,
733    /// Layer 2 - 配置式路由
734    pub config_routes: Vec<RouteRule>,
735    /// Layer 3 - 约定式路由
736    pub convention_routes: Vec<ConventionRoute>,
737}
738
739impl RouteRegistry {
740    /// 创建空注册表
741    pub fn new() -> Self {
742        Self::default()
743    }
744
745    /// 添加属性宏路由
746    pub fn add_attribute_route(&mut self, rule: RouteRule) -> &mut Self {
747        self.attribute_routes.push(rule);
748        self
749    }
750
751    /// 批量添加属性宏路由
752    pub fn add_attribute_routes(
753        &mut self,
754        rules: impl IntoIterator<Item = RouteRule>,
755    ) -> &mut Self {
756        self.attribute_routes.extend(rules);
757        self
758    }
759
760    /// 添加配置式路由
761    pub fn add_config_routes(&mut self, config: &RouteConfig) -> &mut Self {
762        self.config_routes.extend(config.flatten());
763        self
764    }
765
766    /// 添加约定式路由
767    pub fn add_convention_route(&mut self, route: ConventionRoute) -> &mut Self {
768        self.convention_routes.push(route);
769        self
770    }
771
772    /// 转换约定式路由为 RouteRule 列表
773    ///
774    /// 约定式路由的 handler 字段为 `Controller@action` 格式。
775    #[tracing::instrument(skip(self))]
776    pub fn convention_as_rules(&self) -> Vec<RouteRule> {
777        self.convention_routes
778            .iter()
779            .map(|c| RouteRule {
780                method: c.method.clone(),
781                path: c.path.clone(),
782                handler: format!("{}@{}", c.controller, c.action),
783                middleware: Vec::new(),
784                name: Some(format!(
785                    "convention.{}.{}.{}",
786                    c.app, c.controller, c.action
787                )),
788            })
789            .collect()
790    }
791
792    /// 合并所有三层的路由规则(按优先级:attribute > config > convention)
793    ///
794    /// 冲突时优先级高的覆盖低的,返回最终路由列表。
795    #[tracing::instrument(skip(self))]
796    pub fn merged_rules(&self) -> Vec<RouteRule> {
797        let mut seen: HashMap<(String, String), RouteRule> = HashMap::new();
798
799        // 优先级低 → 高(后插入覆盖前插入)
800        for rule in self.convention_as_rules() {
801            let key = (rule.method.to_string(), rule.path.clone());
802            seen.insert(key, rule);
803        }
804        for rule in &self.config_routes {
805            let key = (rule.method.to_string(), rule.path.clone());
806            seen.insert(key, rule.clone());
807        }
808        for rule in &self.attribute_routes {
809            let key = (rule.method.to_string(), rule.path.clone());
810            seen.insert(key, rule.clone());
811        }
812
813        seen.into_values().collect()
814    }
815
816    /// 检测属性宏层内部的冲突
817    pub fn attribute_conflicts(&self) -> Vec<(RouteRule, RouteRule)> {
818        find_conflicts_in(&self.attribute_routes)
819    }
820
821    /// 检测配置式层内部的冲突
822    pub fn config_conflicts(&self) -> Vec<(RouteRule, RouteRule)> {
823        find_conflicts_in(&self.config_routes)
824    }
825
826    /// 路由总数
827    pub fn total_count(&self) -> usize {
828        self.attribute_routes.len() + self.config_routes.len() + self.convention_routes.len()
829    }
830}
831
832/// 在路由规则列表中检测冲突
833fn find_conflicts_in(rules: &[RouteRule]) -> Vec<(RouteRule, RouteRule)> {
834    let mut seen: HashMap<(String, String), usize> = HashMap::new();
835    let mut conflicts = Vec::new();
836
837    for (i, rule) in rules.iter().enumerate() {
838        let key = (rule.method.to_string(), rule.path.clone());
839        if let Some(&prev_idx) = seen.get(&key) {
840            conflicts.push((rules[prev_idx].clone(), rules[i].clone()));
841        } else {
842            seen.insert(key, i);
843        }
844    }
845
846    conflicts
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852
853    // ====================================================================
854    // HttpMethod
855    // ====================================================================
856
857    #[test]
858    fn test_http_method_parse_uppercase() {
859        assert_eq!(HttpMethod::parse("GET").unwrap(), HttpMethod::GET);
860        assert_eq!(HttpMethod::parse("POST").unwrap(), HttpMethod::POST);
861        assert_eq!(HttpMethod::parse("PUT").unwrap(), HttpMethod::PUT);
862        assert_eq!(HttpMethod::parse("DELETE").unwrap(), HttpMethod::DELETE);
863        assert_eq!(HttpMethod::parse("PATCH").unwrap(), HttpMethod::PATCH);
864        assert_eq!(HttpMethod::parse("OPTIONS").unwrap(), HttpMethod::OPTIONS);
865    }
866
867    #[test]
868    fn test_http_method_parse_lowercase() {
869        assert_eq!(HttpMethod::parse("get").unwrap(), HttpMethod::GET);
870        assert_eq!(HttpMethod::parse("post").unwrap(), HttpMethod::POST);
871    }
872
873    #[test]
874    fn test_http_method_parse_mixed_case() {
875        assert_eq!(HttpMethod::parse("Get").unwrap(), HttpMethod::GET);
876        assert_eq!(HttpMethod::parse("pOsT").unwrap(), HttpMethod::POST);
877    }
878
879    #[test]
880    fn test_http_method_parse_invalid() {
881        assert!(HttpMethod::parse("invalid").is_err());
882        assert!(HttpMethod::parse("").is_err());
883        assert!(HttpMethod::parse("CONNECT").is_err());
884        assert!(HttpMethod::parse("TRACE").is_err());
885    }
886
887    #[test]
888    fn test_http_method_to_axum() {
889        assert_eq!(HttpMethod::GET.to_axum_method(), axum::http::Method::GET);
890        assert_eq!(HttpMethod::POST.to_axum_method(), axum::http::Method::POST);
891        assert_eq!(HttpMethod::PUT.to_axum_method(), axum::http::Method::PUT);
892        assert_eq!(
893            HttpMethod::DELETE.to_axum_method(),
894            axum::http::Method::DELETE
895        );
896        assert_eq!(
897            HttpMethod::PATCH.to_axum_method(),
898            axum::http::Method::PATCH
899        );
900        assert_eq!(
901            HttpMethod::OPTIONS.to_axum_method(),
902            axum::http::Method::OPTIONS
903        );
904    }
905
906    #[test]
907    fn test_http_method_display() {
908        assert_eq!(HttpMethod::GET.to_string(), "GET");
909        assert_eq!(HttpMethod::POST.to_string(), "POST");
910        assert_eq!(HttpMethod::PUT.to_string(), "PUT");
911    }
912
913    #[test]
914    fn test_http_method_serde() {
915        let json = serde_json::to_string(&HttpMethod::GET).unwrap();
916        assert_eq!(json, "\"GET\"");
917
918        let m: HttpMethod = serde_json::from_str("\"POST\"").unwrap();
919        assert_eq!(m, HttpMethod::POST);
920    }
921
922    // ====================================================================
923    // HandlerRef
924    // ====================================================================
925
926    #[test]
927    fn test_handler_ref_parse_at_separator() {
928        let h = HandlerRef::parse("User@list").unwrap();
929        assert_eq!(h.controller, "User");
930        assert_eq!(h.action, "list");
931    }
932
933    #[test]
934    fn test_handler_ref_parse_slash_separator() {
935        let h = HandlerRef::parse("User/list").unwrap();
936        assert_eq!(h.controller, "User");
937        assert_eq!(h.action, "list");
938    }
939
940    #[test]
941    fn test_handler_ref_parse_only_controller() {
942        let h = HandlerRef::parse("User").unwrap();
943        assert_eq!(h.controller, "User");
944        assert_eq!(h.action, "index"); // 默认 action
945    }
946
947    #[test]
948    fn test_handler_ref_parse_with_whitespace() {
949        let h = HandlerRef::parse("  User  @  list  ").unwrap();
950        assert_eq!(h.controller, "User");
951        assert_eq!(h.action, "list");
952    }
953
954    #[test]
955    fn test_handler_ref_parse_empty() {
956        assert!(HandlerRef::parse("").is_err());
957        assert!(HandlerRef::parse("   ").is_err());
958    }
959
960    #[test]
961    fn test_handler_ref_parse_empty_controller() {
962        assert!(HandlerRef::parse("@list").is_err());
963        assert!(HandlerRef::parse("/list").is_err());
964    }
965
966    #[test]
967    fn test_handler_ref_parse_empty_action() {
968        assert!(HandlerRef::parse("User@").is_err());
969        assert!(HandlerRef::parse("User/").is_err());
970    }
971
972    #[test]
973    fn test_handler_ref_to_string() {
974        let h = HandlerRef {
975            controller: "User".to_string(),
976            action: "list".to_string(),
977        };
978        assert_eq!(h.to_string(), "User@list");
979    }
980
981    // ====================================================================
982    // HandlerRefRef — 零拷贝借用版本(P3)
983    // ====================================================================
984
985    #[test]
986    fn test_handler_ref_ref_parse_at_separator() {
987        let h = HandlerRefRef::parse("User@list").unwrap();
988        assert_eq!(h.controller, "User");
989        assert_eq!(h.action, "list");
990    }
991
992    #[test]
993    fn test_handler_ref_ref_parse_slash_separator() {
994        let h = HandlerRefRef::parse("User/list").unwrap();
995        assert_eq!(h.controller, "User");
996        assert_eq!(h.action, "list");
997    }
998
999    #[test]
1000    fn test_handler_ref_ref_parse_only_controller() {
1001        let h = HandlerRefRef::parse("User").unwrap();
1002        assert_eq!(h.controller, "User");
1003        assert_eq!(h.action, "index");
1004    }
1005
1006    #[test]
1007    fn test_handler_ref_ref_parse_with_whitespace() {
1008        let h = HandlerRefRef::parse("  User  @  list  ").unwrap();
1009        assert_eq!(h.controller, "User");
1010        assert_eq!(h.action, "list");
1011    }
1012
1013    #[test]
1014    fn test_handler_ref_ref_parse_empty() {
1015        assert!(HandlerRefRef::parse("").is_err());
1016        assert!(HandlerRefRef::parse("   ").is_err());
1017    }
1018
1019    #[test]
1020    fn test_handler_ref_ref_parse_empty_controller() {
1021        assert!(HandlerRefRef::parse("@list").is_err());
1022        assert!(HandlerRefRef::parse("/list").is_err());
1023    }
1024
1025    #[test]
1026    fn test_handler_ref_ref_parse_empty_action() {
1027        assert!(HandlerRefRef::parse("User@").is_err());
1028        assert!(HandlerRefRef::parse("User/").is_err());
1029    }
1030
1031    #[test]
1032    fn test_handler_ref_ref_parse_rejects_path_traversal() {
1033        assert!(HandlerRefRef::parse("../Secret@admin").is_err());
1034    }
1035
1036    #[test]
1037    fn test_handler_ref_ref_parse_rejects_special_chars() {
1038        assert!(HandlerRefRef::parse("User$@list").is_err());
1039    }
1040
1041    #[test]
1042    fn test_handler_ref_ref_to_owned_consistency() {
1043        let ref_ref = HandlerRefRef::parse("User@list").unwrap();
1044        let owned = ref_ref.to_owned();
1045        assert_eq!(owned.controller, "User");
1046        assert_eq!(owned.action, "list");
1047    }
1048
1049    #[test]
1050    fn test_handler_ref_ref_from_into_handler_ref() {
1051        let ref_ref = HandlerRefRef::parse("Admin@dashboard").unwrap();
1052        let owned: HandlerRef = ref_ref.into();
1053        assert_eq!(owned.controller, "Admin");
1054        assert_eq!(owned.action, "dashboard");
1055    }
1056
1057    #[test]
1058    fn test_handler_ref_ref_display() {
1059        let h = HandlerRefRef::parse("User@list").unwrap();
1060        assert_eq!(h.to_string(), "User@list");
1061    }
1062
1063    #[test]
1064    fn test_handler_ref_ref_to_handler_string() {
1065        let h = HandlerRefRef::parse("User@list").unwrap();
1066        assert_eq!(h.to_handler_string(), "User@list");
1067    }
1068
1069    // ====================================================================
1070    // S-1 回归测试:HandlerRef 注入字符校验
1071    // ====================================================================
1072
1073    #[test]
1074    fn test_handler_ref_parse_rejects_path_traversal() {
1075        // `../Secret@admin` → controller="../Secret" → InvalidController
1076        assert!(matches!(
1077            HandlerRef::parse("../Secret@admin"),
1078            Err(RouteConfigError::InvalidController(_))
1079        ));
1080        // `..@admin` → controller=".." → InvalidController
1081        assert!(matches!(
1082            HandlerRef::parse("..@admin"),
1083            Err(RouteConfigError::InvalidController(_))
1084        ));
1085        // `User@../evil` → action="../evil" → InvalidAction
1086        assert!(matches!(
1087            HandlerRef::parse("User@../evil"),
1088            Err(RouteConfigError::InvalidAction(_))
1089        ));
1090    }
1091
1092    #[test]
1093    fn test_handler_ref_parse_rejects_double_at() {
1094        // `User@list@extra` → split_once('@') 得 controller="User", action="list@extra"
1095        // action 包含 '@' → InvalidAction
1096        assert!(matches!(
1097            HandlerRef::parse("User@list@extra"),
1098            Err(RouteConfigError::InvalidAction(_))
1099        ));
1100    }
1101
1102    #[test]
1103    fn test_handler_ref_parse_rejects_space_injection() {
1104        // 内部空格不应被允许(trim 仅处理首尾)
1105        assert!(matches!(
1106            HandlerRef::parse("Us er@list"),
1107            Err(RouteConfigError::InvalidController(_))
1108        ));
1109        assert!(matches!(
1110            HandlerRef::parse("User@li st"),
1111            Err(RouteConfigError::InvalidAction(_))
1112        ));
1113    }
1114
1115    #[test]
1116    fn test_handler_ref_parse_rejects_leading_digit() {
1117        // PHP 标识符首字符不能是数字
1118        assert!(matches!(
1119            HandlerRef::parse("1User@list"),
1120            Err(RouteConfigError::InvalidController(_))
1121        ));
1122        assert!(matches!(
1123            HandlerRef::parse("User@1list"),
1124            Err(RouteConfigError::InvalidAction(_))
1125        ));
1126    }
1127
1128    #[test]
1129    fn test_handler_ref_parse_accepts_underscore_and_alphanumeric() {
1130        let h = HandlerRef::parse("_Private@_index").unwrap();
1131        assert_eq!(h.controller, "_Private");
1132        assert_eq!(h.action, "_index");
1133
1134        let h = HandlerRef::parse("User@action_1").unwrap();
1135        assert_eq!(h.controller, "User");
1136        assert_eq!(h.action, "action_1");
1137
1138        // CamelCase 也合法
1139        let h = HandlerRef::parse("CustomerList@getListById").unwrap();
1140        assert_eq!(h.controller, "CustomerList");
1141        assert_eq!(h.action, "getListById");
1142    }
1143
1144    #[test]
1145    fn test_handler_ref_parse_rejects_special_chars() {
1146        // 冒号、分号、反斜杠等都不允许
1147        assert!(HandlerRef::parse("User:list@action").is_err());
1148        assert!(HandlerRef::parse("User;list@action").is_err());
1149        assert!(HandlerRef::parse(r"User\list@action").is_err());
1150        assert!(HandlerRef::parse("User@act\nion").is_err());
1151    }
1152
1153    // ====================================================================
1154    // RouteRule
1155    // ====================================================================
1156
1157    #[test]
1158    fn test_route_rule_new() {
1159        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list");
1160        assert_eq!(rule.method, HttpMethod::GET);
1161        assert_eq!(rule.path, "/users");
1162        assert_eq!(rule.handler, "User@list");
1163        assert!(rule.middleware.is_empty());
1164        assert!(rule.name.is_none());
1165    }
1166
1167    #[test]
1168    fn test_route_rule_handler_ref() {
1169        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list");
1170        let h = rule.handler_ref().unwrap();
1171        assert_eq!(h.controller, "User");
1172        assert_eq!(h.action, "list");
1173    }
1174
1175    #[test]
1176    fn test_route_rule_with_middleware() {
1177        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list")
1178            .with_middleware("auth")
1179            .with_middleware("log");
1180        assert_eq!(rule.middleware, vec!["auth", "log"]);
1181    }
1182
1183    #[test]
1184    fn test_route_rule_with_name() {
1185        let rule = RouteRule::new(HttpMethod::GET, "/users", "User@list").with_name("user.list");
1186        assert_eq!(rule.name, Some("user.list".to_string()));
1187    }
1188
1189    // ====================================================================
1190    // RouteGroup
1191    // ====================================================================
1192
1193    #[test]
1194    fn test_route_group_new() {
1195        let g = RouteGroup::new("/api/v1");
1196        assert_eq!(g.prefix, "/api/v1");
1197        assert!(g.routes.is_empty());
1198        assert!(g.middleware.is_empty());
1199    }
1200
1201    #[test]
1202    fn test_route_group_add_route() {
1203        let mut g = RouteGroup::new("/api");
1204        g.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1205        assert_eq!(g.routes.len(), 1);
1206    }
1207
1208    #[test]
1209    fn test_route_group_with_middleware() {
1210        let g = RouteGroup::new("/api")
1211            .with_middleware("auth")
1212            .with_middleware("log");
1213        assert_eq!(g.middleware, vec!["auth", "log"]);
1214    }
1215
1216    // ====================================================================
1217    // RouteConfig::flatten + join_path
1218    // ====================================================================
1219
1220    #[test]
1221    fn test_join_path_basic() {
1222        assert_eq!(join_path("/api", "/users"), "/api/users");
1223        assert_eq!(join_path("/api/", "/users"), "/api/users");
1224        assert_eq!(join_path("/api", "users"), "/api/users");
1225        assert_eq!(join_path("/api/", "users"), "/api/users");
1226    }
1227
1228    #[test]
1229    fn test_join_path_empty_prefix() {
1230        assert_eq!(join_path("", "/users"), "/users");
1231        assert_eq!(join_path("", "users"), "/users");
1232    }
1233
1234    #[test]
1235    fn test_join_path_empty_path() {
1236        assert_eq!(join_path("/api", ""), "/api");
1237        assert_eq!(join_path("/api/", ""), "/api");
1238    }
1239
1240    #[test]
1241    fn test_join_path_both_empty() {
1242        assert_eq!(join_path("", ""), "");
1243    }
1244
1245    #[test]
1246    fn test_route_config_flatten_no_groups() {
1247        let mut config = RouteConfig::new();
1248        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1249        config.add_route(RouteRule::new(HttpMethod::POST, "/users", "User@create"));
1250
1251        let flat = config.flatten();
1252        assert_eq!(flat.len(), 2);
1253        assert_eq!(flat[0].path, "/users");
1254        assert_eq!(flat[1].path, "/users");
1255    }
1256
1257    #[test]
1258    fn test_route_config_flatten_with_group() {
1259        let mut config = RouteConfig::new();
1260        let mut group = RouteGroup::new("/api/v1");
1261        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1262        group.add_route(RouteRule::new(HttpMethod::POST, "/items", "Item@create"));
1263        config.add_group(group);
1264
1265        let flat = config.flatten();
1266        assert_eq!(flat.len(), 2);
1267        assert_eq!(flat[0].path, "/api/v1/items");
1268        assert_eq!(flat[1].path, "/api/v1/items");
1269    }
1270
1271    #[test]
1272    fn test_route_config_flatten_group_middleware_prepended() {
1273        let mut config = RouteConfig::new();
1274        let mut group = RouteGroup::new("/api");
1275        group.middleware = vec!["auth".to_string(), "log".to_string()];
1276        let mut rule = RouteRule::new(HttpMethod::GET, "/items", "Item@list");
1277        rule.middleware = vec!["cache".to_string()];
1278        group.routes.push(rule);
1279        config.add_group(group);
1280
1281        let flat = config.flatten();
1282        assert_eq!(flat[0].middleware, vec!["auth", "log", "cache"]);
1283    }
1284
1285    #[test]
1286    fn test_route_config_flatten_mixed() {
1287        let mut config = RouteConfig::new();
1288        config.add_route(RouteRule::new(HttpMethod::GET, "/health", "Health@check"));
1289        let mut group = RouteGroup::new("/api");
1290        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1291        config.add_group(group);
1292
1293        let flat = config.flatten();
1294        assert_eq!(flat.len(), 2);
1295        assert!(flat.iter().any(|r| r.path == "/health"));
1296        assert!(flat.iter().any(|r| r.path == "/api/items"));
1297    }
1298
1299    // ====================================================================
1300    // RouteConfig::find_conflicts
1301    // ====================================================================
1302
1303    #[test]
1304    fn test_route_config_no_conflicts() {
1305        let mut config = RouteConfig::new();
1306        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1307        config.add_route(RouteRule::new(HttpMethod::POST, "/users", "User@create"));
1308        assert!(config.find_conflicts().is_empty());
1309    }
1310
1311    #[test]
1312    fn test_route_config_conflict_same_method_path() {
1313        let mut config = RouteConfig::new();
1314        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1315        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@all"));
1316
1317        let conflicts = config.find_conflicts();
1318        assert_eq!(conflicts.len(), 1);
1319        let (a, b) = &conflicts[0];
1320        assert_eq!(a.handler, "User@list");
1321        assert_eq!(b.handler, "User@all");
1322    }
1323
1324    #[test]
1325    fn test_route_config_no_conflict_different_method() {
1326        let mut config = RouteConfig::new();
1327        config.add_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1328        config.add_route(RouteRule::new(HttpMethod::DELETE, "/users", "User@delete"));
1329        assert!(config.find_conflicts().is_empty());
1330    }
1331
1332    #[test]
1333    fn test_route_config_conflict_in_group() {
1334        let mut config = RouteConfig::new();
1335        let mut group = RouteGroup::new("/api");
1336        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1337        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@all"));
1338        config.add_group(group);
1339
1340        let conflicts = config.find_conflicts();
1341        assert_eq!(conflicts.len(), 1);
1342    }
1343
1344    #[test]
1345    fn test_route_config_conflict_between_top_and_group() {
1346        let mut config = RouteConfig::new();
1347        // 顶层 /api/items
1348        config.add_route(RouteRule::new(HttpMethod::GET, "/api/items", "Item@list"));
1349        // group prefix /api + /items = /api/items
1350        let mut group = RouteGroup::new("/api");
1351        group.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@all"));
1352        config.add_group(group);
1353
1354        let conflicts = config.find_conflicts();
1355        assert_eq!(conflicts.len(), 1);
1356    }
1357
1358    // ====================================================================
1359    // YAML 加载
1360    // ====================================================================
1361
1362    #[test]
1363    fn test_load_routes_from_yaml_str_simple() {
1364        let yaml = r#"
1365routes:
1366  - method: GET
1367    path: /users
1368    handler: User@list
1369  - method: POST
1370    path: /users
1371    handler: User@create
1372"#;
1373        let config = load_routes_from_yaml_str(yaml).unwrap();
1374        assert_eq!(config.routes.len(), 2);
1375        assert_eq!(config.routes[0].method, HttpMethod::GET);
1376        assert_eq!(config.routes[0].path, "/users");
1377        assert_eq!(config.routes[0].handler, "User@list");
1378        assert_eq!(config.routes[1].method, HttpMethod::POST);
1379    }
1380
1381    #[test]
1382    fn test_load_routes_from_yaml_str_with_groups() {
1383        let yaml = r#"
1384routes:
1385  - method: GET
1386    path: /health
1387    handler: Health@check
1388groups:
1389  - prefix: /api/v1
1390    middleware: [auth, log]
1391    routes:
1392      - method: GET
1393        path: /items
1394        handler: Item@list
1395      - method: POST
1396        path: /items
1397        handler: Item@create
1398"#;
1399        let config = load_routes_from_yaml_str(yaml).unwrap();
1400        assert_eq!(config.routes.len(), 1);
1401        assert_eq!(config.groups.len(), 1);
1402        assert_eq!(config.groups[0].prefix, "/api/v1");
1403        assert_eq!(config.groups[0].middleware, vec!["auth", "log"]);
1404        assert_eq!(config.groups[0].routes.len(), 2);
1405
1406        let flat = config.flatten();
1407        assert_eq!(flat.len(), 3);
1408        assert!(flat.iter().any(|r| r.path == "/health"));
1409        assert!(flat.iter().any(|r| r.path == "/api/v1/items"));
1410    }
1411
1412    #[test]
1413    fn test_load_routes_from_yaml_str_with_name_and_middleware() {
1414        let yaml = r#"
1415routes:
1416  - method: GET
1417    path: /users/{id}
1418    handler: User@show
1419    middleware: [auth, cache]
1420    name: user.show
1421"#;
1422        let config = load_routes_from_yaml_str(yaml).unwrap();
1423        assert_eq!(config.routes.len(), 1);
1424        let rule = &config.routes[0];
1425        assert_eq!(rule.middleware, vec!["auth", "cache"]);
1426        assert_eq!(rule.name, Some("user.show".to_string()));
1427    }
1428
1429    #[test]
1430    fn test_load_routes_from_yaml_str_empty() {
1431        let yaml = "";
1432        let config = load_routes_from_yaml_str(yaml).unwrap();
1433        assert_eq!(config.routes.len(), 0);
1434        assert_eq!(config.groups.len(), 0);
1435    }
1436
1437    #[test]
1438    fn test_load_routes_from_yaml_str_invalid_method() {
1439        let yaml = r#"
1440routes:
1441  - method: INVALID
1442    path: /users
1443    handler: User@list
1444"#;
1445        let result = load_routes_from_yaml_str(yaml);
1446        // serde_yaml 会因为 INVALID 无法反序列化为 HttpMethod 而失败
1447        assert!(result.is_err());
1448    }
1449
1450    #[test]
1451    fn test_load_routes_from_yaml_str_invalid_yaml() {
1452        let yaml = "not: valid: yaml: at: all";
1453        let result = load_routes_from_yaml_str(yaml);
1454        assert!(result.is_err());
1455    }
1456
1457    // ====================================================================
1458    // JSON 加载
1459    // ====================================================================
1460
1461    #[test]
1462    fn test_load_routes_from_json_str_simple() {
1463        let json = r#"{
1464  "routes": [
1465    {"method": "GET", "path": "/users", "handler": "User@list"},
1466    {"method": "POST", "path": "/users", "handler": "User@create"}
1467  ]
1468}"#;
1469        let config = load_routes_from_json_str(json).unwrap();
1470        assert_eq!(config.routes.len(), 2);
1471        assert_eq!(config.routes[0].method, HttpMethod::GET);
1472        assert_eq!(config.routes[1].method, HttpMethod::POST);
1473    }
1474
1475    #[test]
1476    fn test_load_routes_from_json_str_with_groups() {
1477        let json = r#"{
1478  "routes": [
1479    {"method": "GET", "path": "/health", "handler": "Health@check"}
1480  ],
1481  "groups": [
1482    {
1483      "prefix": "/api",
1484      "middleware": ["auth"],
1485      "routes": [
1486        {"method": "GET", "path": "/items", "handler": "Item@list"}
1487      ]
1488    }
1489  ]
1490}"#;
1491        let config = load_routes_from_json_str(json).unwrap();
1492        assert_eq!(config.routes.len(), 1);
1493        assert_eq!(config.groups.len(), 1);
1494        assert_eq!(config.groups[0].prefix, "/api");
1495    }
1496
1497    #[test]
1498    fn test_load_routes_from_json_str_empty() {
1499        let json = "{}";
1500        let config = load_routes_from_json_str(json).unwrap();
1501        assert_eq!(config.routes.len(), 0);
1502        assert_eq!(config.groups.len(), 0);
1503    }
1504
1505    #[test]
1506    fn test_load_routes_from_json_str_invalid() {
1507        let json = "{not valid json";
1508        let result = load_routes_from_json_str(json);
1509        assert!(result.is_err());
1510    }
1511
1512    // ====================================================================
1513    // ConventionRoute
1514    // ====================================================================
1515
1516    #[test]
1517    fn test_convention_route_from_uri_with_app() {
1518        let r = ConventionRoute::from_uri("/oapc/customer/index").unwrap();
1519        assert_eq!(r.app, "oapc");
1520        assert_eq!(r.controller, "Customer");
1521        assert_eq!(r.action, "index");
1522        assert_eq!(r.path, "/oapc/customer/index");
1523        assert_eq!(r.method, HttpMethod::GET);
1524    }
1525
1526    #[test]
1527    fn test_convention_route_from_uri_admin_app() {
1528        let r = ConventionRoute::from_uri("/admin/login/index").unwrap();
1529        assert_eq!(r.app, "admin");
1530        assert_eq!(r.controller, "Login");
1531        assert_eq!(r.action, "index");
1532    }
1533
1534    #[test]
1535    fn test_convention_route_from_uri_root_returns_none() {
1536        // 根路径 → 默认应用+控制器+action → 不生成约定式路由
1537        assert!(ConventionRoute::from_uri("/").is_none());
1538        assert!(ConventionRoute::from_uri("").is_none());
1539    }
1540
1541    #[test]
1542    fn test_convention_route_from_uri_single_segment() {
1543        // /foo → (index, Foo, index)
1544        // 不在 app_map,所以 app=index,但 controller=Foo,action=index
1545        // 这个 case 会生成约定式路由吗?看实现:app=index, controller=Foo, action=index
1546        // 由于 action=index 是默认值,但 controller=Foo 不是默认值,所以应生成
1547        let r = ConventionRoute::from_uri("/customer").unwrap();
1548        assert_eq!(r.app, "index");
1549        assert_eq!(r.controller, "Customer");
1550        assert_eq!(r.action, "index");
1551    }
1552
1553    #[test]
1554    fn test_convention_route_from_parsed() {
1555        let parsed = ParsedPath::new("api", "User", "list");
1556        let r = ConventionRoute::from_parsed(parsed).unwrap();
1557        assert_eq!(r.app, "api");
1558        assert_eq!(r.controller, "User");
1559        assert_eq!(r.action, "list");
1560    }
1561
1562    // ====================================================================
1563    // RouteRegistry
1564    // ====================================================================
1565
1566    #[test]
1567    fn test_route_registry_new() {
1568        let r = RouteRegistry::new();
1569        assert!(r.attribute_routes.is_empty());
1570        assert!(r.config_routes.is_empty());
1571        assert!(r.convention_routes.is_empty());
1572        assert_eq!(r.total_count(), 0);
1573    }
1574
1575    #[test]
1576    fn test_route_registry_add_attribute_route() {
1577        let mut r = RouteRegistry::new();
1578        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1579        assert_eq!(r.attribute_routes.len(), 1);
1580        assert_eq!(r.total_count(), 1);
1581    }
1582
1583    #[test]
1584    fn test_route_registry_add_attribute_routes_batch() {
1585        let mut r = RouteRegistry::new();
1586        r.add_attribute_routes(vec![
1587            RouteRule::new(HttpMethod::GET, "/users", "User@list"),
1588            RouteRule::new(HttpMethod::POST, "/users", "User@create"),
1589        ]);
1590        assert_eq!(r.attribute_routes.len(), 2);
1591    }
1592
1593    #[test]
1594    fn test_route_registry_add_config_routes() {
1595        let mut r = RouteRegistry::new();
1596        let mut config = RouteConfig::new();
1597        config.add_route(RouteRule::new(HttpMethod::GET, "/items", "Item@list"));
1598        config.add_route(RouteRule::new(HttpMethod::POST, "/items", "Item@create"));
1599        r.add_config_routes(&config);
1600        assert_eq!(r.config_routes.len(), 2);
1601    }
1602
1603    #[test]
1604    fn test_route_registry_add_convention_route() {
1605        let mut r = RouteRegistry::new();
1606        let cr = ConventionRoute::from_uri("/oapc/customer/index").unwrap();
1607        r.add_convention_route(cr);
1608        assert_eq!(r.convention_routes.len(), 1);
1609    }
1610
1611    #[test]
1612    fn test_route_registry_convention_as_rules() {
1613        let mut r = RouteRegistry::new();
1614        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1615        r.add_convention_route(ConventionRoute::from_uri("/admin/login/index").unwrap());
1616
1617        let rules = r.convention_as_rules();
1618        assert_eq!(rules.len(), 2);
1619        assert_eq!(rules[0].handler, "Customer@index");
1620        assert_eq!(rules[1].handler, "Login@index");
1621        assert_eq!(
1622            rules[0].name,
1623            Some("convention.oapc.Customer.index".to_string())
1624        );
1625    }
1626
1627    #[test]
1628    fn test_route_registry_merged_rules_attribute_overrides_config() {
1629        let mut r = RouteRegistry::new();
1630        // Layer 2 - config
1631        r.add_config_routes(&RouteConfig {
1632            routes: vec![RouteRule::new(HttpMethod::GET, "/users", "User@old")],
1633            groups: vec![],
1634        });
1635        // Layer 1 - attribute (优先级更高,覆盖 config)
1636        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@new"));
1637
1638        let merged = r.merged_rules();
1639        assert_eq!(merged.len(), 1);
1640        assert_eq!(merged[0].handler, "User@new");
1641    }
1642
1643    #[test]
1644    fn test_route_registry_merged_rules_config_overrides_convention() {
1645        let mut r = RouteRegistry::new();
1646        // Layer 3 - convention
1647        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1648        // Layer 2 - config (优先级更高,覆盖 convention)
1649        r.add_config_routes(&RouteConfig {
1650            routes: vec![RouteRule::new(
1651                HttpMethod::GET,
1652                "/oapc/customer/index",
1653                "Customer@custom",
1654            )],
1655            groups: vec![],
1656        });
1657
1658        let merged = r.merged_rules();
1659        assert_eq!(merged.len(), 1);
1660        assert_eq!(merged[0].handler, "Customer@custom");
1661    }
1662
1663    #[test]
1664    fn test_route_registry_merged_rules_different_paths_no_override() {
1665        let mut r = RouteRegistry::new();
1666        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1667        r.add_config_routes(&RouteConfig {
1668            routes: vec![RouteRule::new(HttpMethod::GET, "/items", "Item@list")],
1669            groups: vec![],
1670        });
1671        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1672
1673        let merged = r.merged_rules();
1674        assert_eq!(merged.len(), 3);
1675    }
1676
1677    #[test]
1678    fn test_route_registry_attribute_conflicts() {
1679        let mut r = RouteRegistry::new();
1680        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1681        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@all"));
1682
1683        let conflicts = r.attribute_conflicts();
1684        assert_eq!(conflicts.len(), 1);
1685    }
1686
1687    #[test]
1688    fn test_route_registry_config_conflicts() {
1689        let mut r = RouteRegistry::new();
1690        r.add_config_routes(&RouteConfig {
1691            routes: vec![
1692                RouteRule::new(HttpMethod::GET, "/users", "User@list"),
1693                RouteRule::new(HttpMethod::GET, "/users", "User@all"),
1694            ],
1695            groups: vec![],
1696        });
1697
1698        let conflicts = r.config_conflicts();
1699        assert_eq!(conflicts.len(), 1);
1700    }
1701
1702    #[test]
1703    fn test_route_registry_no_conflicts() {
1704        let mut r = RouteRegistry::new();
1705        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@list"));
1706        r.add_attribute_route(RouteRule::new(HttpMethod::POST, "/users", "User@create"));
1707
1708        assert!(r.attribute_conflicts().is_empty());
1709    }
1710
1711    #[test]
1712    fn test_route_registry_total_count() {
1713        let mut r = RouteRegistry::new();
1714        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/a", "A@index"));
1715        r.add_config_routes(&RouteConfig {
1716            routes: vec![RouteRule::new(HttpMethod::GET, "/b", "B@index")],
1717            groups: vec![],
1718        });
1719        r.add_convention_route(ConventionRoute::from_uri("/oapc/c/d").unwrap());
1720
1721        assert_eq!(r.total_count(), 3);
1722    }
1723
1724    // ====================================================================
1725    // 集成测试 - 完整三层路由流程
1726    // ====================================================================
1727
1728    #[test]
1729    fn test_integration_three_layer_routing() {
1730        // Layer 1 - 属性宏路由
1731        let mut r = RouteRegistry::new();
1732        r.add_attribute_routes(vec![
1733            RouteRule::new(HttpMethod::GET, "/users", "User@list"),
1734            RouteRule::new(HttpMethod::POST, "/users", "User@create"),
1735            RouteRule::new(HttpMethod::GET, "/users/{id}", "User@show"),
1736        ]);
1737
1738        // Layer 2 - 配置式路由
1739        let yaml = r#"
1740routes:
1741  - method: GET
1742    path: /items
1743    handler: Item@list
1744  - method: POST
1745    path: /items
1746    handler: Item@create
1747groups:
1748  - prefix: /api/v1
1749    middleware: [auth]
1750    routes:
1751      - method: GET
1752        path: /orders
1753        handler: Order@list
1754"#;
1755        let config = load_routes_from_yaml_str(yaml).unwrap();
1756        r.add_config_routes(&config);
1757
1758        // Layer 3 - 约定式路由
1759        r.add_convention_route(ConventionRoute::from_uri("/oapc/customer/index").unwrap());
1760        r.add_convention_route(ConventionRoute::from_uri("/admin/login/index").unwrap());
1761
1762        // 验证总数
1763        assert_eq!(r.attribute_routes.len(), 3);
1764        assert_eq!(r.config_routes.len(), 3); // 2 顶层 + 1 group
1765        assert_eq!(r.convention_routes.len(), 2);
1766        assert_eq!(r.total_count(), 8);
1767
1768        // 合并后应无冲突(各层路径不重复)
1769        let merged = r.merged_rules();
1770        assert_eq!(merged.len(), 8);
1771
1772        // 验证各层无内部冲突
1773        assert!(r.attribute_conflicts().is_empty());
1774        assert!(r.config_conflicts().is_empty());
1775    }
1776
1777    #[test]
1778    fn test_integration_layer_override_priority() {
1779        // 三层都注册同一路径,attribute 应覆盖
1780        let mut r = RouteRegistry::new();
1781
1782        // Layer 3 - convention
1783        r.add_convention_route(ConventionRoute {
1784            app: "index".to_string(),
1785            controller: "User".to_string(),
1786            action: "list".to_string(),
1787            method: HttpMethod::GET,
1788            path: "/users".to_string(),
1789        });
1790
1791        // Layer 2 - config
1792        r.add_config_routes(&RouteConfig {
1793            routes: vec![RouteRule::new(HttpMethod::GET, "/users", "User@config")],
1794            groups: vec![],
1795        });
1796
1797        // Layer 1 - attribute (优先级最高)
1798        r.add_attribute_route(RouteRule::new(HttpMethod::GET, "/users", "User@attribute"));
1799
1800        let merged = r.merged_rules();
1801        assert_eq!(merged.len(), 1);
1802        assert_eq!(merged[0].handler, "User@attribute");
1803    }
1804}