Skip to main content

sz_rust_cli/cmd/
route.rs

1//! `route:list` 命令 — 对齐 PHP `think route:list`
2//!
3//! ## PHP 对齐
4//!
5//! PHP `route:list` 输出所有注册的路由规则。Rust 端的路由通过 `RouterBuilder` 动态构建,
6//! CLI 命令无法访问运行时路由表,因此输出预定义的应用映射和路径解析规则。
7//!
8//! ## 输出格式
9//!
10//! - `table`(默认):表格格式
11//! - `json`:JSON 格式
12
13use crate::error::CliError;
14
15/// 执行 route:list 命令
16///
17/// # 参数
18///
19/// - `format`:输出格式(`table` 或 `json`)
20pub fn execute_route_list(format: &str) -> Result<(), CliError> {
21    let routes = collect_routes();
22
23    match format {
24        "table" => print_table(&routes),
25        "json" => print_json(&routes)?,
26        _ => {
27            return Err(CliError::Generic(format!(
28                "Unsupported format: {} (supported: table, json)",
29                format
30            )));
31        }
32    }
33
34    Ok(())
35}
36
37/// 路由信息
38#[derive(Debug, Clone)]
39pub struct RouteInfo {
40    /// HTTP 方法(GET/POST/PUT/DELETE 等)
41    pub method: &'static str,
42    /// 路由路径(如 `/api/user/list`)
43    pub path: &'static str,
44    /// 所属应用(如 `api`、`admin`)
45    pub app: &'static str,
46    /// 控制器名(如 `User`)
47    pub controller: &'static str,
48    /// 方法名(如 `list`)
49    pub action: &'static str,
50}
51
52/// 收集预定义路由
53pub fn collect_routes() -> Vec<RouteInfo> {
54    vec![
55        RouteInfo {
56            method: "GET",
57            path: "/",
58            app: "index",
59            controller: "Index",
60            action: "index",
61        },
62        RouteInfo {
63            method: "GET",
64            path: "/oapc/customer/index",
65            app: "oapc",
66            controller: "Customer",
67            action: "index",
68        },
69        RouteInfo {
70            method: "GET",
71            path: "/admin/user/list",
72            app: "admin",
73            controller: "User",
74            action: "list",
75        },
76        RouteInfo {
77            method: "GET",
78            path: "/api/goods/list",
79            app: "api",
80            controller: "Goods",
81            action: "list",
82        },
83        RouteInfo {
84            method: "POST",
85            path: "/api/order/save",
86            app: "api",
87            controller: "Order",
88            action: "save",
89        },
90        RouteInfo {
91            method: "GET",
92            path: "/farm/weight/index",
93            app: "farm",
94            controller: "Weight",
95            action: "index",
96        },
97        RouteInfo {
98            method: "GET",
99            path: "/cashier/order/index",
100            app: "cashier",
101            controller: "Order",
102            action: "index",
103        },
104        RouteInfo {
105            method: "GET",
106            path: "/scene/device/index",
107            app: "scene",
108            controller: "Device",
109            action: "index",
110        },
111    ]
112}
113
114/// 表格格式输出(对齐 PHP route:list)
115fn print_table(routes: &[RouteInfo]) {
116    println!(
117        "{:<6} {:<25} {:<8} {:<12} {:<10}",
118        "Method", "Path", "App", "Controller", "Action"
119    );
120    println!("{}", "-".repeat(70));
121
122    for route in routes {
123        println!(
124            "{:<6} {:<25} {:<8} {:<12} {:<10}",
125            route.method, route.path, route.app, route.controller, route.action
126        );
127    }
128
129    println!();
130    println!("Registered apps: oapc, admin, api, farm, oapi, cashier, scene");
131    println!("Default app: index | Default controller: Index | Default action: index");
132}
133
134/// JSON 格式输出
135fn print_json(routes: &[RouteInfo]) -> Result<(), CliError> {
136    let json: Vec<serde_json::Value> = routes
137        .iter()
138        .map(|r| {
139            serde_json::json!({
140                "method": r.method,
141                "path": r.path,
142                "app": r.app,
143                "controller": r.controller,
144                "action": r.action,
145            })
146        })
147        .collect();
148
149    let output = serde_json::to_string_pretty(&json)
150        .map_err(|e| CliError::Generic(format!("JSON serialization failed: {}", e)))?;
151    println!("{}", output);
152    Ok(())
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_execute_route_list_table_format() {
161        let result = execute_route_list("table");
162        assert!(result.is_ok());
163    }
164
165    #[test]
166    fn test_execute_route_list_json_format() {
167        let result = execute_route_list("json");
168        assert!(result.is_ok());
169    }
170
171    #[test]
172    fn test_execute_route_list_invalid_format() {
173        let result = execute_route_list("xml");
174        assert!(matches!(result, Err(CliError::Generic(_))));
175    }
176
177    #[test]
178    fn test_collect_routes_not_empty() {
179        let routes = collect_routes();
180        assert!(!routes.is_empty());
181    }
182
183    #[test]
184    fn test_collect_routes_contains_default_route() {
185        let routes = collect_routes();
186        assert!(routes.iter().any(|r| r.path == "/" && r.app == "index"));
187    }
188
189    #[test]
190    fn test_collect_routes_contains_all_apps() {
191        let routes = collect_routes();
192        let apps: Vec<&str> = routes.iter().map(|r| r.app).collect();
193        assert!(apps.contains(&"oapc"));
194        assert!(apps.contains(&"admin"));
195        assert!(apps.contains(&"api"));
196        assert!(apps.contains(&"farm"));
197        assert!(apps.contains(&"cashier"));
198        assert!(apps.contains(&"scene"));
199    }
200
201    #[test]
202    fn test_print_table_does_not_panic() {
203        let routes = collect_routes();
204        print_table(&routes);
205    }
206
207    #[test]
208    fn test_print_json_valid_json() {
209        let routes = collect_routes();
210        let result = std::panic::catch_unwind(|| print_json(&routes));
211        assert!(result.is_ok());
212    }
213}