sbom_tools/diff/changes/
dependencies.rs1use crate::diff::traits::{ChangeComputer, ComponentMatches, DependencyChangeSet};
4use crate::diff::DependencyChange;
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 let mut normalized_old_edges: HashSet<(String, String)> = HashSet::new();
38 for edge in &old.edges {
39 let from = matches
40 .get(&edge.from)
41 .and_then(|v| v.as_ref()).map_or_else(|| edge.from.to_string(), std::string::ToString::to_string);
42 let to = matches
43 .get(&edge.to)
44 .and_then(|v| v.as_ref()).map_or_else(|| edge.to.to_string(), std::string::ToString::to_string);
45 normalized_old_edges.insert((from, to));
46 }
47
48 for edge in &new.edges {
50 let from = edge.from.to_string();
51 let to = edge.to.to_string();
52 if !normalized_old_edges.contains(&(from, to)) {
53 result.added.push(DependencyChange::added(edge));
54 }
55 }
56
57 let mut normalized_new_edges: HashSet<(String, String)> = HashSet::new();
59 for edge in &new.edges {
60 normalized_new_edges.insert((edge.from.to_string(), edge.to.to_string()));
61 }
62
63 for edge in &old.edges {
65 let from = matches
66 .get(&edge.from)
67 .and_then(|v| v.as_ref()).map_or_else(|| edge.from.to_string(), std::string::ToString::to_string);
68 let to = matches
69 .get(&edge.to)
70 .and_then(|v| v.as_ref()).map_or_else(|| edge.to.to_string(), std::string::ToString::to_string);
71
72 if !normalized_new_edges.contains(&(from, to)) {
73 result.removed.push(DependencyChange::removed(edge));
74 }
75 }
76
77 result
78 }
79
80 fn name(&self) -> &'static str {
81 "DependencyChangeComputer"
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_dependency_change_computer_default() {
91 let computer = DependencyChangeComputer::default();
92 assert_eq!(computer.name(), "DependencyChangeComputer");
93 }
94
95 #[test]
96 fn test_empty_sboms() {
97 let computer = DependencyChangeComputer::default();
98 let old = NormalizedSbom::default();
99 let new = NormalizedSbom::default();
100 let matches = ComponentMatches::new();
101
102 let result = computer.compute(&old, &new, &matches);
103 assert!(result.is_empty());
104 }
105}