1use rustc_hash::FxHashSet;
2use serde::{Deserialize, Serialize};
3
4use crate::{Capability, CapabilityFinding, CapabilityProfile};
5
6#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
8pub struct CapabilityDiff {
9 pub added: Box<[CapabilityFinding]>,
11 pub removed: Box<[CapabilityFinding]>,
13 pub new_capabilities: Box<[Capability]>,
15 pub dropped_capabilities: Box<[Capability]>,
17}
18
19impl CapabilityDiff {
20 pub fn is_empty(&self) -> bool {
22 self.added.is_empty()
23 && self.removed.is_empty()
24 && self.new_capabilities.is_empty()
25 && self.dropped_capabilities.is_empty()
26 }
27
28 pub fn compute(old: &CapabilityProfile, new: &CapabilityProfile) -> Self {
30 let old_set: FxHashSet<&CapabilityFinding> = old.findings.iter().collect();
31 let new_set: FxHashSet<&CapabilityFinding> = new.findings.iter().collect();
32
33 let added: Box<[CapabilityFinding]> = new
34 .findings
35 .iter()
36 .filter(|f| !old_set.contains(f))
37 .cloned()
38 .collect();
39
40 let removed: Box<[CapabilityFinding]> = old
41 .findings
42 .iter()
43 .filter(|f| !new_set.contains(f))
44 .cloned()
45 .collect();
46
47 let old_caps: FxHashSet<Capability> = old.findings.iter().map(|f| f.capability).collect();
48 let new_caps: FxHashSet<Capability> = new.findings.iter().map(|f| f.capability).collect();
49
50 let mut new_capabilities: Vec<Capability> =
51 new_caps.difference(&old_caps).copied().collect();
52 new_capabilities.sort_unstable();
53
54 let mut dropped_capabilities: Vec<Capability> =
55 old_caps.difference(&new_caps).copied().collect();
56 dropped_capabilities.sort_unstable();
57
58 Self {
59 added,
60 removed,
61 new_capabilities: new_capabilities.into_boxed_slice(),
62 dropped_capabilities: dropped_capabilities.into_boxed_slice(),
63 }
64 }
65}