use crate::scan::ScanReport;
use crate::scan::inventory::PathEntry;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DiffReport {
pub added: Vec<PathEntry>,
pub modified: Vec<Change>,
pub removed: Vec<PathEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Change {
pub path: PathBuf,
pub prev_sha256: String,
pub curr_sha256: String,
pub prev_size: u64,
pub curr_size: u64,
}
pub fn compute(prev: &ScanReport, curr: &ScanReport) -> DiffReport {
let prev_map: HashMap<&PathBuf, &PathEntry> = prev.paths.iter().map(|e| (&e.path, e)).collect();
let curr_map: HashMap<&PathBuf, &PathEntry> = curr.paths.iter().map(|e| (&e.path, e)).collect();
let mut added = Vec::new();
let mut modified = Vec::new();
let mut removed = Vec::new();
for (path, curr_entry) in &curr_map {
match prev_map.get(path) {
None => added.push((*curr_entry).clone()),
Some(prev_entry) if prev_entry.sha256 != curr_entry.sha256 => {
modified.push(Change {
path: (*path).clone(),
prev_sha256: prev_entry.sha256.clone(),
curr_sha256: curr_entry.sha256.clone(),
prev_size: prev_entry.size,
curr_size: curr_entry.size,
});
}
_ => {}
}
}
for (path, prev_entry) in &prev_map {
if !curr_map.contains_key(path) {
removed.push((*prev_entry).clone());
}
}
added.sort_by(|a, b| a.path.cmp(&b.path));
modified.sort_by(|a, b| a.path.cmp(&b.path));
removed.sort_by(|a, b| a.path.cmp(&b.path));
DiffReport {
added,
modified,
removed,
}
}
impl DiffReport {
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.modified.is_empty() && self.removed.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(p: &str, sha: &str, size: u64) -> PathEntry {
PathEntry {
path: p.into(),
category: "x".into(),
sha256: sha.repeat(8),
size,
}
}
fn report(entries: Vec<PathEntry>) -> ScanReport {
ScanReport {
scanned_at: chrono::Utc::now(),
paths: entries,
}
}
#[test]
fn empty_diff() {
let r = report(vec![entry("/a", "11111111", 1)]);
let d = compute(&r, &r);
assert!(d.is_empty());
}
#[test]
fn detects_added_modified_removed() {
let prev = report(vec![entry("/a", "11111111", 1), entry("/b", "22222222", 2)]);
let curr = report(vec![
entry("/a", "33333333", 1), entry("/c", "44444444", 3), ]);
let d = compute(&prev, &curr);
assert_eq!(d.added.len(), 1);
assert_eq!(d.added[0].path, PathBuf::from("/c"));
assert_eq!(d.modified.len(), 1);
assert_eq!(d.modified[0].path, PathBuf::from("/a"));
assert_eq!(d.removed.len(), 1);
assert_eq!(d.removed[0].path, PathBuf::from("/b"));
}
}