Skip to main content

rusticity_term/sqs/
queue.rs

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}