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