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)]
39struct RouteInfo {
40    method: &'static str,
41    path: &'static str,
42    app: &'static str,
43    controller: &'static str,
44    action: &'static str,
45}
46
47/// 收集预定义路由
48fn collect_routes() -> Vec<RouteInfo> {
49    vec![
50        RouteInfo {
51            method: "GET",
52            path: "/",
53            app: "index",
54            controller: "Index",
55            action: "index",
56        },
57        RouteInfo {
58            method: "GET",
59            path: "/oapc/customer/index",
60            app: "oapc",
61            controller: "Customer",
62            action: "index",
63        },
64        RouteInfo {
65            method: "GET",
66            path: "/admin/user/list",
67            app: "admin",
68            controller: "User",
69            action: "list",
70        },
71        RouteInfo {
72            method: "GET",
73            path: "/api/goods/list",
74            app: "api",
75            controller: "Goods",
76            action: "list",
77        },
78        RouteInfo {
79            method: "POST",
80            path: "/api/order/save",
81            app: "api",
82            controller: "Order",
83            action: "save",
84        },
85        RouteInfo {
86            method: "GET",
87            path: "/farm/weight/index",
88            app: "farm",
89            controller: "Weight",
90            action: "index",
91        },
92        RouteInfo {
93            method: "GET",
94            path: "/cashier/order/index",
95            app: "cashier",
96            controller: "Order",
97            action: "index",
98        },
99        RouteInfo {
100            method: "GET",
101            path: "/scene/device/index",
102            app: "scene",
103            controller: "Device",
104            action: "index",
105        },
106    ]
107}
108
109/// 表格格式输出(对齐 PHP route:list)
110fn print_table(routes: &[RouteInfo]) {
111    println!(
112        "{:<6} {:<25} {:<8} {:<12} {:<10}",
113        "Method", "Path", "App", "Controller", "Action"
114    );
115    println!("{}", "-".repeat(70));
116
117    for route in routes {
118        println!(
119            "{:<6} {:<25} {:<8} {:<12} {:<10}",
120            route.method, route.path, route.app, route.controller, route.action
121        );
122    }
123
124    println!();
125    println!("Registered apps: oapc, admin, api, farm, oapi, cashier, scene");
126    println!("Default app: index | Default controller: Index | Default action: index");
127}
128
129/// JSON 格式输出
130fn print_json(routes: &[RouteInfo]) -> Result<(), CliError> {
131    let json: Vec<serde_json::Value> = routes
132        .iter()
133        .map(|r| {
134            serde_json::json!({
135                "method": r.method,
136                "path": r.path,
137                "app": r.app,
138                "controller": r.controller,
139                "action": r.action,
140            })
141        })
142        .collect();
143
144    let output = serde_json::to_string_pretty(&json)
145        .map_err(|e| CliError::Generic(format!("JSON serialization failed: {}", e)))?;
146    println!("{}", output);
147    Ok(())
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_execute_route_list_table_format() {
156        let result = execute_route_list("table");
157        assert!(result.is_ok());
158    }
159
160    #[test]
161    fn test_execute_route_list_json_format() {
162        let result = execute_route_list("json");
163        assert!(result.is_ok());
164    }
165
166    #[test]
167    fn test_execute_route_list_invalid_format() {
168        let result = execute_route_list("xml");
169        assert!(matches!(result, Err(CliError::Generic(_))));
170    }
171
172    #[test]
173    fn test_collect_routes_not_empty() {
174        let routes = collect_routes();
175        assert!(!routes.is_empty());
176    }
177
178    #[test]
179    fn test_collect_routes_contains_default_route() {
180        let routes = collect_routes();
181        assert!(routes.iter().any(|r| r.path == "/" && r.app == "index"));
182    }
183
184    #[test]
185    fn test_collect_routes_contains_all_apps() {
186        let routes = collect_routes();
187        let apps: Vec<&str> = routes.iter().map(|r| r.app).collect();
188        assert!(apps.contains(&"oapc"));
189        assert!(apps.contains(&"admin"));
190        assert!(apps.contains(&"api"));
191        assert!(apps.contains(&"farm"));
192        assert!(apps.contains(&"cashier"));
193        assert!(apps.contains(&"scene"));
194    }
195
196    #[test]
197    fn test_print_table_does_not_panic() {
198        let routes = collect_routes();
199        print_table(&routes);
200    }
201
202    #[test]
203    fn test_print_json_valid_json() {
204        let routes = collect_routes();
205        let result = std::panic::catch_unwind(|| print_json(&routes));
206        assert!(result.is_ok());
207    }
208}