1use super::DiffResult;
9use crate::model::{NormalizedSbom, VulnerabilityCounts};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct SbomInfo {
20 pub name: String,
22 pub file_path: String,
24 pub format: String,
26 pub component_count: usize,
28 pub dependency_count: usize,
30 pub vulnerability_counts: VulnerabilityCounts,
32 pub timestamp: Option<String>,
34}
35
36impl SbomInfo {
37 pub fn from_sbom(sbom: &NormalizedSbom, name: String, file_path: String) -> Self {
38 Self {
39 name,
40 file_path,
41 format: sbom.document.format.to_string(),
42 component_count: sbom.component_count(),
43 dependency_count: sbom.edges.len(),
44 vulnerability_counts: sbom.vulnerability_counts(),
45 timestamp: Some(sbom.document.created.to_rfc3339()),
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MultiDiffResult {
57 pub baseline: SbomInfo,
59 pub comparisons: Vec<ComparisonResult>,
61 pub summary: MultiDiffSummary,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ComparisonResult {
68 pub target: SbomInfo,
70 pub diff: DiffResult,
72 pub unique_components: Vec<String>,
74 pub divergent_components: Vec<DivergentComponent>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct DivergentComponent {
81 pub id: String,
82 pub name: String,
83 pub baseline_version: Option<String>,
84 pub target_version: String,
85 pub versions_across_targets: HashMap<String, String>,
87 pub divergence_type: DivergenceType,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub enum DivergenceType {
92 VersionMismatch,
94 Added,
96 Removed,
98 LicenseMismatch,
100 SupplierMismatch,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct MultiDiffSummary {
111 pub baseline_component_count: usize,
113 pub universal_components: Vec<String>,
115 pub variable_components: Vec<VariableComponent>,
117 pub inconsistent_components: Vec<InconsistentComponent>,
119 pub deviation_scores: HashMap<String, f64>,
121 pub max_deviation: f64,
123 pub vulnerability_matrix: VulnerabilityMatrix,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct VariableComponent {
130 pub id: String,
131 pub name: String,
132 pub ecosystem: Option<String>,
133 pub version_spread: VersionSpread,
134 pub targets_with_component: Vec<String>,
135 pub security_impact: SecurityImpact,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct VersionSpread {
141 pub baseline: Option<String>,
143 pub min_version: Option<String>,
145 pub max_version: Option<String>,
147 pub unique_versions: Vec<String>,
149 pub is_consistent: bool,
151 pub major_version_spread: u32,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub enum SecurityImpact {
157 Critical,
159 High,
161 Medium,
163 Low,
165}
166
167impl SecurityImpact {
168 pub fn label(&self) -> &'static str {
169 match self {
170 SecurityImpact::Critical => "CRITICAL",
171 SecurityImpact::High => "high",
172 SecurityImpact::Medium => "medium",
173 SecurityImpact::Low => "low",
174 }
175 }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct InconsistentComponent {
181 pub id: String,
182 pub name: String,
183 pub in_baseline: bool,
185 pub present_in: Vec<String>,
187 pub missing_from: Vec<String>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct VulnerabilityMatrix {
194 pub per_sbom: HashMap<String, VulnerabilityCounts>,
196 pub unique_vulnerabilities: HashMap<String, Vec<String>>,
198 pub common_vulnerabilities: Vec<String>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct TimelineResult {
209 pub sboms: Vec<SbomInfo>,
211 pub incremental_diffs: Vec<DiffResult>,
213 pub cumulative_from_first: Vec<DiffResult>,
215 pub evolution_summary: EvolutionSummary,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct EvolutionSummary {
222 pub components_added: Vec<ComponentEvolution>,
224 pub components_removed: Vec<ComponentEvolution>,
226 pub version_history: HashMap<String, Vec<VersionAtPoint>>,
228 pub vulnerability_trend: Vec<VulnerabilitySnapshot>,
230 pub license_changes: Vec<LicenseChange>,
232 pub dependency_trend: Vec<DependencySnapshot>,
234 pub compliance_trend: Vec<ComplianceSnapshot>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct ComponentEvolution {
241 pub id: String,
242 pub name: String,
243 pub first_seen_index: usize,
245 pub first_seen_version: String,
246 pub last_seen_index: Option<usize>,
248 pub current_version: Option<String>,
250 pub version_change_count: usize,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct VersionAtPoint {
257 pub sbom_index: usize,
258 pub sbom_name: String,
259 pub version: Option<String>,
260 pub change_type: VersionChangeType,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264pub enum VersionChangeType {
265 Initial,
266 MajorUpgrade,
267 MinorUpgrade,
268 PatchUpgrade,
269 Downgrade,
270 Unchanged,
271 Removed,
272 Absent,
273}
274
275impl VersionChangeType {
276 pub fn symbol(&self) -> &'static str {
277 match self {
278 VersionChangeType::Initial => "●",
279 VersionChangeType::MajorUpgrade => "⬆",
280 VersionChangeType::MinorUpgrade => "↑",
281 VersionChangeType::PatchUpgrade => "↗",
282 VersionChangeType::Downgrade => "⬇",
283 VersionChangeType::Unchanged => "─",
284 VersionChangeType::Removed => "✗",
285 VersionChangeType::Absent => " ",
286 }
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct ComplianceSnapshot {
293 pub sbom_index: usize,
294 pub sbom_name: String,
295 pub scores: Vec<ComplianceScoreEntry>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct ComplianceScoreEntry {
302 pub standard: String,
303 pub error_count: usize,
304 pub warning_count: usize,
305 pub info_count: usize,
306 pub is_compliant: bool,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct VulnerabilitySnapshot {
312 pub sbom_index: usize,
313 pub sbom_name: String,
314 pub counts: VulnerabilityCounts,
315 pub new_vulnerabilities: Vec<String>,
316 pub resolved_vulnerabilities: Vec<String>,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct LicenseChange {
322 pub sbom_index: usize,
323 pub component_id: String,
324 pub component_name: String,
325 pub old_license: Vec<String>,
326 pub new_license: Vec<String>,
327 pub change_type: LicenseChangeType,
328}
329
330#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
331pub enum LicenseChangeType {
332 MorePermissive,
333 MoreRestrictive,
334 Incompatible,
335 Equivalent,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct DependencySnapshot {
341 pub sbom_index: usize,
342 pub sbom_name: String,
343 pub direct_dependencies: usize,
344 pub transitive_dependencies: usize,
345 pub total_edges: usize,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct MatrixResult {
355 pub sboms: Vec<SbomInfo>,
357 pub diffs: Vec<Option<DiffResult>>,
360 pub similarity_scores: Vec<f64>,
363 pub clustering: Option<SbomClustering>,
365}
366
367impl MatrixResult {
368 pub fn get_diff(&self, i: usize, j: usize) -> Option<&DiffResult> {
370 if i == j {
371 return None;
372 }
373 let (a, b) = if i < j { (i, j) } else { (j, i) };
374 let idx = self.matrix_index(a, b);
375 self.diffs.get(idx).and_then(|d| d.as_ref())
376 }
377
378 pub fn get_similarity(&self, i: usize, j: usize) -> f64 {
380 if i == j {
381 return 1.0;
382 }
383 let (a, b) = if i < j { (i, j) } else { (j, i) };
384 let idx = self.matrix_index(a, b);
385 self.similarity_scores.get(idx).copied().unwrap_or(0.0)
386 }
387
388 fn matrix_index(&self, i: usize, j: usize) -> usize {
390 let n = self.sboms.len();
391 i * (2 * n - i - 1) / 2 + (j - i - 1)
393 }
394
395 pub fn num_pairs(&self) -> usize {
397 let n = self.sboms.len();
398 n * (n - 1) / 2
399 }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct SbomClustering {
405 pub clusters: Vec<SbomCluster>,
407 pub outliers: Vec<usize>,
409 pub algorithm: String,
411 pub threshold: f64,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct SbomCluster {
418 pub members: Vec<usize>,
420 pub centroid_index: usize,
422 pub internal_similarity: f64,
424 pub label: Option<String>,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize)]
434pub struct IncrementalChange {
435 pub from_index: usize,
436 pub to_index: usize,
437 pub from_name: String,
438 pub to_name: String,
439 pub components_added: usize,
440 pub components_removed: usize,
441 pub components_modified: usize,
442 pub vulnerabilities_introduced: usize,
443 pub vulnerabilities_resolved: usize,
444}
445
446impl IncrementalChange {
447 pub fn from_diff(
448 from_idx: usize,
449 to_idx: usize,
450 from_name: &str,
451 to_name: &str,
452 diff: &DiffResult,
453 ) -> Self {
454 Self {
455 from_index: from_idx,
456 to_index: to_idx,
457 from_name: from_name.to_string(),
458 to_name: to_name.to_string(),
459 components_added: diff.summary.components_added,
460 components_removed: diff.summary.components_removed,
461 components_modified: diff.summary.components_modified,
462 vulnerabilities_introduced: diff.summary.vulnerabilities_introduced,
463 vulnerabilities_resolved: diff.summary.vulnerabilities_resolved,
464 }
465 }
466}