sbom_tools/diff/changes/
dependencies.rs1use crate::diff::DependencyChange;
4use crate::diff::traits::{ChangeComputer, ComponentMatches, DependencyChangeSet};
5use crate::model::NormalizedSbom;
6use std::collections::HashSet;
7
8pub struct DependencyChangeComputer;
10
11impl DependencyChangeComputer {
12 #[must_use]
14 pub const fn new() -> Self {
15 Self
16 }
17}
18
19impl Default for DependencyChangeComputer {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl ChangeComputer for DependencyChangeComputer {
26 type ChangeSet = DependencyChangeSet;
27
28 fn compute(
29 &self,
30 old: &NormalizedSbom,
31 new: &NormalizedSbom,
32 matches: &ComponentMatches,
33 ) -> DependencyChangeSet {
34 let mut result = DependencyChangeSet::new();
35
36 type EdgeKey = (String, String, String, Option<String>);
38
39 let mut normalized_old_edges: HashSet<EdgeKey> = HashSet::new();
41 for edge in &old.edges {
42 let from = matches
43 .get(&edge.from)
44 .and_then(|v| v.as_ref())
45 .map_or_else(|| edge.from.to_string(), std::string::ToString::to_string);
46 let to = matches
47 .get(&edge.to)
48 .and_then(|v| v.as_ref())
49 .map_or_else(|| edge.to.to_string(), std::string::ToString::to_string);
50 normalized_old_edges.insert((
51 from,
52 to,
53 edge.relationship.to_string(),
54 edge.scope.as_ref().map(std::string::ToString::to_string),
55 ));
56 }
57
58 for edge in &new.edges {
60 let key: EdgeKey = (
61 edge.from.to_string(),
62 edge.to.to_string(),
63 edge.relationship.to_string(),
64 edge.scope.as_ref().map(std::string::ToString::to_string),
65 );
66 if !normalized_old_edges.contains(&key) {
67 result.added.push(DependencyChange::added(edge));
68 }
69 }
70
71 let mut normalized_new_edges: HashSet<EdgeKey> = HashSet::new();
73 for edge in &new.edges {
74 normalized_new_edges.insert((
75 edge.from.to_string(),
76 edge.to.to_string(),
77 edge.relationship.to_string(),
78 edge.scope.as_ref().map(std::string::ToString::to_string),
79 ));
80 }
81
82 for edge in &old.edges {
84 let from = matches
85 .get(&edge.from)
86 .and_then(|v| v.as_ref())
87 .map_or_else(|| edge.from.to_string(), std::string::ToString::to_string);
88 let to = matches
89 .get(&edge.to)
90 .and_then(|v| v.as_ref())
91 .map_or_else(|| edge.to.to_string(), std::string::ToString::to_string);
92
93 let key: EdgeKey = (
94 from,
95 to,
96 edge.relationship.to_string(),
97 edge.scope.as_ref().map(std::string::ToString::to_string),
98 );
99 if !normalized_new_edges.contains(&key) {
100 result.removed.push(DependencyChange::removed(edge));
101 }
102 }
103
104 result
105 }
106
107 fn name(&self) -> &'static str {
108 "DependencyChangeComputer"
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_dependency_change_computer_default() {
118 let computer = DependencyChangeComputer;
119 assert_eq!(computer.name(), "DependencyChangeComputer");
120 }
121
122 #[test]
123 fn test_empty_sboms() {
124 let computer = DependencyChangeComputer;
125 let old = NormalizedSbom::default();
126 let new = NormalizedSbom::default();
127 let matches = ComponentMatches::new();
128
129 let result = computer.compute(&old, &new, &matches);
130 assert!(result.is_empty());
131 }
132}