1use crate::ui::table::Column as TableColumn;
2use crate::ui::tree::TreeItem;
3use ratatui::prelude::*;
4
5#[derive(Debug, Clone)]
6pub struct Route {
7 pub route_id: String,
8 pub route_key: String,
9 pub target: String,
10 pub authorization_type: String,
11 pub api_key_required: bool,
12 pub display_name: String,
13 pub arn: String,
14}
15
16impl TreeItem for Route {
17 fn id(&self) -> &str {
18 &self.route_key
19 }
20
21 fn display_name(&self) -> &str {
22 &self.display_name
23 }
24
25 fn is_expandable(&self) -> bool {
26 self.target.is_empty()
29 }
30
31 fn icon(&self) -> &str {
32 if self.route_key == "$default"
33 || self.route_key == "$connect"
34 || self.route_key == "$disconnect"
35 {
36 "🔌" } else {
38 "" }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub enum Column {
45 RouteKey,
46 RouteId,
47 Arn,
48 AuthorizationType,
49 Target,
50}
51
52impl Column {
53 const ID_ROUTE_KEY: &'static str = "route_key";
54 const ID_ROUTE_ID: &'static str = "route_id";
55 const ID_ARN: &'static str = "arn";
56 const ID_AUTHORIZATION_TYPE: &'static str = "authorization_type";
57 const ID_TARGET: &'static str = "target";
58
59 pub const fn id(&self) -> &'static str {
60 match self {
61 Column::RouteKey => Self::ID_ROUTE_KEY,
62 Column::RouteId => Self::ID_ROUTE_ID,
63 Column::Arn => Self::ID_ARN,
64 Column::AuthorizationType => Self::ID_AUTHORIZATION_TYPE,
65 Column::Target => Self::ID_TARGET,
66 }
67 }
68
69 pub fn from_id(id: &str) -> Option<Self> {
70 match id {
71 Self::ID_ROUTE_KEY => Some(Column::RouteKey),
72 Self::ID_ROUTE_ID => Some(Column::RouteId),
73 Self::ID_ARN => Some(Column::Arn),
74 Self::ID_AUTHORIZATION_TYPE => Some(Column::AuthorizationType),
75 Self::ID_TARGET => Some(Column::Target),
76 _ => None,
77 }
78 }
79
80 pub const fn all() -> [Column; 5] {
81 [
82 Column::RouteKey,
83 Column::RouteId,
84 Column::Arn,
85 Column::AuthorizationType,
86 Column::Target,
87 ]
88 }
89}
90
91impl TableColumn<Route> for Column {
92 fn name(&self) -> &str {
93 match self {
94 Column::RouteKey => "Route",
95 Column::RouteId => "ID",
96 Column::Arn => "ARN",
97 Column::AuthorizationType => "Authorization",
98 Column::Target => "Integration",
99 }
100 }
101
102 fn width(&self) -> u16 {
103 match self {
104 Column::RouteKey => 30,
105 Column::RouteId => 15,
106 Column::Arn => 50,
107 Column::AuthorizationType => 15,
108 Column::Target => 30,
109 }
110 }
111
112 fn render(&self, item: &Route) -> (String, Style) {
113 let text = match self {
114 Column::RouteKey => item.route_key.clone(),
115 Column::RouteId => item.route_id.clone(),
116 Column::Arn => item.arn.clone(),
117 Column::AuthorizationType => item.authorization_type.clone(),
118 Column::Target => item.target.clone(),
119 };
120 (text, Style::default())
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_route_tree_item_id() {
130 let route = Route {
131 route_id: "abc123".to_string(),
132 route_key: "/api/users".to_string(),
133 target: "integration1".to_string(),
134 authorization_type: "NONE".to_string(),
135 api_key_required: false,
136 display_name: String::new(),
137 arn: String::new(),
138 };
139 assert_eq!(route.id(), "/api/users");
140 }
141
142 #[test]
143 fn test_route_tree_item_display_name() {
144 let route = Route {
145 route_id: "abc123".to_string(),
146 route_key: "/api/users/{id}".to_string(),
147 target: "integration1".to_string(),
148 authorization_type: "NONE".to_string(),
149 api_key_required: false,
150 display_name: "/api/users/{id}".to_string(),
151 arn: String::new(),
152 };
153 assert_eq!(route.display_name(), "/api/users/{id}");
154 }
155
156 #[test]
157 fn test_route_is_expandable() {
158 let virtual_parent = Route {
160 route_id: "virtual_/v1".to_string(),
161 route_key: "/v1".to_string(),
162 target: String::new(),
163 authorization_type: String::new(),
164 api_key_required: false,
165 display_name: String::new(),
166 arn: String::new(),
167 };
168 assert!(virtual_parent.is_expandable());
169
170 let real_route = Route {
172 route_id: "1".to_string(),
173 route_key: "/api/users".to_string(),
174 target: "int1".to_string(),
175 authorization_type: "NONE".to_string(),
176 api_key_required: false,
177 display_name: String::new(),
178 arn: String::new(),
179 };
180 assert!(!real_route.is_expandable());
181 }
182
183 #[test]
184 fn test_route_icons() {
185 let websocket_default = Route {
186 route_id: "3".to_string(),
187 route_key: "$default".to_string(),
188 target: "int3".to_string(),
189 authorization_type: "NONE".to_string(),
190 api_key_required: false,
191 display_name: String::new(),
192 arn: String::new(),
193 };
194 assert_eq!(websocket_default.icon(), "🔌");
195
196 let websocket_connect = Route {
197 route_id: "4".to_string(),
198 route_key: "$connect".to_string(),
199 target: "int4".to_string(),
200 authorization_type: "NONE".to_string(),
201 api_key_required: false,
202 display_name: String::new(),
203 arn: String::new(),
204 };
205 assert_eq!(websocket_connect.icon(), "🔌");
206
207 let regular_route = Route {
209 route_id: "1".to_string(),
210 route_key: "/api/users".to_string(),
211 target: "int1".to_string(),
212 authorization_type: "NONE".to_string(),
213 api_key_required: false,
214 display_name: String::new(),
215 arn: String::new(),
216 };
217 assert_eq!(regular_route.icon(), "");
218 }
219
220 #[test]
221 fn test_build_route_hierarchy_flat() {
222 use crate::ui::apig::build_route_hierarchy;
223
224 let routes = vec![
225 Route {
226 route_id: "1".to_string(),
227 route_key: "/users".to_string(),
228 target: "int1".to_string(),
229 authorization_type: "NONE".to_string(),
230 api_key_required: false,
231 display_name: String::new(),
232 arn: String::new(),
233 },
234 Route {
235 route_id: "2".to_string(),
236 route_key: "/health".to_string(),
237 target: "int2".to_string(),
238 authorization_type: "NONE".to_string(),
239 api_key_required: false,
240 display_name: String::new(),
241 arn: String::new(),
242 },
243 ];
244
245 let (root, children) = build_route_hierarchy(routes);
246 assert_eq!(root.len(), 2);
247 assert!(children.is_empty());
248 }
249
250 #[test]
251 fn test_build_route_hierarchy_nested() {
252 use crate::ui::apig::build_route_hierarchy;
253
254 let routes = vec![
255 Route {
256 route_id: "1".to_string(),
257 route_key: "/api/users".to_string(),
258 target: "int1".to_string(),
259 authorization_type: "NONE".to_string(),
260 api_key_required: false,
261 display_name: String::new(),
262 arn: String::new(),
263 },
264 Route {
265 route_id: "2".to_string(),
266 route_key: "/api/users/{id}".to_string(),
267 target: "int2".to_string(),
268 authorization_type: "NONE".to_string(),
269 api_key_required: false,
270 display_name: String::new(),
271 arn: String::new(),
272 },
273 ];
274
275 let (root, children) = build_route_hierarchy(routes);
276 assert_eq!(root.len(), 1);
278 assert_eq!(root[0].route_key, "/api");
279
280 assert!(children.contains_key("/api"));
282 assert_eq!(children.get("/api").unwrap().len(), 1);
283 assert_eq!(children.get("/api").unwrap()[0].route_key, "/api/users");
284
285 assert!(children.contains_key("/api/users"));
287 assert_eq!(children.get("/api/users").unwrap().len(), 1);
288 }
289
290 #[test]
291 fn test_build_route_hierarchy_websocket() {
292 use crate::ui::apig::build_route_hierarchy;
293
294 let routes = vec![
295 Route {
296 route_id: "1".to_string(),
297 route_key: "$default".to_string(),
298 target: "int1".to_string(),
299 authorization_type: "NONE".to_string(),
300 api_key_required: false,
301 display_name: String::new(),
302 arn: String::new(),
303 },
304 Route {
305 route_id: "2".to_string(),
306 route_key: "$connect".to_string(),
307 target: "int2".to_string(),
308 authorization_type: "NONE".to_string(),
309 api_key_required: false,
310 display_name: String::new(),
311 arn: String::new(),
312 },
313 Route {
314 route_id: "3".to_string(),
315 route_key: "/users".to_string(),
316 target: "int3".to_string(),
317 authorization_type: "NONE".to_string(),
318 api_key_required: false,
319 display_name: String::new(),
320 arn: String::new(),
321 },
322 ];
323
324 let (root, children) = build_route_hierarchy(routes);
325 assert_eq!(root.len(), 3);
326 assert!(children.is_empty());
327 }
328}