use crate::Diff;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::hash::Hash;
use std::iter::empty;
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum InnerValue<K, V: Diff> {
Change {
#[serde(rename = "c:k")]
key: K,
#[serde(rename = "c:v")]
value: <V as Diff>::Type,
},
Add {
#[serde(rename = "a:k")]
key: K,
#[serde(rename = "a:v")]
value: <V as Diff>::Type,
},
Remove {
#[serde(rename = "r:k")]
key: K,
},
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct DiffHashMap<K: Diff, V: Diff>(
#[serde(skip_serializing_if = "Option::is_none")] pub Option<Vec<InnerValue<K, V>>>,
);
impl<K, V> Diff for HashMap<K, V>
where
K: Clone + Debug + PartialEq + Eq + Hash + Diff + for<'de> Deserialize<'de> + Serialize,
V: Clone + Debug + PartialEq + Diff + for<'de> Deserialize<'de> + Serialize,
{
type Type = DiffHashMap<K, V>;
fn diff(&self, other: &Self) -> crate::Result<Self::Type> {
let old: HashSet<&K> = self.keys().collect();
let new: HashSet<&K> = other.keys().collect();
let changed_keys = old.intersection(&new).filter(|k| self[k] != other[k]);
let removed_keys = old.difference(&new);
let added_keys = new.difference(&old);
let mut changes: Vec<InnerValue<K, V>> = Vec::new();
for key in changed_keys {
let (old_val, new_val): (&V, &V) = (&self[key], &other[key]);
let diff = old_val.diff(new_val)?;
changes.push(InnerValue::Change {
key: (*key).clone(),
value: diff,
});
}
for key in added_keys {
changes.push(InnerValue::Add {
key: (*key).clone(),
value: other[key].clone().into_diff()?,
});
}
for key in removed_keys {
changes.push(InnerValue::Remove { key: (*key).clone() });
}
Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) }))
}
fn merge(&self, diff: Self::Type) -> crate::Result<Self> {
let mut new = self.clone();
for change in diff.0.into_iter().flatten() {
match change {
InnerValue::Change { key, value } => {
let fake: &mut V = &mut *new.get_mut(&key).expect("Failed to get value");
*fake = <V>::from_diff(value)?;
}
InnerValue::Add { key, value } => {
new.insert(key, <V>::from_diff(value)?);
}
InnerValue::Remove { key } => {
new.remove(&key);
}
}
}
Ok(new)
}
fn from_diff(diff: Self::Type) -> crate::Result<Self> {
let mut map = Self::new();
if let Some(diff) = diff.0 {
for (idx, elm) in diff.into_iter().enumerate() {
match elm {
InnerValue::Add { key, value } => {
map.insert(key, <V>::from_diff(value)?);
}
_ => {
panic!("Unable to create Diff at index: {:?}", idx);
}
}
}
}
Ok(map)
}
fn into_diff(self) -> crate::Result<Self::Type> {
let mut changes: Vec<InnerValue<K, V>> = Vec::new();
for (key, val) in self {
changes.push(InnerValue::Add {
key,
value: val.into_diff()?,
});
}
Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) }))
}
}
impl<K, V> Debug for DiffHashMap<K, V>
where
K: Debug + Diff,
V: Debug + Diff,
{
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "DiffHashMap")?;
let mut buf = f.debug_list();
if let Some(val) = &self.0 {
buf.entries(val.iter());
} else {
buf.entries(empty::<Vec<InnerValue<K, V>>>());
}
buf.finish()
}
}
impl<K, V> Default for DiffHashMap<K, V>
where
K: Diff,
V: Diff,
{
fn default() -> Self {
DiffHashMap(None)
}
}
impl<K, V> Debug for InnerValue<K, V>
where
K: Debug + Diff,
V: Debug + Diff,
{
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match &self {
Self::Change { key, value } => f
.debug_struct("Change")
.field("key", key)
.field("value", value)
.finish(),
Self::Add { key, value } => f.debug_struct("Add").field("key", key).field("value", value).finish(),
Self::Remove { key } => f.debug_struct("Remove").field("key", key).finish(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
macro_rules! map {
($($key:expr => $val:expr),* $(,)?) => {{
let mut map = HashMap::new();
$( map.insert($key, $val); )*
map
}}
}
#[test]
fn test_hashmap_diff() {
let m0: HashMap<String, usize> = map! {
"test".into() => 300usize,
"foo".into() => 10usize,
"bar".into() => 20usize,
"baz".into() => 1usize,
};
let m1: HashMap<String, usize> = map! {
"test".into() => 300usize,
"foo".into() => 0usize,
"bar".into() => 20usize,
"quux".into() => 10usize,
};
let diff = m0.diff(&m1).unwrap();
let expected: DiffHashMap<String, usize> = DiffHashMap(Some(vec![
InnerValue::Change {
key: "foo".into(),
value: 0usize.into_diff().unwrap(),
},
InnerValue::Add {
key: "quux".into(),
value: 10usize.into_diff().unwrap(),
},
InnerValue::Remove { key: "baz".into() },
]));
assert_eq!(expected, diff);
let m2 = m0.merge(diff).unwrap();
assert_eq!(m1, m2);
}
}