Skip to main content

sbom_tools/tui/
app_impl_constructors.rs

1//! Constructor methods for App.
2
3use super::app::{App, AppMode, AppOverlays, DataContext, TabKind, TabStates};
4use super::app_states::{
5    ComponentsState, DependenciesState, GraphChangesState, LicensesState, MatrixState,
6    MultiDiffState, NavigationContext, QualityState, SideBySideState, SourceDiffState,
7    TimelineState, VulnerabilitiesState,
8};
9use crate::diff::{DiffResult, MatrixResult, MultiDiffResult, TimelineResult};
10use crate::model::NormalizedSbom;
11use crate::quality::{ComplianceChecker, ComplianceLevel, QualityScorer, ScoringProfile};
12
13impl App {
14    /// Shared default initialization for all mode-independent fields.
15    /// Mode-specific fields (mode, `active_tab`, and data fields) must be set by the caller.
16    fn base(mode: AppMode, components_len: usize, vulns_len: usize) -> Self {
17        Self {
18            mode,
19            active_tab: crate::config::TuiPreferences::load()
20                .last_tab
21                .as_deref()
22                .and_then(TabKind::from_str_opt)
23                .unwrap_or(TabKind::Summary),
24            data: DataContext {
25                diff_result: None,
26                old_sbom: None,
27                new_sbom: None,
28                sbom: None,
29                multi_diff_result: None,
30                timeline_result: None,
31                matrix_result: None,
32                old_sbom_index: None,
33                new_sbom_index: None,
34                sbom_index: None,
35                old_quality: None,
36                new_quality: None,
37                quality_report: None,
38                old_cra_compliance: None,
39                new_cra_compliance: None,
40                old_compliance_results: None,
41                new_compliance_results: None,
42                matching_threshold: 0.85,
43                #[cfg(feature = "enrichment")]
44                enrichment_stats_old: None,
45                #[cfg(feature = "enrichment")]
46                enrichment_stats_new: None,
47            },
48            tabs: TabStates {
49                components: ComponentsState::new(components_len),
50                dependencies: DependenciesState::new(),
51                licenses: LicensesState::new(),
52                vulnerabilities: VulnerabilitiesState::new(vulns_len),
53                quality: QualityState::new(),
54                graph_changes: GraphChangesState::new(),
55                side_by_side: SideBySideState::new(),
56                diff_compliance: crate::tui::app_states::DiffComplianceState::new(),
57                multi_diff: MultiDiffState::new(),
58                timeline: TimelineState::new(),
59                matrix: MatrixState::new(),
60                source: SourceDiffState::new("", ""),
61            },
62            overlays: AppOverlays::new(),
63            should_quit: false,
64            status_message: None,
65            status_sticky: false,
66            tick: 0,
67            last_export_path: None,
68            navigation_ctx: NavigationContext::new(),
69            security_cache: crate::tui::security::SecurityAnalysisCache::new(),
70            compliance_state: crate::tui::app_states::PolicyComplianceState::new(),
71            export_template: None,
72            quality_view: Some(crate::tui::view_states::QualityView::new()),
73        }
74    }
75
76    /// Create a new app for diff mode
77    #[must_use]
78    pub fn new_diff(
79        diff_result: DiffResult,
80        old_sbom: NormalizedSbom,
81        new_sbom: NormalizedSbom,
82        old_raw: &str,
83        new_raw: &str,
84    ) -> Self {
85        let components_len = diff_result.components.total();
86        let vulns_len = diff_result.vulnerabilities.introduced.len()
87            + diff_result.vulnerabilities.resolved.len()
88            + diff_result.vulnerabilities.persistent.len();
89
90        // Calculate quality reports for both SBOMs
91        let scorer = QualityScorer::new(ScoringProfile::Standard);
92        let old_quality = Some(scorer.score(&old_sbom));
93        let new_quality = Some(scorer.score(&new_sbom));
94
95        // Compute only CRA Phase2 for the summary card; full compliance is lazy
96        let old_cra_compliance =
97            Some(ComplianceChecker::new(ComplianceLevel::CraPhase2).check(&old_sbom));
98        let new_cra_compliance =
99            Some(ComplianceChecker::new(ComplianceLevel::CraPhase2).check(&new_sbom));
100
101        // Build indexes for fast lookups (O(1) instead of O(n))
102        let old_sbom_index = Some(old_sbom.build_index());
103        let new_sbom_index = Some(new_sbom.build_index());
104
105        let mut app = Self::base(AppMode::Diff, components_len, vulns_len);
106        app.tabs.source = SourceDiffState::new(old_raw, new_raw);
107        app.tabs.source.populate_annotations(&diff_result);
108        app.data.diff_result = Some(diff_result);
109        app.data.old_sbom = Some(old_sbom);
110        app.data.new_sbom = Some(new_sbom);
111        app.data.old_quality = old_quality;
112        app.data.new_quality = new_quality;
113        app.data.old_cra_compliance = old_cra_compliance;
114        app.data.new_cra_compliance = new_cra_compliance;
115        app.data.old_sbom_index = old_sbom_index;
116        app.data.new_sbom_index = new_sbom_index;
117        app
118    }
119
120    /// Set enrichment statistics for the diff mode
121    #[must_use]
122    #[cfg(feature = "enrichment")]
123    pub fn with_enrichment_stats(
124        mut self,
125        old_stats: Option<crate::enrichment::EnrichmentStats>,
126        new_stats: Option<crate::enrichment::EnrichmentStats>,
127    ) -> Self {
128        self.data.enrichment_stats_old = old_stats;
129        self.data.enrichment_stats_new = new_stats;
130        self
131    }
132
133    /// Get combined enrichment stats for display
134    #[cfg(feature = "enrichment")]
135    #[must_use]
136    pub fn combined_enrichment_stats(&self) -> Option<crate::enrichment::EnrichmentStats> {
137        match (
138            &self.data.enrichment_stats_old,
139            &self.data.enrichment_stats_new,
140        ) {
141            (Some(old), Some(new)) => {
142                let mut combined = old.clone();
143                combined.merge(new);
144                Some(combined)
145            }
146            (Some(stats), None) | (None, Some(stats)) => Some(stats.clone()),
147            (None, None) => None,
148        }
149    }
150
151    /// Create a new app for view mode
152    #[must_use]
153    pub fn new_view(sbom: NormalizedSbom) -> Self {
154        let components_len = sbom.component_count();
155        let vulns_len = sbom.all_vulnerabilities().len();
156
157        // Calculate quality report
158        let scorer = QualityScorer::new(ScoringProfile::Standard);
159        let quality_report = Some(scorer.score(&sbom));
160
161        // Build index for fast lookups
162        let sbom_index = Some(sbom.build_index());
163
164        let mut app = Self::base(AppMode::View, components_len, vulns_len);
165        app.data.sbom = Some(sbom);
166        app.data.quality_report = quality_report;
167        app.data.sbom_index = sbom_index;
168        app
169    }
170
171    /// Create a new app for multi-diff mode
172    #[must_use]
173    pub fn new_multi_diff(result: MultiDiffResult) -> Self {
174        let target_count = result.comparisons.len();
175
176        let mut app = Self::base(AppMode::MultiDiff, 0, 0);
177        app.data.multi_diff_result = Some(result);
178        app.tabs.multi_diff = MultiDiffState::new_with_targets(target_count);
179        app
180    }
181
182    /// Create a new app for timeline mode
183    #[must_use]
184    pub fn new_timeline(result: TimelineResult) -> Self {
185        let version_count = result.sboms.len();
186
187        let mut app = Self::base(AppMode::Timeline, 0, 0);
188        app.data.timeline_result = Some(result);
189        app.tabs.timeline = TimelineState::new_with_versions(version_count);
190        app
191    }
192
193    /// Create a new app for matrix mode
194    #[must_use]
195    pub fn new_matrix(result: MatrixResult) -> Self {
196        let sbom_count = result.sboms.len();
197
198        let mut app = Self::base(AppMode::Matrix, 0, 0);
199        app.data.matrix_result = Some(result);
200        app.tabs.matrix = MatrixState::new_with_size(sbom_count);
201        app
202    }
203}