Skip to main content

sbom_tools/diff/
vertex.rs

1//! Diff graph vertex representation.
2
3use crate::model::CanonicalId;
4use std::hash::{Hash, Hasher};
5
6/// Vertex in the diff graph representing an alignment position.
7///
8/// Each vertex represents a position pair (left_pos, right_pos) in the
9/// comparison of two SBOMs.
10#[derive(Debug, Clone)]
11pub struct DiffVertex {
12    /// Position in the old (left) SBOM
13    pub left_pos: Option<CanonicalId>,
14    /// Position in the new (right) SBOM
15    pub right_pos: Option<CanonicalId>,
16    /// Components that were processed together at this vertex
17    pub processed_together: Vec<CanonicalId>,
18}
19
20impl DiffVertex {
21    /// Create a new diff vertex
22    pub fn new(left_pos: Option<CanonicalId>, right_pos: Option<CanonicalId>) -> Self {
23        Self {
24            left_pos,
25            right_pos,
26            processed_together: Vec::new(),
27        }
28    }
29
30    /// Create the start vertex (both positions at beginning)
31    pub fn start() -> Self {
32        Self::new(None, None)
33    }
34
35    /// Check if this is the end vertex (both positions exhausted)
36    pub fn is_end(&self) -> bool {
37        self.left_pos.is_none() && self.right_pos.is_none() && !self.processed_together.is_empty()
38    }
39}
40
41impl PartialEq for DiffVertex {
42    fn eq(&self, other: &Self) -> bool {
43        self.left_pos == other.left_pos && self.right_pos == other.right_pos
44    }
45}
46
47impl Eq for DiffVertex {}
48
49impl Hash for DiffVertex {
50    fn hash<H: Hasher>(&self, state: &mut H) {
51        self.left_pos.hash(state);
52        self.right_pos.hash(state);
53    }
54}
55
56/// Edge in the diff graph representing a diff operation.
57#[derive(Debug, Clone)]
58#[allow(dead_code)]
59pub enum DiffEdge {
60    /// Component was removed from old SBOM
61    ComponentRemoved {
62        component_id: CanonicalId,
63        cost: u32,
64    },
65    /// Component was added in new SBOM
66    ComponentAdded {
67        component_id: CanonicalId,
68        cost: u32,
69    },
70    /// Component version changed
71    VersionChanged {
72        component_id: CanonicalId,
73        old_version: String,
74        new_version: String,
75        cost: u32,
76    },
77    /// Component license changed
78    LicenseChanged {
79        component_id: CanonicalId,
80        cost: u32,
81    },
82    /// Component supplier changed
83    SupplierChanged {
84        component_id: CanonicalId,
85        cost: u32,
86    },
87    /// New vulnerability introduced
88    VulnerabilityIntroduced {
89        component_id: CanonicalId,
90        vuln_id: String,
91        cost: u32,
92    },
93    /// Existing vulnerability resolved
94    VulnerabilityResolved {
95        component_id: CanonicalId,
96        vuln_id: String,
97        reward: i32,
98    },
99    /// Dependency relationship added
100    DependencyAdded {
101        from: CanonicalId,
102        to: CanonicalId,
103        cost: u32,
104    },
105    /// Dependency relationship removed
106    DependencyRemoved {
107        from: CanonicalId,
108        to: CanonicalId,
109        cost: u32,
110    },
111    /// Component unchanged (zero cost transition)
112    ComponentUnchanged { component_id: CanonicalId },
113}
114
115#[allow(dead_code)]
116impl DiffEdge {
117    /// Get the cost of this edge
118    pub fn cost(&self) -> i32 {
119        match self {
120            DiffEdge::ComponentRemoved { cost, .. } => *cost as i32,
121            DiffEdge::ComponentAdded { cost, .. } => *cost as i32,
122            DiffEdge::VersionChanged { cost, .. } => *cost as i32,
123            DiffEdge::LicenseChanged { cost, .. } => *cost as i32,
124            DiffEdge::SupplierChanged { cost, .. } => *cost as i32,
125            DiffEdge::VulnerabilityIntroduced { cost, .. } => *cost as i32,
126            DiffEdge::VulnerabilityResolved { reward, .. } => *reward,
127            DiffEdge::DependencyAdded { cost, .. } => *cost as i32,
128            DiffEdge::DependencyRemoved { cost, .. } => *cost as i32,
129            DiffEdge::ComponentUnchanged { .. } => 0,
130        }
131    }
132
133    /// Check if this edge represents a change (non-zero cost)
134    pub fn is_change(&self) -> bool {
135        !matches!(self, DiffEdge::ComponentUnchanged { .. })
136    }
137}