1use crate::common::translate_column;
2use crate::common::{format_iso_timestamp, ColumnId, UTC_TIMESTAMP_WIDTH};
3use crate::ui::table::Column as TableColumn;
4use ratatui::prelude::*;
5use std::collections::HashMap;
6
7pub fn init(i18n: &mut HashMap<String, String>) {
8 for col in Column::all() {
9 i18n.entry(col.id().to_string())
10 .or_insert_with(|| col.default_name().to_string());
11 }
12}
13
14#[derive(Debug, Clone)]
15pub struct RestApi {
16 pub id: String,
17 pub name: String,
18 pub description: String,
19 pub created_date: String,
20 pub api_key_source: String,
21 pub endpoint_configuration: String,
22 pub protocol_type: String,
23 pub disable_execute_api_endpoint: bool,
24 pub status: String,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq)]
28pub enum Column {
29 Name,
30 Description,
31 Id,
32 Protocol,
33 EndpointType,
34 Created,
35 SecurityPolicy,
36 ApiStatus,
37}
38
39impl Column {
40 const ID_NAME: &'static str = "column.apig.api.name";
41 const ID_DESCRIPTION: &'static str = "column.apig.api.description";
42 const ID_ID: &'static str = "column.apig.api.id";
43 const ID_PROTOCOL: &'static str = "column.apig.api.protocol";
44 const ID_ENDPOINT_TYPE: &'static str = "column.apig.api.endpoint_type";
45 const ID_CREATED: &'static str = "column.apig.api.created";
46 const ID_SECURITY_POLICY: &'static str = "column.apig.api.security_policy";
47 const ID_API_STATUS: &'static str = "column.apig.api.api_status";
48
49 pub const fn id(&self) -> &'static str {
50 match self {
51 Column::Name => Self::ID_NAME,
52 Column::Description => Self::ID_DESCRIPTION,
53 Column::Id => Self::ID_ID,
54 Column::Protocol => Self::ID_PROTOCOL,
55 Column::EndpointType => Self::ID_ENDPOINT_TYPE,
56 Column::Created => Self::ID_CREATED,
57 Column::SecurityPolicy => Self::ID_SECURITY_POLICY,
58 Column::ApiStatus => Self::ID_API_STATUS,
59 }
60 }
61
62 pub const fn default_name(&self) -> &'static str {
63 match self {
64 Column::Name => "Name",
65 Column::Description => "Description",
66 Column::Id => "ID",
67 Column::Protocol => "Protocol",
68 Column::EndpointType => "API endpoint type",
69 Column::Created => "Created",
70 Column::SecurityPolicy => "Security policy",
71 Column::ApiStatus => "API status",
72 }
73 }
74
75 pub const fn all() -> [Column; 8] {
76 [
77 Column::Name,
78 Column::Description,
79 Column::Id,
80 Column::Protocol,
81 Column::EndpointType,
82 Column::Created,
83 Column::SecurityPolicy,
84 Column::ApiStatus,
85 ]
86 }
87
88 pub fn ids() -> Vec<ColumnId> {
89 Self::all().iter().map(|c| c.id()).collect()
90 }
91
92 pub fn from_id(id: &str) -> Option<Self> {
93 match id {
94 Self::ID_NAME => Some(Column::Name),
95 Self::ID_DESCRIPTION => Some(Column::Description),
96 Self::ID_ID => Some(Column::Id),
97 Self::ID_PROTOCOL => Some(Column::Protocol),
98 Self::ID_ENDPOINT_TYPE => Some(Column::EndpointType),
99 Self::ID_CREATED => Some(Column::Created),
100 Self::ID_SECURITY_POLICY => Some(Column::SecurityPolicy),
101 Self::ID_API_STATUS => Some(Column::ApiStatus),
102 _ => None,
103 }
104 }
105
106 pub fn name(&self) -> String {
107 translate_column(self.id(), self.default_name())
108 }
109}
110
111impl TableColumn<RestApi> for Column {
112 fn name(&self) -> &str {
113 Box::leak(translate_column(self.id(), self.default_name()).into_boxed_str())
114 }
115
116 fn width(&self) -> u16 {
117 let translated = translate_column(self.id(), self.default_name());
118 translated.len().max(match self {
119 Column::Name => 30,
120 Column::Description => 40,
121 Column::Id => 15,
122 Column::Protocol => 10,
123 Column::EndpointType => 20,
124 Column::Created => UTC_TIMESTAMP_WIDTH as usize,
125 Column::SecurityPolicy => 18,
126 Column::ApiStatus => 15,
127 }) as u16
128 }
129
130 fn render(&self, item: &RestApi) -> (String, Style) {
131 match self {
132 Column::Name => (item.name.clone(), Style::default()),
133 Column::Description => (item.description.clone(), Style::default()),
134 Column::Id => (item.id.clone(), Style::default()),
135 Column::Protocol => {
136 let protocol = match item.protocol_type.to_uppercase().as_str() {
138 "HTTP" => "HTTP",
139 "WEBSOCKET" => "WEBSOCKET",
140 "REST" => "REST",
141 _ => &item.protocol_type,
142 };
143 (protocol.to_string(), Style::default())
144 }
145 Column::EndpointType => {
146 let endpoint = match item.endpoint_configuration.to_uppercase().as_str() {
148 "REGIONAL" => "Regional",
149 "EDGE" => "Edge-optimized",
150 "PRIVATE" => "Private",
151 _ => &item.endpoint_configuration,
152 };
153 (endpoint.to_string(), Style::default())
154 }
155 Column::Created => (format_iso_timestamp(&item.created_date), Style::default()),
156 Column::SecurityPolicy => {
157 let policy = if item.protocol_type.to_uppercase() == "REST" {
160 "-" } else {
162 "TLS 1.2"
163 };
164 (policy.to_string(), Style::default())
165 }
166 Column::ApiStatus => {
167 if item.disable_execute_api_endpoint {
169 ("Disabled".to_string(), Style::default())
170 } else {
171 (
172 "✅ Available".to_string(),
173 Style::default().fg(Color::Green),
174 )
175 }
176 }
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_column_ids_have_correct_prefix() {
187 for col in Column::all() {
188 assert!(
189 col.id().starts_with("column.apig.api."),
190 "Column ID '{}' should start with 'column.apig.api.'",
191 col.id()
192 );
193 }
194 }
195
196 #[test]
197 fn test_all_columns_count() {
198 assert_eq!(Column::all().len(), 8);
199 }
200
201 #[test]
202 fn test_column_from_id() {
203 assert_eq!(Column::from_id("column.apig.api.name"), Some(Column::Name));
204 }
205
206 #[test]
207 fn test_protocol_capitalization() {
208 use crate::ui::table::Column as TableColumn;
209
210 let api = RestApi {
211 id: "test".to_string(),
212 name: "Test".to_string(),
213 description: "".to_string(),
214 created_date: "".to_string(),
215 api_key_source: "".to_string(),
216 endpoint_configuration: "".to_string(),
217 protocol_type: "Http".to_string(),
218 disable_execute_api_endpoint: false,
219 status: "".to_string(),
220 };
221
222 let (text, _) = Column::Protocol.render(&api);
223 assert_eq!(text, "HTTP");
224 }
225
226 #[test]
227 fn test_endpoint_type_formatting() {
228 use crate::ui::table::Column as TableColumn;
229
230 let mut api = RestApi {
231 id: "test".to_string(),
232 name: "Test".to_string(),
233 description: "".to_string(),
234 created_date: "".to_string(),
235 api_key_source: "".to_string(),
236 endpoint_configuration: "REGIONAL".to_string(),
237 protocol_type: "HTTP".to_string(),
238 disable_execute_api_endpoint: false,
239 status: "".to_string(),
240 };
241
242 let (text, _) = Column::EndpointType.render(&api);
243 assert_eq!(text, "Regional");
244
245 api.endpoint_configuration = "EDGE".to_string();
246 let (text, _) = Column::EndpointType.render(&api);
247 assert_eq!(text, "Edge-optimized");
248 }
249
250 #[test]
251 fn test_api_status_available_green() {
252 use crate::ui::table::Column as TableColumn;
253
254 let api = RestApi {
255 id: "test".to_string(),
256 name: "Test".to_string(),
257 description: "".to_string(),
258 created_date: "".to_string(),
259 api_key_source: "".to_string(),
260 endpoint_configuration: "".to_string(),
261 protocol_type: "HTTP".to_string(),
262 disable_execute_api_endpoint: false,
263 status: "".to_string(),
264 };
265
266 let (text, style) = Column::ApiStatus.render(&api);
267 assert_eq!(text, "✅ Available");
268 assert_eq!(style.fg, Some(Color::Green));
269 }
270
271 #[test]
272 fn test_api_status_disabled() {
273 use crate::ui::table::Column as TableColumn;
274
275 let api = RestApi {
276 id: "test".to_string(),
277 name: "Test".to_string(),
278 description: "".to_string(),
279 created_date: "".to_string(),
280 api_key_source: "".to_string(),
281 endpoint_configuration: "".to_string(),
282 protocol_type: "HTTP".to_string(),
283 disable_execute_api_endpoint: true,
284 status: "".to_string(),
285 };
286
287 let (text, _) = Column::ApiStatus.render(&api);
288 assert_eq!(text, "Disabled");
289 }
290
291 #[test]
292 fn test_security_policy_rest_api() {
293 use crate::ui::table::Column as TableColumn;
294
295 let api = RestApi {
296 id: "test".to_string(),
297 name: "Test".to_string(),
298 description: "".to_string(),
299 created_date: "".to_string(),
300 api_key_source: "".to_string(),
301 endpoint_configuration: "".to_string(),
302 protocol_type: "REST".to_string(),
303 disable_execute_api_endpoint: false,
304 status: "".to_string(),
305 };
306
307 let (text, _) = Column::SecurityPolicy.render(&api);
308 assert_eq!(text, "-");
309 }
310
311 #[test]
312 fn test_security_policy_http_api() {
313 use crate::ui::table::Column as TableColumn;
314
315 let api = RestApi {
316 id: "test".to_string(),
317 name: "Test".to_string(),
318 description: "".to_string(),
319 created_date: "".to_string(),
320 api_key_source: "".to_string(),
321 endpoint_configuration: "".to_string(),
322 protocol_type: "HTTP".to_string(),
323 disable_execute_api_endpoint: false,
324 status: "".to_string(),
325 };
326
327 let (text, _) = Column::SecurityPolicy.render(&api);
328 assert_eq!(text, "TLS 1.2");
329 }
330}