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 pub fn new() -> Self {
14 Self
15 }
16}
17
18impl Default for DependencyChangeComputer {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl ChangeComputer for DependencyChangeComputer {
25 type ChangeSet = DependencyChangeSet;
26
27 fn compute(
28 &self,
29 old: &NormalizedSbom,
30 new: &NormalizedSbom,
31 matches: &ComponentMatches,
32 ) -> DependencyChangeSet {
33 let mut result = DependencyChangeSet::new();
34
35 let mut normalized_old_edges: HashSet<(String, String)> = HashSet::new();
37 for edge in &old.edges {
38 let from = matches
39 .get(&edge.from)
40 .and_then(|v| v.as_ref())
41 .map(|id| id.to_string())
42 .unwrap_or_else(|| edge.from.to_string());
43 let to = matches
44 .get(&edge.to)
45 .and_then(|v| v.as_ref())
46 .map(|id| id.to_string())
47 .unwrap_or_else(|| edge.to.to_string());
48 normalized_old_edges.insert((from, to));
49 }
50
51 for edge in &new.edges {
53 let from = edge.from.to_string();
54 let to = edge.to.to_string();
55 if !normalized_old_edges.contains(&(from, to)) {
56 result.added.push(DependencyChange::added(edge));
57 }
58 }
59
60 let mut normalized_new_edges: HashSet<(String, String)> = HashSet::new();
62 for edge in &new.edges {
63 normalized_new_edges.insert((edge.from.to_string(), edge.to.to_string()));
64 }
65
66 for edge in &old.edges {
68 let from = matches
69 .get(&edge.from)
70 .and_then(|v| v.as_ref())
71 .map(|id| id.to_string())
72 .unwrap_or_else(|| edge.from.to_string());
73 let to = matches
74 .get(&edge.to)
75 .and_then(|v| v.as_ref())
76 .map(|id| id.to_string())
77 .unwrap_or_else(|| edge.to.to_string());
78
79 if !normalized_new_edges.contains(&(from, to)) {
80 result.removed.push(DependencyChange::removed(edge));
81 }
82 }
83
84 result
85 }
86
87 fn name(&self) -> &str {
88 "DependencyChangeComputer"
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn test_dependency_change_computer_default() {
98 let computer = DependencyChangeComputer::default();
99 assert_eq!(computer.name(), "DependencyChangeComputer");
100 }
101
102 #[test]
103 fn test_empty_sboms() {
104 let computer = DependencyChangeComputer::default();
105 let old = NormalizedSbom::default();
106 let new = NormalizedSbom::default();
107 let matches = ComponentMatches::new();
108
109 let result = computer.compute(&old, &new, &matches);
110 assert!(result.is_empty());
111 }
112}