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