From 22a6e68c7cf2e2e314ba3d0f154452cbf14c6d1c Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Mon, 19 May 2025 11:45:49 +0200 Subject: [PATCH] feat(backup): implement mutation methods and specialized PartialEq for State --- backup/src/delta.rs | 24 ++------------ backup/src/state.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/backup/src/delta.rs b/backup/src/delta.rs index dde906b..85b36a0 100644 --- a/backup/src/delta.rs +++ b/backup/src/delta.rs @@ -1,8 +1,4 @@ -use std::{ - borrow::Borrow, - fmt, - path::{Path, PathBuf}, -}; +use std::{borrow::Borrow, fmt, path::PathBuf}; use serde::{Deserialize, Serialize}; @@ -153,14 +149,7 @@ impl Delta { I: IntoIterator, I::Item: Into, { - let dir: PathBuf = dir.into(); - let files = files.into_iter().map(Into::into); - - if let Some(dir_files) = self.added.get_mut(&dir) { - dir_files.extend(files); - } else { - self.added.insert(dir, files.collect()); - } + self.added.append_dir(dir, files); } /// Wrapper around the `append_added` method for a builder-style construction of delta's @@ -179,14 +168,7 @@ impl Delta { I: IntoIterator, I::Item: Into, { - let dir: PathBuf = dir.into(); - let files = files.into_iter().map(Into::into); - - if let Some(dir_files) = self.removed.get_mut(&dir) { - dir_files.extend(files); - } else { - self.removed.insert(dir, files.collect()); - } + self.removed.append_dir(dir, files); } /// Wrapper around the `append_removed` method for a builder-style construction of delta's diff --git a/backup/src/state.rs b/backup/src/state.rs index c0f66b9..b94370f 100644 --- a/backup/src/state.rs +++ b/backup/src/state.rs @@ -11,7 +11,7 @@ use crate::Delta; /// Struct that represents a current state for a backup. This struct acts as a smart pointer around /// a HashMap. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct State(HashMap>); impl State { @@ -49,8 +49,52 @@ impl State { pub fn is_empty(&self) -> bool { self.0.values().all(|s| s.is_empty()) } + + pub fn append_dir(&mut self, dir: impl Into, files: I) + where + I: IntoIterator, + I::Item: Into, + { + let dir = dir.into(); + let files = files.into_iter().map(Into::into); + + if let Some(dir_files) = self.0.get_mut(&dir) { + dir_files.extend(files); + } else { + self.0.insert(dir, files.collect()); + } + } + + pub fn with_dir(mut self, dir: impl Into, files: I) -> Self + where + I: IntoIterator, + I::Item: Into, + { + self.append_dir(dir, files); + self + } } +impl PartialEq for State { + fn eq(&self, other: &Self) -> bool { + let self_non_empty = self.0.values().filter(|files| !files.is_empty()).count(); + let other_non_empty = other.0.values().filter(|files| !files.is_empty()).count(); + + if self_non_empty != other_non_empty { + return false; + } + + // If both states have the same number of non-empty directories, then comparing each + // directory of one with the other will only be true if their list of non-empty directories + // is identical. + self.0 + .iter() + .all(|(dir, files)| files.is_empty() || other.0.get(dir).map_or(false, |v| v == files)) + } +} + +impl Eq for State {} + impl From for State where T: IntoIterator, @@ -86,3 +130,33 @@ impl DerefMut for State { &mut self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_eq() { + let a = State::default().with_dir("dir1", ["file1", "file2"]); + let b = State::default().with_dir("dir1", ["file1", "file2"]); + + assert_eq!(a, b); + + let b = b.with_dir("dir2", ["file3"]); + + assert_ne!(a, b); + } + + #[test] + fn test_eq_empty_dirs() { + let a = State::default().with_dir("dir1", ["file1", "file2"]); + let b = State::default() + .with_dir("dir1", ["file1", "file2"]) + .with_dir("dir2", Vec::::new()); + + assert_eq!(a, b); + + let b = b.with_dir("dir2", ["file3"]); + assert_ne!(a, b); + } +}