1use crate::error::CliError;
7use crate::report::build::route_reports;
8use crate::report::text::render_route_rows;
9use siderite_core::App;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct RouteRow {
14 pub method: String,
16 pub path: String,
18 pub operation_id: Option<String>,
20}
21
22pub fn route_table(app: &App) -> Result<Vec<RouteRow>, CliError> {
28 Ok(route_reports(app)?
29 .into_iter()
30 .map(|r| RouteRow {
31 method: r.method,
32 path: r.path,
33 operation_id: r.operation_id,
34 })
35 .collect())
36}
37
38pub fn render_routes(rows: &[RouteRow]) -> String {
40 render_route_rows(rows.iter().map(|r| {
41 (
42 r.method.as_str(),
43 r.path.as_str(),
44 r.operation_id.as_deref(),
45 )
46 }))
47}
48
49#[cfg(test)]
50mod tests {
51 #![allow(clippy::unwrap_used)]
52
53 use super::*;
54 use siderite_core::{get, post};
55
56 fn app() -> App {
57 let v1 = App::new()
58 .route(
59 "/users",
60 post(|| async { "c" })
61 .operation_id("create_user")
62 .get(|| async { "l" }),
63 )
64 .route("/", get(|| async { "root" }));
65 App::new()
66 .route("/health", get(|| async { "ok" }).operation_id("health"))
67 .route("/secret", get(|| async { "s" }).hidden())
68 .mount("/api/v1", v1)
69 }
70
71 #[test]
72 fn lists_mounts_sorted_and_skips_hidden() {
73 let rows = route_table(&app()).unwrap();
74 let summary: Vec<_> = rows
75 .iter()
76 .map(|r| {
77 (
78 r.method.as_str(),
79 r.path.as_str(),
80 r.operation_id.as_deref(),
81 )
82 })
83 .collect();
84 assert_eq!(
85 summary,
86 [
87 ("GET", "/api/v1", None),
88 ("GET", "/api/v1/users", None),
89 ("POST", "/api/v1/users", Some("create_user")),
90 ("GET", "/health", Some("health")),
91 ]
92 );
93 }
94
95 #[test]
96 fn duplicate_operations_are_an_error() {
97 let app = App::new()
98 .route("/a", get(|| async { "a" }).operation_id("same"))
99 .route("/b", get(|| async { "b" }).operation_id("same"));
100 let err = route_table(&app).unwrap_err();
101 assert!(err.to_string().contains("same"), "{err}");
102 }
103
104 #[test]
105 fn table_is_aligned() {
106 let text = render_routes(&route_table(&app()).unwrap());
107 let lines: Vec<&str> = text.lines().collect();
108 assert_eq!(lines.len(), 4);
109 assert_eq!(lines[2], "POST /api/v1/users create_user");
110 assert_eq!(lines[3], "GET /health health");
111 assert_eq!(lines[0], "GET /api/v1 -");
112 }
113
114 #[test]
115 fn empty_app_has_no_routes() {
116 assert_eq!(
117 render_routes(&route_table(&App::new()).unwrap()),
118 "No routes.\n"
119 );
120 }
121}