Skip to main content

sbom_tools/tui/
app_impl_constructors.rs

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