identity_diff/
hashmap.rs

1// Copyright 2020-2022 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::Diff;
5use serde::Deserialize;
6use serde::Serialize;
7use std::collections::HashMap;
8use std::collections::HashSet;
9use std::fmt::Debug;
10use std::fmt::Formatter;
11
12use std::hash::Hash;
13use std::iter::empty;
14
15/// Inner value of the `DiffHashMap` type.
16#[derive(Clone, PartialEq, Serialize, Deserialize)]
17#[serde(untagged)]
18pub enum InnerValue<K, V: Diff> {
19  // Logs if a value has changed between the two types being Diffed.
20  Change {
21    #[serde(rename = "c:k")]
22    key: K,
23    #[serde(rename = "c:v")]
24    value: <V as Diff>::Type,
25  },
26  // Logs an addition.
27  Add {
28    #[serde(rename = "a:k")]
29    key: K,
30    #[serde(rename = "a:v")]
31    value: <V as Diff>::Type,
32  },
33  // Logs a removal.
34  Remove {
35    #[serde(rename = "r:k")]
36    key: K,
37  },
38}
39
40/// A `DiffHashMap` type which represents a Diffed `HashMap`.
41/// By default this value is transparent to `serde`.
42#[derive(Clone, PartialEq, Serialize, Deserialize)]
43#[serde(transparent)]
44pub struct DiffHashMap<K: Diff, V: Diff>(
45  #[serde(skip_serializing_if = "Option::is_none")] pub Option<Vec<InnerValue<K, V>>>,
46);
47
48/// Diff Implementation on a HashMap<K, V>
49impl<K, V> Diff for HashMap<K, V>
50where
51  K: Clone + Debug + PartialEq + Eq + Hash + Diff + for<'de> Deserialize<'de> + Serialize,
52  V: Clone + Debug + PartialEq + Diff + for<'de> Deserialize<'de> + Serialize,
53{
54  /// the Diff type of the HashMap<K, V>
55  type Type = DiffHashMap<K, V>;
56
57  /// Diffs two `HashMaps`; `self` and `other` and creates a `DiffHashMap<K, V>`
58  fn diff(&self, other: &Self) -> crate::Result<Self::Type> {
59    let old: HashSet<&K> = self.keys().collect();
60    let new: HashSet<&K> = other.keys().collect();
61
62    let changed_keys = old.intersection(&new).filter(|k| self[k] != other[k]);
63    let removed_keys = old.difference(&new);
64    let added_keys = new.difference(&old);
65
66    let mut changes: Vec<InnerValue<K, V>> = Vec::new();
67
68    for key in changed_keys {
69      let (old_val, new_val): (&V, &V) = (&self[key], &other[key]);
70
71      let diff = old_val.diff(new_val)?;
72
73      changes.push(InnerValue::Change {
74        key: (*key).clone(),
75        value: diff,
76      });
77    }
78    for key in added_keys {
79      changes.push(InnerValue::Add {
80        key: (*key).clone(),
81        value: other[key].clone().into_diff()?,
82      });
83    }
84    for key in removed_keys {
85      changes.push(InnerValue::Remove { key: (*key).clone() });
86    }
87
88    Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) }))
89  }
90
91  /// Merges the changes in a `DiffHashMap<K, V>`, `diff` with a `HashMap<K, V>`, `self`.
92  fn merge(&self, diff: Self::Type) -> crate::Result<Self> {
93    let mut new = self.clone();
94
95    for change in diff.0.into_iter().flatten() {
96      match change {
97        InnerValue::Change { key, value } => {
98          let fake: &mut V = &mut *new.get_mut(&key).expect("Failed to get value");
99
100          *fake = <V>::from_diff(value)?;
101        }
102        InnerValue::Add { key, value } => {
103          new.insert(key, <V>::from_diff(value)?);
104        }
105        InnerValue::Remove { key } => {
106          new.remove(&key);
107        }
108      }
109    }
110
111    Ok(new)
112  }
113
114  /// Converts a `DiffHashMap<K, V>`, `diff` into a `HashMap<K, V>`.
115  fn from_diff(diff: Self::Type) -> crate::Result<Self> {
116    let mut map = Self::new();
117    if let Some(diff) = diff.0 {
118      for (idx, elm) in diff.into_iter().enumerate() {
119        match elm {
120          InnerValue::Add { key, value } => {
121            map.insert(key, <V>::from_diff(value)?);
122          }
123          _ => {
124            panic!("Unable to create Diff at index: {:?}", idx);
125          }
126        }
127      }
128    }
129
130    Ok(map)
131  }
132
133  /// Converts a `HashMap<K, V>`, `diff` into a `DiffHashMap<K, V>`.
134  fn into_diff(self) -> crate::Result<Self::Type> {
135    let mut changes: Vec<InnerValue<K, V>> = Vec::new();
136    for (key, val) in self {
137      changes.push(InnerValue::Add {
138        key,
139        value: val.into_diff()?,
140      });
141    }
142
143    Ok(DiffHashMap(if changes.is_empty() { None } else { Some(changes) }))
144  }
145}
146
147/// Debug implementation for the `DiffHashMap<K, V>` type.
148impl<K, V> Debug for DiffHashMap<K, V>
149where
150  K: Debug + Diff,
151  V: Debug + Diff,
152{
153  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
154    write!(f, "DiffHashMap")?;
155
156    let mut buf = f.debug_list();
157
158    if let Some(val) = &self.0 {
159      buf.entries(val.iter());
160    } else {
161      buf.entries(empty::<Vec<InnerValue<K, V>>>());
162    }
163    buf.finish()
164  }
165}
166
167/// Default implementation for the `DiffHashMap<K, V>` type.
168impl<K, V> Default for DiffHashMap<K, V>
169where
170  K: Diff,
171  V: Diff,
172{
173  fn default() -> Self {
174    DiffHashMap(None)
175  }
176}
177
178/// Debug implementation for the `InnerValue<K, V>` type.
179impl<K, V> Debug for InnerValue<K, V>
180where
181  K: Debug + Diff,
182  V: Debug + Diff,
183{
184  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
185    match &self {
186      Self::Change { key, value } => f
187        .debug_struct("Change")
188        .field("key", key)
189        .field("value", value)
190        .finish(),
191      Self::Add { key, value } => f.debug_struct("Add").field("key", key).field("value", value).finish(),
192      Self::Remove { key } => f.debug_struct("Remove").field("key", key).finish(),
193    }
194  }
195}
196
197#[cfg(test)]
198mod tests {
199  use super::*;
200  use std::collections::HashMap;
201
202  /// Quickly creates a simple map using `map! { "key" => "value"}
203  macro_rules! map {
204        ($($key:expr => $val:expr),* $(,)?) => {{
205            let mut map = HashMap::new();
206            $( map.insert($key, $val); )*
207                map
208        }}
209    }
210
211  #[test]
212  fn test_hashmap_diff() {
213    let m0: HashMap<String, usize> = map! {
214        "test".into() => 300usize,
215        "foo".into() => 10usize,
216        "bar".into() => 20usize,
217        "baz".into() => 1usize,
218    };
219
220    let m1: HashMap<String, usize> = map! {
221        "test".into() => 300usize,
222        "foo".into() => 0usize,
223        "bar".into() => 20usize,
224        "quux".into() => 10usize,
225    };
226
227    let diff = m0.diff(&m1).unwrap();
228
229    let expected: DiffHashMap<String, usize> = DiffHashMap(Some(vec![
230      InnerValue::Change {
231        key: "foo".into(),
232        value: 0usize.into_diff().unwrap(),
233      },
234      InnerValue::Add {
235        key: "quux".into(),
236        value: 10usize.into_diff().unwrap(),
237      },
238      InnerValue::Remove { key: "baz".into() },
239    ]));
240
241    assert_eq!(expected, diff);
242
243    let m2 = m0.merge(diff).unwrap();
244
245    assert_eq!(m1, m2);
246  }
247}