1use crate::common::{
2 format_bytes, format_duration_seconds, format_unix_timestamp, translate_column, ColumnId,
3 UTC_TIMESTAMP_WIDTH,
4};
5use crate::ui::table::Column as TableColumn;
6use ratatui::prelude::*;
7use std::collections::HashMap;
8
9pub fn init(i18n: &mut HashMap<String, String>) {
10 for col in Column::all() {
11 i18n.entry(col.id().to_string())
12 .or_insert_with(|| col.default_name().to_string());
13 }
14}
15
16#[derive(Debug, Clone)]
17pub struct Queue {
18 pub name: String,
19 pub url: String,
20 pub queue_type: String,
21 pub created_timestamp: String,
22 pub messages_available: String,
23 pub messages_in_flight: String,
24 pub encryption: String,
25 pub content_based_deduplication: String,
26 pub last_modified_timestamp: String,
27 pub visibility_timeout: String,
28 pub message_retention_period: String,
29 pub maximum_message_size: String,
30 pub delivery_delay: String,
31 pub receive_message_wait_time: String,
32 pub high_throughput_fifo: String,
33 pub deduplication_scope: String,
34 pub fifo_throughput_limit: String,
35 pub dead_letter_queue: String,
36 pub messages_delayed: String,
37 pub redrive_allow_policy: String,
38 pub redrive_policy: String,
39 pub redrive_task_id: String,
40 pub redrive_task_start_time: String,
41 pub redrive_task_status: String,
42 pub redrive_task_percent: String,
43 pub redrive_task_destination: String,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq)]
47pub enum Column {
48 Name,
49 Type,
50 Created,
51 MessagesAvailable,
52 MessagesInFlight,
53 Encryption,
54 ContentBasedDeduplication,
55 LastUpdated,
56 VisibilityTimeout,
57 MessageRetentionPeriod,
58 MaximumMessageSize,
59 DeliveryDelay,
60 ReceiveMessageWaitTime,
61 HighThroughputFifo,
62 DeduplicationScope,
63 FifoThroughputLimit,
64}
65
66impl Column {
67 const ID_NAME: &'static str = "column.sqs.queue.name";
68 const ID_TYPE: &'static str = "column.sqs.queue.type";
69 const ID_CREATED: &'static str = "column.sqs.queue.created";
70 const ID_MESSAGES_AVAILABLE: &'static str = "column.sqs.queue.messages_available";
71 const ID_MESSAGES_IN_FLIGHT: &'static str = "column.sqs.queue.messages_in_flight";
72 const ID_ENCRYPTION: &'static str = "column.sqs.queue.encryption";
73 const ID_CONTENT_BASED_DEDUPLICATION: &'static str =
74 "column.sqs.queue.content_based_deduplication";
75 const ID_LAST_UPDATED: &'static str = "column.sqs.queue.last_updated";
76 const ID_VISIBILITY_TIMEOUT: &'static str = "column.sqs.queue.visibility_timeout";
77 const ID_MESSAGE_RETENTION_PERIOD: &'static str = "column.sqs.queue.message_retention_period";
78 const ID_MAXIMUM_MESSAGE_SIZE: &'static str = "column.sqs.queue.maximum_message_size";
79 const ID_DELIVERY_DELAY: &'static str = "column.sqs.queue.delivery_delay";
80 const ID_RECEIVE_MESSAGE_WAIT_TIME: &'static str = "column.sqs.queue.receive_message_wait_time";
81 const ID_HIGH_THROUGHPUT_FIFO: &'static str = "column.sqs.queue.high_throughput_fifo";
82 const ID_DEDUPLICATION_SCOPE: &'static str = "column.sqs.queue.deduplication_scope";
83 const ID_FIFO_THROUGHPUT_LIMIT: &'static str = "column.sqs.queue.fifo_throughput_limit";
84
85 pub const fn id(&self) -> ColumnId {
86 match self {
87 Column::Name => Self::ID_NAME,
88 Column::Type => Self::ID_TYPE,
89 Column::Created => Self::ID_CREATED,
90 Column::MessagesAvailable => Self::ID_MESSAGES_AVAILABLE,
91 Column::MessagesInFlight => Self::ID_MESSAGES_IN_FLIGHT,
92 Column::Encryption => Self::ID_ENCRYPTION,
93 Column::ContentBasedDeduplication => Self::ID_CONTENT_BASED_DEDUPLICATION,
94 Column::LastUpdated => Self::ID_LAST_UPDATED,
95 Column::VisibilityTimeout => Self::ID_VISIBILITY_TIMEOUT,
96 Column::MessageRetentionPeriod => Self::ID_MESSAGE_RETENTION_PERIOD,
97 Column::MaximumMessageSize => Self::ID_MAXIMUM_MESSAGE_SIZE,
98 Column::DeliveryDelay => Self::ID_DELIVERY_DELAY,
99 Column::ReceiveMessageWaitTime => Self::ID_RECEIVE_MESSAGE_WAIT_TIME,
100 Column::HighThroughputFifo => Self::ID_HIGH_THROUGHPUT_FIFO,
101 Column::DeduplicationScope => Self::ID_DEDUPLICATION_SCOPE,
102 Column::FifoThroughputLimit => Self::ID_FIFO_THROUGHPUT_LIMIT,
103 }
104 }
105
106 pub const fn default_name(&self) -> &'static str {
107 match self {
108 Column::Name => "Name",
109 Column::Type => "Type",
110 Column::Created => "Created",
111 Column::MessagesAvailable => "Messages available",
112 Column::MessagesInFlight => "Messages in flight",
113 Column::Encryption => "Encryption",
114 Column::ContentBasedDeduplication => "Content-based deduplication",
115 Column::LastUpdated => "Last updated",
116 Column::VisibilityTimeout => "Visibility timeout",
117 Column::MessageRetentionPeriod => "Message retention period",
118 Column::MaximumMessageSize => "Maximum message size",
119 Column::DeliveryDelay => "Delivery delay",
120 Column::ReceiveMessageWaitTime => "Receive message wait time",
121 Column::HighThroughputFifo => "High throughput FIFO",
122 Column::DeduplicationScope => "Deduplication scope",
123 Column::FifoThroughputLimit => "FIFO throughput limit",
124 }
125 }
126
127 pub fn name(&self) -> String {
128 translate_column(self.id(), self.default_name())
129 }
130
131 pub fn from_id(id: &str) -> Option<Self> {
132 match id {
133 Self::ID_NAME => Some(Column::Name),
134 Self::ID_TYPE => Some(Column::Type),
135 Self::ID_CREATED => Some(Column::Created),
136 Self::ID_MESSAGES_AVAILABLE => Some(Column::MessagesAvailable),
137 Self::ID_MESSAGES_IN_FLIGHT => Some(Column::MessagesInFlight),
138 Self::ID_ENCRYPTION => Some(Column::Encryption),
139 Self::ID_CONTENT_BASED_DEDUPLICATION => Some(Column::ContentBasedDeduplication),
140 Self::ID_LAST_UPDATED => Some(Column::LastUpdated),
141 Self::ID_VISIBILITY_TIMEOUT => Some(Column::VisibilityTimeout),
142 Self::ID_MESSAGE_RETENTION_PERIOD => Some(Column::MessageRetentionPeriod),
143 Self::ID_MAXIMUM_MESSAGE_SIZE => Some(Column::MaximumMessageSize),
144 Self::ID_DELIVERY_DELAY => Some(Column::DeliveryDelay),
145 Self::ID_RECEIVE_MESSAGE_WAIT_TIME => Some(Column::ReceiveMessageWaitTime),
146 Self::ID_HIGH_THROUGHPUT_FIFO => Some(Column::HighThroughputFifo),
147 Self::ID_DEDUPLICATION_SCOPE => Some(Column::DeduplicationScope),
148 Self::ID_FIFO_THROUGHPUT_LIMIT => Some(Column::FifoThroughputLimit),
149 _ => None,
150 }
151 }
152
153 pub fn all() -> [Column; 16] {
154 [
155 Column::Name,
156 Column::Type,
157 Column::Created,
158 Column::MessagesAvailable,
159 Column::MessagesInFlight,
160 Column::Encryption,
161 Column::ContentBasedDeduplication,
162 Column::LastUpdated,
163 Column::VisibilityTimeout,
164 Column::MessageRetentionPeriod,
165 Column::MaximumMessageSize,
166 Column::DeliveryDelay,
167 Column::ReceiveMessageWaitTime,
168 Column::HighThroughputFifo,
169 Column::DeduplicationScope,
170 Column::FifoThroughputLimit,
171 ]
172 }
173
174 pub fn ids() -> Vec<ColumnId> {
175 Self::all().iter().map(|c| c.id()).collect()
176 }
177}
178
179impl TableColumn<Queue> for Column {
180 fn name(&self) -> &str {
181 Box::leak(translate_column(self.id(), self.default_name()).into_boxed_str())
182 }
183
184 fn width(&self) -> u16 {
185 let translated = translate_column(self.id(), self.default_name());
186 translated.len().max(match self {
187 Column::Name => 40,
188 Column::Type => 10,
189 Column::Created => UTC_TIMESTAMP_WIDTH as usize,
190 Column::MessagesAvailable => 20,
191 Column::MessagesInFlight => 20,
192 Column::Encryption => 12,
193 Column::ContentBasedDeduplication => 30,
194 Column::LastUpdated => UTC_TIMESTAMP_WIDTH as usize,
195 Column::VisibilityTimeout => 20,
196 Column::MessageRetentionPeriod => 25,
197 Column::MaximumMessageSize => 22,
198 Column::DeliveryDelay => 15,
199 Column::ReceiveMessageWaitTime => 25,
200 Column::HighThroughputFifo => 22,
201 Column::DeduplicationScope => 22,
202 Column::FifoThroughputLimit => 22,
203 }) as u16
204 }
205
206 fn render(&self, item: &Queue) -> (String, Style) {
207 let text = match self {
208 Column::Name => item.name.clone(),
209 Column::Type => item.queue_type.clone(),
210 Column::Created => format_unix_timestamp(&item.created_timestamp),
211 Column::MessagesAvailable => item.messages_available.clone(),
212 Column::MessagesInFlight => item.messages_in_flight.clone(),
213 Column::Encryption => item.encryption.clone(),
214 Column::ContentBasedDeduplication => item.content_based_deduplication.clone(),
215 Column::LastUpdated => format_unix_timestamp(&item.last_modified_timestamp),
216 Column::VisibilityTimeout => {
217 if let Ok(seconds) = item.visibility_timeout.parse::<i32>() {
218 format_duration_seconds(seconds)
219 } else {
220 item.visibility_timeout.clone()
221 }
222 }
223 Column::MessageRetentionPeriod => {
224 if let Ok(seconds) = item.message_retention_period.parse::<i32>() {
225 format_duration_seconds(seconds)
226 } else {
227 item.message_retention_period.clone()
228 }
229 }
230 Column::MaximumMessageSize => {
231 if let Some(bytes_str) = item.maximum_message_size.split_whitespace().next() {
232 if let Ok(bytes) = bytes_str.parse::<i64>() {
233 format_bytes(bytes)
234 } else {
235 item.maximum_message_size.clone()
236 }
237 } else {
238 item.maximum_message_size.clone()
239 }
240 }
241 Column::DeliveryDelay => {
242 if let Ok(seconds) = item.delivery_delay.parse::<i32>() {
243 format_duration_seconds(seconds)
244 } else {
245 item.delivery_delay.clone()
246 }
247 }
248 Column::ReceiveMessageWaitTime => {
249 if let Ok(seconds) = item.receive_message_wait_time.parse::<i32>() {
250 format_duration_seconds(seconds)
251 } else {
252 item.receive_message_wait_time.clone()
253 }
254 }
255 Column::HighThroughputFifo => item.high_throughput_fifo.clone(),
256 Column::DeduplicationScope => item.deduplication_scope.clone(),
257 Column::FifoThroughputLimit => item.fifo_throughput_limit.clone(),
258 };
259 (text, Style::default())
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_column_all() {
269 assert_eq!(Column::all().len(), 16);
270 }
271
272 #[test]
273 fn test_maximum_message_size_formatting() {
274 let queue = Queue {
275 name: "test".to_string(),
276 url: String::new(),
277 queue_type: "Standard".to_string(),
278 created_timestamp: String::new(),
279 messages_available: "0".to_string(),
280 messages_in_flight: "0".to_string(),
281 encryption: "Disabled".to_string(),
282 content_based_deduplication: "Disabled".to_string(),
283 last_modified_timestamp: String::new(),
284 visibility_timeout: String::new(),
285 message_retention_period: String::new(),
286 maximum_message_size: "262144 bytes".to_string(),
287 delivery_delay: String::new(),
288 receive_message_wait_time: String::new(),
289 high_throughput_fifo: "-".to_string(),
290 deduplication_scope: "-".to_string(),
291 fifo_throughput_limit: "-".to_string(),
292 dead_letter_queue: "-".to_string(),
293 messages_delayed: "0".to_string(),
294 redrive_allow_policy: "-".to_string(),
295 redrive_policy: "".to_string(),
296 redrive_task_id: "-".to_string(),
297 redrive_task_start_time: "-".to_string(),
298 redrive_task_status: "-".to_string(),
299 redrive_task_percent: "-".to_string(),
300 redrive_task_destination: "-".to_string(),
301 };
302
303 let (text, _) = Column::MaximumMessageSize.render(&queue);
304 assert_eq!(text, "262.14 KB");
305 }
306
307 #[test]
308 fn test_duration_formatting() {
309 let queue = Queue {
310 name: "test".to_string(),
311 url: String::new(),
312 queue_type: "Standard".to_string(),
313 created_timestamp: String::new(),
314 messages_available: "0".to_string(),
315 messages_in_flight: "0".to_string(),
316 encryption: "Disabled".to_string(),
317 content_based_deduplication: "Disabled".to_string(),
318 last_modified_timestamp: String::new(),
319 visibility_timeout: "30".to_string(),
320 message_retention_period: "345600".to_string(),
321 maximum_message_size: String::new(),
322 delivery_delay: "0".to_string(),
323 receive_message_wait_time: "20".to_string(),
324 high_throughput_fifo: "-".to_string(),
325 deduplication_scope: "-".to_string(),
326 fifo_throughput_limit: "-".to_string(),
327 dead_letter_queue: "-".to_string(),
328 messages_delayed: "0".to_string(),
329 redrive_allow_policy: "-".to_string(),
330 redrive_policy: "".to_string(),
331 redrive_task_id: "-".to_string(),
332 redrive_task_start_time: "-".to_string(),
333 redrive_task_status: "-".to_string(),
334 redrive_task_percent: "-".to_string(),
335 redrive_task_destination: "-".to_string(),
336 };
337
338 let (text, _) = Column::VisibilityTimeout.render(&queue);
339 assert_eq!(text, "30s");
340
341 let (text, _) = Column::MessageRetentionPeriod.render(&queue);
342 assert_eq!(text, "4d");
343
344 let (text, _) = Column::DeliveryDelay.render(&queue);
345 assert_eq!(text, "0s");
346
347 let (text, _) = Column::ReceiveMessageWaitTime.render(&queue);
348 assert_eq!(text, "20s");
349 }
350
351 #[test]
352 fn test_timestamp_formatting() {
353 let queue = Queue {
354 name: "test".to_string(),
355 url: String::new(),
356 queue_type: "Standard".to_string(),
357 created_timestamp: "1609459200".to_string(),
358 messages_available: "0".to_string(),
359 messages_in_flight: "0".to_string(),
360 encryption: "Disabled".to_string(),
361 content_based_deduplication: "Disabled".to_string(),
362 last_modified_timestamp: "1609459200".to_string(),
363 visibility_timeout: String::new(),
364 message_retention_period: String::new(),
365 maximum_message_size: String::new(),
366 delivery_delay: String::new(),
367 receive_message_wait_time: String::new(),
368 high_throughput_fifo: "-".to_string(),
369 deduplication_scope: "-".to_string(),
370 fifo_throughput_limit: "-".to_string(),
371 dead_letter_queue: "-".to_string(),
372 messages_delayed: "0".to_string(),
373 redrive_allow_policy: "-".to_string(),
374 redrive_policy: "".to_string(),
375 redrive_task_id: "-".to_string(),
376 redrive_task_start_time: "-".to_string(),
377 redrive_task_status: "-".to_string(),
378 redrive_task_percent: "-".to_string(),
379 redrive_task_destination: "-".to_string(),
380 };
381
382 let (text, _) = Column::Created.render(&queue);
383 assert!(text.contains("2021-01-01"));
384 assert!(text.contains("(UTC)"));
385 }
386}
387
388#[test]
389fn test_column_ids_have_correct_prefix() {
390 for col in Column::all() {
391 assert!(
392 col.id().starts_with("column.sqs.queue."),
393 "Column ID '{}' should start with 'column.sqs.queue.'",
394 col.id()
395 );
396 }
397}