Skip to main content

sz_rust_cli/cmd/
route.rs

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