rusticity_term/
lambda.rs

1use crate::common::{format_bytes, ColumnTrait, UTC_TIMESTAMP_WIDTH};
2use crate::ui::lambda::{ApplicationDetailTab, DetailTab};
3use crate::ui::table::Column as TableColumn;
4use ratatui::prelude::*;
5
6pub fn format_runtime(runtime: &str) -> String {
7    let lower = runtime.to_lowercase();
8
9    if let Some(rest) = lower.strip_prefix("python") {
10        let version = if rest.contains('.') {
11            rest.to_string()
12        } else if rest.len() >= 2 {
13            format!("{}.{}", &rest[0..1], &rest[1..])
14        } else {
15            rest.to_string()
16        };
17        format!("Python {}", version)
18    } else if let Some(rest) = lower.strip_prefix("nodejs") {
19        let formatted = rest.replace("x", ".x");
20        format!("Node.js {}", formatted)
21    } else if let Some(rest) = lower.strip_prefix("java") {
22        format!("Java {}", rest)
23    } else if let Some(rest) = lower.strip_prefix("dotnet") {
24        format!(".NET {}", rest)
25    } else if let Some(rest) = lower.strip_prefix("go") {
26        format!("Go {}", rest)
27    } else if let Some(rest) = lower.strip_prefix("ruby") {
28        format!("Ruby {}", rest)
29    } else {
30        runtime.to_string()
31    }
32}
33
34pub fn format_architecture(arch: &str) -> String {
35    match arch {
36        "X86_64" => "x86-64".to_string(),
37        "X8664" => "x86-64".to_string(),
38        "Arm64" => "arm64".to_string(),
39        _ => arch.replace("X86", "x86").replace("Arm", "arm"),
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn test_format_runtime() {
49        // AWS SDK format (e.g., Python39, Nodejs20x)
50        assert_eq!(format_runtime("Python39"), "Python 3.9");
51        assert_eq!(format_runtime("Python312"), "Python 3.12");
52        assert_eq!(format_runtime("Nodejs20x"), "Node.js 20.x");
53
54        // Lowercase format
55        assert_eq!(format_runtime("python3.12"), "Python 3.12");
56        assert_eq!(format_runtime("python3.11"), "Python 3.11");
57        assert_eq!(format_runtime("nodejs20x"), "Node.js 20.x");
58        assert_eq!(format_runtime("nodejs18x"), "Node.js 18.x");
59        assert_eq!(format_runtime("java21"), "Java 21");
60        assert_eq!(format_runtime("dotnet8"), ".NET 8");
61        assert_eq!(format_runtime("go1.x"), "Go 1.x");
62        assert_eq!(format_runtime("ruby3.3"), "Ruby 3.3");
63        assert_eq!(format_runtime("unknown"), "unknown");
64    }
65
66    #[test]
67    fn test_format_architecture() {
68        assert_eq!(format_architecture("X8664"), "x86-64");
69        assert_eq!(format_architecture("X86_64"), "x86-64");
70        assert_eq!(format_architecture("Arm64"), "arm64");
71        assert_eq!(format_architecture("arm64"), "arm64");
72    }
73
74    #[test]
75    fn test_runtime_formatter_in_table_column() {
76        let func = Function {
77            name: "test-func".to_string(),
78            arn: "arn".to_string(),
79            application: None,
80            description: "desc".to_string(),
81            package_type: "Zip".to_string(),
82            runtime: "python3.12".to_string(),
83            architecture: "X86_64".to_string(),
84            code_size: 1024,
85            code_sha256: "hash".to_string(),
86            memory_mb: 128,
87            timeout_seconds: 30,
88            last_modified: "2024-01-01".to_string(),
89            layers: vec![],
90        };
91
92        let runtime_col = Column::Runtime.to_column();
93        let (text, _) = runtime_col.render(&func);
94        assert_eq!(text, "Python 3.12");
95    }
96
97    #[test]
98    fn test_architecture_formatter_in_table_column() {
99        let func = Function {
100            name: "test-func".to_string(),
101            arn: "arn".to_string(),
102            application: None,
103            description: "desc".to_string(),
104            package_type: "Zip".to_string(),
105            runtime: "python3.12".to_string(),
106            architecture: "X86_64".to_string(),
107            code_size: 1024,
108            code_sha256: "hash".to_string(),
109            memory_mb: 128,
110            timeout_seconds: 30,
111            last_modified: "2024-01-01".to_string(),
112            layers: vec![],
113        };
114
115        let arch_col = Column::Architecture.to_column();
116        let (text, _) = arch_col.render(&func);
117        assert_eq!(text, "x86-64");
118    }
119
120    #[test]
121    fn test_nodejs_runtime_formatting() {
122        assert_eq!(format_runtime("nodejs16x"), "Node.js 16.x");
123        assert_eq!(format_runtime("nodejs18x"), "Node.js 18.x");
124        assert_eq!(format_runtime("nodejs20x"), "Node.js 20.x");
125    }
126
127    #[test]
128    fn test_python_runtime_formatting() {
129        // AWS SDK format
130        assert_eq!(format_runtime("Python38"), "Python 3.8");
131        assert_eq!(format_runtime("Python39"), "Python 3.9");
132        assert_eq!(format_runtime("Python310"), "Python 3.10");
133        assert_eq!(format_runtime("Python311"), "Python 3.11");
134        assert_eq!(format_runtime("Python312"), "Python 3.12");
135
136        // Lowercase with dots
137        assert_eq!(format_runtime("python3.8"), "Python 3.8");
138        assert_eq!(format_runtime("python3.9"), "Python 3.9");
139        assert_eq!(format_runtime("python3.10"), "Python 3.10");
140        assert_eq!(format_runtime("python3.11"), "Python 3.11");
141        assert_eq!(format_runtime("python3.12"), "Python 3.12");
142    }
143
144    #[test]
145    fn test_timeout_formatting() {
146        // Test timeout conversion to min/sec format
147        assert_eq!(300 / 60, 5); // 300 seconds = 5 minutes
148        assert_eq!(300 % 60, 0); // 0 seconds remainder
149        assert_eq!(900 / 60, 15); // 900 seconds = 15 minutes
150        assert_eq!(900 % 60, 0); // 0 seconds remainder
151        assert_eq!(330 / 60, 5); // 330 seconds = 5 minutes
152        assert_eq!(330 % 60, 30); // 30 seconds remainder
153    }
154
155    #[test]
156    fn test_version_column_architecture_formatter() {
157        let version = Version {
158            version: "1".to_string(),
159            aliases: "prod".to_string(),
160            description: "Production version".to_string(),
161            last_modified: "2024-01-01".to_string(),
162            architecture: "X86_64".to_string(),
163        };
164
165        let arch_col = VersionColumn::Architecture.to_column();
166        let (text, _) = arch_col.render(&version);
167        assert_eq!(text, "x86-64");
168    }
169
170    #[test]
171    fn test_version_architecture_formatter_arm() {
172        let version = Version {
173            version: "1".to_string(),
174            aliases: "".to_string(),
175            description: "".to_string(),
176            last_modified: "".to_string(),
177            architecture: "Arm64".to_string(),
178        };
179
180        let arch_col = VersionColumn::Architecture.to_column();
181        let (text, _) = arch_col.render(&version);
182        assert_eq!(text, "arm64");
183    }
184
185    #[test]
186    fn test_version_column_all() {
187        let all = VersionColumn::all();
188        assert_eq!(all.len(), 5);
189        assert!(all.contains(&VersionColumn::Version));
190        assert!(all.contains(&VersionColumn::Aliases));
191        assert!(all.contains(&VersionColumn::Description));
192        assert!(all.contains(&VersionColumn::LastModified));
193        assert!(all.contains(&VersionColumn::Architecture));
194    }
195
196    #[test]
197    fn test_version_column_names() {
198        assert_eq!(VersionColumn::Version.name(), "Version");
199        assert_eq!(VersionColumn::Aliases.name(), "Aliases");
200        assert_eq!(VersionColumn::Description.name(), "Description");
201        assert_eq!(VersionColumn::LastModified.name(), "Last modified");
202        assert_eq!(VersionColumn::Architecture.name(), "Architecture");
203    }
204
205    #[test]
206    fn test_versions_table_sort_config() {
207        // Versions table shows "Version ↓" header (DESC sort)
208        // This is configured in render_detail with:
209        // sort_column: "Version", sort_direction: "DESC"
210        let sort_column = "Version";
211        let sort_direction = "DESC";
212        assert_eq!(sort_column, "Version");
213        assert_eq!(sort_direction, "DESC");
214    }
215
216    #[test]
217    fn test_input_focus_cycling() {
218        use crate::common::InputFocus;
219        use crate::ui::lambda::FILTER_CONTROLS;
220
221        let focus = InputFocus::Filter;
222        assert_eq!(focus.next(&FILTER_CONTROLS), InputFocus::Pagination);
223
224        let focus = InputFocus::Pagination;
225        assert_eq!(focus.next(&FILTER_CONTROLS), InputFocus::Filter);
226
227        let focus = InputFocus::Filter;
228        assert_eq!(focus.prev(&FILTER_CONTROLS), InputFocus::Pagination);
229
230        let focus = InputFocus::Pagination;
231        assert_eq!(focus.prev(&FILTER_CONTROLS), InputFocus::Filter);
232    }
233
234    #[test]
235    #[allow(clippy::useless_vec)]
236    fn test_version_sorting_desc() {
237        let mut versions = vec![
238            Version {
239                version: "1".to_string(),
240                aliases: "".to_string(),
241                description: "".to_string(),
242                last_modified: "".to_string(),
243                architecture: "".to_string(),
244            },
245            Version {
246                version: "10".to_string(),
247                aliases: "".to_string(),
248                description: "".to_string(),
249                last_modified: "".to_string(),
250                architecture: "".to_string(),
251            },
252            Version {
253                version: "2".to_string(),
254                aliases: "".to_string(),
255                description: "".to_string(),
256                last_modified: "".to_string(),
257                architecture: "".to_string(),
258            },
259        ];
260
261        // Sort DESC by version number
262        versions.sort_by(|a, b| {
263            let a_num = a.version.parse::<i32>().unwrap_or(0);
264            let b_num = b.version.parse::<i32>().unwrap_or(0);
265            b_num.cmp(&a_num)
266        });
267
268        assert_eq!(versions[0].version, "10");
269        assert_eq!(versions[1].version, "2");
270        assert_eq!(versions[2].version, "1");
271    }
272
273    #[test]
274    fn test_version_sorting_with_36_versions() {
275        let mut versions: Vec<Version> = (1..=36)
276            .map(|i| Version {
277                version: i.to_string(),
278                aliases: "".to_string(),
279                description: "".to_string(),
280                last_modified: "".to_string(),
281                architecture: "".to_string(),
282            })
283            .collect();
284
285        // Sort DESC by version number
286        versions.sort_by(|a, b| {
287            let a_num = a.version.parse::<i32>().unwrap_or(0);
288            let b_num = b.version.parse::<i32>().unwrap_or(0);
289            b_num.cmp(&a_num)
290        });
291
292        // Verify DESC order
293        assert_eq!(versions[0].version, "36");
294        assert_eq!(versions[1].version, "35");
295        assert_eq!(versions[35].version, "1");
296        assert_eq!(versions.len(), 36);
297    }
298}
299
300pub fn console_url_functions(region: &str) -> String {
301    format!(
302        "https://{}.console.aws.amazon.com/lambda/home?region={}#/functions",
303        region, region
304    )
305}
306
307pub fn console_url_function_detail(region: &str, function_name: &str) -> String {
308    format!(
309        "https://{}.console.aws.amazon.com/lambda/home?region={}#/functions/{}",
310        region, region, function_name
311    )
312}
313
314pub fn console_url_function_version(
315    region: &str,
316    function_name: &str,
317    version: &str,
318    detail_tab: &DetailTab,
319) -> String {
320    let tab = match detail_tab {
321        DetailTab::Code => "code",
322        DetailTab::Configuration => "configure",
323        _ => "code",
324    };
325    format!(
326        "https://{}.console.aws.amazon.com/lambda/home?region={}#/functions/{}/versions/{}?tab={}",
327        region, region, function_name, version, tab
328    )
329}
330
331pub fn console_url_applications(region: &str) -> String {
332    format!(
333        "https://{}.console.aws.amazon.com/lambda/home?region={}#/applications",
334        region, region
335    )
336}
337
338pub fn console_url_application_detail(
339    region: &str,
340    app_name: &str,
341    tab: &ApplicationDetailTab,
342) -> String {
343    let tab_param = match tab {
344        ApplicationDetailTab::Overview => "overview",
345        ApplicationDetailTab::Deployments => "deployments",
346    };
347    format!(
348        "https://{}.console.aws.amazon.com/lambda/home?region={}#/applications/{}?tab={}",
349        region, region, app_name, tab_param
350    )
351}
352
353#[derive(Debug, Clone)]
354pub struct Function {
355    pub name: String,
356    pub arn: String,
357    pub application: Option<String>,
358    pub description: String,
359    pub package_type: String,
360    pub runtime: String,
361    pub architecture: String,
362    pub code_size: i64,
363    pub code_sha256: String,
364    pub memory_mb: i32,
365    pub timeout_seconds: i32,
366    pub last_modified: String,
367    pub layers: Vec<Layer>,
368}
369
370#[derive(Debug, Clone)]
371pub struct Layer {
372    pub arn: String,
373    pub code_size: i64,
374}
375
376#[derive(Debug, Clone)]
377pub struct Version {
378    pub version: String,
379    pub aliases: String,
380    pub description: String,
381    pub last_modified: String,
382    pub architecture: String,
383}
384
385#[derive(Debug, Clone)]
386pub struct Alias {
387    pub name: String,
388    pub versions: String,
389    pub description: String,
390}
391
392#[derive(Debug, Clone)]
393pub struct Application {
394    pub name: String,
395    pub arn: String,
396    pub description: String,
397    pub status: String,
398    pub last_modified: String,
399}
400
401#[derive(Debug, Clone, Copy, PartialEq)]
402pub enum Column {
403    Name,
404    Description,
405    PackageType,
406    Runtime,
407    Architecture,
408    CodeSize,
409    MemoryMb,
410    TimeoutSeconds,
411    LastModified,
412}
413
414impl Column {
415    pub fn name(&self) -> &'static str {
416        match self {
417            Column::Name => "Function name",
418            Column::Description => "Description",
419            Column::PackageType => "Package type",
420            Column::Runtime => "Runtime",
421            Column::Architecture => "Architecture",
422            Column::CodeSize => "Code size",
423            Column::MemoryMb => "Memory (MB)",
424            Column::TimeoutSeconds => "Timeout (s)",
425            Column::LastModified => "Last modified",
426        }
427    }
428
429    pub fn all() -> Vec<Column> {
430        vec![
431            Column::Name,
432            Column::Description,
433            Column::PackageType,
434            Column::Runtime,
435            Column::Architecture,
436            Column::CodeSize,
437            Column::MemoryMb,
438            Column::TimeoutSeconds,
439            Column::LastModified,
440        ]
441    }
442
443    pub fn to_column(&self) -> Box<dyn TableColumn<Function>> {
444        struct FunctionColumn {
445            variant: Column,
446        }
447
448        impl TableColumn<Function> for FunctionColumn {
449            fn name(&self) -> &str {
450                self.variant.name()
451            }
452
453            fn width(&self) -> u16 {
454                self.variant.name().len().max(match self.variant {
455                    Column::Name => 30,
456                    Column::Description => 40,
457                    Column::Runtime => 20,
458                    Column::LastModified => UTC_TIMESTAMP_WIDTH as usize,
459                    _ => 0,
460                }) as u16
461            }
462
463            fn render(&self, item: &Function) -> (String, Style) {
464                let text = match self.variant {
465                    Column::Name => item.name.clone(),
466                    Column::Description => item.description.clone(),
467                    Column::PackageType => item.package_type.clone(),
468                    Column::Runtime => format_runtime(&item.runtime),
469                    Column::Architecture => format_architecture(&item.architecture),
470                    Column::CodeSize => format_bytes(item.code_size),
471                    Column::MemoryMb => item.memory_mb.to_string(),
472                    Column::TimeoutSeconds => item.timeout_seconds.to_string(),
473                    Column::LastModified => item.last_modified.clone(),
474                };
475                (text, Style::default())
476            }
477        }
478
479        Box::new(FunctionColumn { variant: *self })
480    }
481}
482
483#[derive(Debug, Clone, Copy, PartialEq)]
484pub enum ApplicationColumn {
485    Name,
486    Description,
487    Status,
488    LastModified,
489}
490
491impl ApplicationColumn {
492    pub fn name(&self) -> &'static str {
493        match self {
494            ApplicationColumn::Name => "Name",
495            ApplicationColumn::Description => "Description",
496            ApplicationColumn::Status => "Status",
497            ApplicationColumn::LastModified => "Last modified",
498        }
499    }
500
501    pub fn all() -> Vec<ApplicationColumn> {
502        vec![
503            ApplicationColumn::Name,
504            ApplicationColumn::Description,
505            ApplicationColumn::Status,
506            ApplicationColumn::LastModified,
507        ]
508    }
509
510    pub fn to_column(&self) -> Box<dyn crate::ui::table::Column<Application>> {
511        struct ApplicationColumnImpl {
512            variant: ApplicationColumn,
513        }
514
515        impl crate::ui::table::Column<Application> for ApplicationColumnImpl {
516            fn name(&self) -> &str {
517                self.variant.name()
518            }
519
520            fn width(&self) -> u16 {
521                match self.variant {
522                    ApplicationColumn::Name => 40,
523                    ApplicationColumn::Description => 50,
524                    ApplicationColumn::Status => 20,
525                    ApplicationColumn::LastModified => UTC_TIMESTAMP_WIDTH,
526                }
527            }
528
529            fn render(&self, item: &Application) -> (String, Style) {
530                use ratatui::prelude::{Color, Style};
531                match self.variant {
532                    ApplicationColumn::Name => (item.name.clone(), Style::default()),
533                    ApplicationColumn::Description => (item.description.clone(), Style::default()),
534                    ApplicationColumn::Status => {
535                        let status_upper = item.status.to_uppercase();
536                        let (text, color) = if status_upper.contains("UPDATE_COMPLETE") {
537                            ("✅ Update complete", Color::Green)
538                        } else if status_upper.contains("CREATE_COMPLETE") {
539                            ("✅ Create complete", Color::Green)
540                        } else {
541                            (item.status.as_str(), Color::White)
542                        };
543                        (text.to_string(), Style::default().fg(color))
544                    }
545                    ApplicationColumn::LastModified => {
546                        (item.last_modified.clone(), Style::default())
547                    }
548                }
549            }
550        }
551
552        Box::new(ApplicationColumnImpl { variant: *self })
553    }
554}
555
556impl ColumnTrait for Column {
557    fn name(&self) -> &'static str {
558        self.name()
559    }
560}
561
562impl ColumnTrait for ApplicationColumn {
563    fn name(&self) -> &'static str {
564        self.name()
565    }
566}
567
568#[derive(Debug, Clone, Copy, PartialEq)]
569pub enum VersionColumn {
570    Version,
571    Aliases,
572    Description,
573    LastModified,
574    Architecture,
575}
576
577impl VersionColumn {
578    pub fn name(&self) -> &'static str {
579        match self {
580            VersionColumn::Version => "Version",
581            VersionColumn::Aliases => "Aliases",
582            VersionColumn::Description => "Description",
583            VersionColumn::LastModified => "Last modified",
584            VersionColumn::Architecture => "Architecture",
585        }
586    }
587
588    pub fn all() -> Vec<VersionColumn> {
589        vec![
590            VersionColumn::Version,
591            VersionColumn::Aliases,
592            VersionColumn::Description,
593            VersionColumn::LastModified,
594            VersionColumn::Architecture,
595        ]
596    }
597
598    pub fn to_column(&self) -> Box<dyn crate::ui::table::Column<Version>> {
599        struct VersionCol {
600            variant: VersionColumn,
601        }
602
603        impl crate::ui::table::Column<Version> for VersionCol {
604            fn name(&self) -> &str {
605                self.variant.name()
606            }
607
608            fn width(&self) -> u16 {
609                match self.variant {
610                    VersionColumn::Version => 10,
611                    VersionColumn::Aliases => 20,
612                    VersionColumn::Description => 40,
613                    VersionColumn::LastModified => UTC_TIMESTAMP_WIDTH,
614                    VersionColumn::Architecture => 15,
615                }
616            }
617
618            fn render(&self, item: &Version) -> (String, Style) {
619                let text = match self.variant {
620                    VersionColumn::Version => item.version.clone(),
621                    VersionColumn::Aliases => item.aliases.clone(),
622                    VersionColumn::Description => item.description.clone(),
623                    VersionColumn::LastModified => item.last_modified.clone(),
624                    VersionColumn::Architecture => format_architecture(&item.architecture),
625                };
626                (text, ratatui::style::Style::default())
627            }
628        }
629
630        Box::new(VersionCol { variant: *self })
631    }
632}
633
634impl ColumnTrait for VersionColumn {
635    fn name(&self) -> &'static str {
636        self.name()
637    }
638}
639
640#[derive(Debug, Clone, Copy, PartialEq)]
641pub enum AliasColumn {
642    Name,
643    Versions,
644    Description,
645}
646
647impl AliasColumn {
648    pub fn name(&self) -> &'static str {
649        match self {
650            AliasColumn::Name => "Name",
651            AliasColumn::Versions => "Versions",
652            AliasColumn::Description => "Description",
653        }
654    }
655
656    pub fn all() -> Vec<AliasColumn> {
657        vec![
658            AliasColumn::Name,
659            AliasColumn::Versions,
660            AliasColumn::Description,
661        ]
662    }
663
664    pub fn to_column(&self) -> Box<dyn crate::ui::table::Column<Alias>> {
665        struct AliasCol {
666            variant: AliasColumn,
667        }
668
669        impl crate::ui::table::Column<Alias> for AliasCol {
670            fn name(&self) -> &str {
671                self.variant.name()
672            }
673
674            fn width(&self) -> u16 {
675                match self.variant {
676                    AliasColumn::Name => 20,
677                    AliasColumn::Versions => 15,
678                    AliasColumn::Description => 50,
679                }
680            }
681
682            fn render(&self, item: &Alias) -> (String, Style) {
683                let text = match self.variant {
684                    AliasColumn::Name => item.name.clone(),
685                    AliasColumn::Versions => item.versions.clone(),
686                    AliasColumn::Description => item.description.clone(),
687                };
688                (text, ratatui::style::Style::default())
689            }
690        }
691
692        Box::new(AliasCol { variant: *self })
693    }
694}
695
696impl ColumnTrait for AliasColumn {
697    fn name(&self) -> &'static str {
698        self.name()
699    }
700}
701
702#[derive(Debug, Clone, Copy, PartialEq)]
703pub enum LayerColumn {
704    MergeOrder,
705    Name,
706    LayerVersion,
707    CompatibleRuntimes,
708    CompatibleArchitectures,
709    VersionArn,
710}
711
712impl LayerColumn {
713    pub fn name(&self) -> &'static str {
714        match self {
715            LayerColumn::MergeOrder => "Merge order",
716            LayerColumn::Name => "Name",
717            LayerColumn::LayerVersion => "Layer version",
718            LayerColumn::CompatibleRuntimes => "Compatible runtimes",
719            LayerColumn::CompatibleArchitectures => "Compatible architectures",
720            LayerColumn::VersionArn => "Version ARN",
721        }
722    }
723
724    pub fn all() -> Vec<LayerColumn> {
725        vec![
726            LayerColumn::MergeOrder,
727            LayerColumn::Name,
728            LayerColumn::LayerVersion,
729            LayerColumn::CompatibleRuntimes,
730            LayerColumn::CompatibleArchitectures,
731            LayerColumn::VersionArn,
732        ]
733    }
734}
735
736impl ColumnTrait for LayerColumn {
737    fn name(&self) -> &'static str {
738        self.name()
739    }
740}
741
742#[derive(Debug, Clone, Copy, PartialEq)]
743pub enum DeploymentColumn {
744    Deployment,
745    ResourceType,
746    LastUpdated,
747    Status,
748}
749
750impl DeploymentColumn {
751    pub fn all() -> Vec<Self> {
752        vec![
753            Self::Deployment,
754            Self::ResourceType,
755            Self::LastUpdated,
756            Self::Status,
757        ]
758    }
759
760    pub fn name(&self) -> &'static str {
761        match self {
762            Self::Deployment => "Deployment",
763            Self::ResourceType => "Resource type",
764            Self::LastUpdated => "Last updated time",
765            Self::Status => "Status",
766        }
767    }
768
769    pub fn as_table_column(self) -> Box<dyn TableColumn<Deployment>> {
770        struct DeploymentColumnImpl {
771            variant: DeploymentColumn,
772        }
773
774        impl TableColumn<Deployment> for DeploymentColumnImpl {
775            fn name(&self) -> &str {
776                self.variant.name()
777            }
778
779            fn width(&self) -> u16 {
780                match self.variant {
781                    DeploymentColumn::Deployment => 30,
782                    DeploymentColumn::ResourceType => 20,
783                    DeploymentColumn::LastUpdated => UTC_TIMESTAMP_WIDTH,
784                    DeploymentColumn::Status => 20,
785                }
786            }
787
788            fn render(&self, item: &Deployment) -> (String, Style) {
789                match self.variant {
790                    DeploymentColumn::Deployment => (item.deployment_id.clone(), Style::default()),
791                    DeploymentColumn::ResourceType => {
792                        (item.resource_type.clone(), Style::default())
793                    }
794                    DeploymentColumn::LastUpdated => (item.last_updated.clone(), Style::default()),
795                    DeploymentColumn::Status => {
796                        if item.status == "Succeeded" {
797                            (
798                                format!("✅ {}", item.status),
799                                Style::default().fg(Color::Green),
800                            )
801                        } else {
802                            (item.status.clone(), Style::default())
803                        }
804                    }
805                }
806            }
807        }
808
809        Box::new(DeploymentColumnImpl { variant: self })
810    }
811}
812
813impl ColumnTrait for DeploymentColumn {
814    fn name(&self) -> &'static str {
815        self.name()
816    }
817}
818
819#[derive(Clone, Debug)]
820pub struct Resource {
821    pub logical_id: String,
822    pub physical_id: String,
823    pub resource_type: String,
824    pub last_modified: String,
825}
826
827#[derive(Clone, Debug)]
828pub struct Deployment {
829    pub deployment_id: String,
830    pub resource_type: String,
831    pub last_updated: String,
832    pub status: String,
833}
834
835#[derive(Debug, Clone, Copy, PartialEq)]
836pub enum ResourceColumn {
837    LogicalId,
838    PhysicalId,
839    Type,
840    LastModified,
841}
842
843impl ColumnTrait for ResourceColumn {
844    fn name(&self) -> &'static str {
845        self.name()
846    }
847}
848
849impl ResourceColumn {
850    pub fn all() -> Vec<Self> {
851        vec![
852            Self::LogicalId,
853            Self::PhysicalId,
854            Self::Type,
855            Self::LastModified,
856        ]
857    }
858
859    pub fn name(&self) -> &'static str {
860        match self {
861            Self::LogicalId => "Logical ID",
862            Self::PhysicalId => "Physical ID",
863            Self::Type => "Type",
864            Self::LastModified => "Last modified",
865        }
866    }
867}