dipa/
map.rs

1use crate::Diffable;
2use serde::de::DeserializeOwned;
3use serde::Serialize;
4use std::fmt::{Debug, Formatter};
5
6#[macro_use]
7mod map_impl_macro;
8
9map_impl!(std::collections::HashMap<K,V>, hash_map_impl, );
10map_impl!(std::collections::BTreeMap<K,V>, btree_map_impl, + Ord);
11
12#[derive(Serialize)]
13/// The delta between two maps.
14pub enum MapDelta<'s, 'e, K, V: Diffable<'s, 'e, V>>
15where
16    <V as Diffable<'s, 'e, V>>::Delta: Serialize,
17{
18    /// Nothing has changed
19    NoChange,
20    /// Remove all elements from the HashMap
21    RemoveAll,
22    /// Add one field to the HashMap
23    AddOneField(&'e K, &'e V),
24    /// Remove one field to the HashMap
25    RemoveOneField(&'s K),
26    /// Change one field to the HashMap
27    ChangeOneField(&'s K, <V as Diffable<'s, 'e, V>>::Delta),
28    /// Modify multiple entries
29    ModifyMany {
30        added: Vec<(&'e K, &'e V)>,
31        removed: Vec<&'s K>,
32        changed: Vec<(&'s K, <V as Diffable<'s, 'e, V>>::Delta)>,
33    },
34}
35
36#[derive(Deserialize)]
37#[allow(missing_docs)]
38/// The delta between two maps.
39pub enum MapDeltaOwned<'s, 'e, K, V: Diffable<'s, 'e, V>>
40where
41    <V as Diffable<'s, 'e, V>>::DeltaOwned: DeserializeOwned,
42{
43    /// Nothing has changed
44    NoChange,
45    /// Remove all elements from the HashMap
46    RemoveAll,
47    /// Add one field to the HashMap
48    AddOneField(K, V),
49    /// Remove one field to the HashMap
50    RemoveOneField(K),
51    /// Change one field to the HashMap
52    ChangeOneField(K, <V as Diffable<'s, 'e, V>>::DeltaOwned),
53    /// Modify multiple entries
54    ModifyMany {
55        added: Vec<(K, V)>,
56        removed: Vec<K>,
57        changed: Vec<(K, <V as Diffable<'s, 'e, V>>::DeltaOwned)>,
58    },
59}
60
61// Used by DipaImplTester
62impl<'s, 'e, K, V> Debug for MapDelta<'s, 'e, K, V>
63where
64    V: Diffable<'s, 'e, V>,
65    <V as Diffable<'s, 'e, V>>::Delta: Debug,
66    <V as Diffable<'s, 'e, V>>::Delta: Serialize,
67    K: Debug,
68    V: Debug,
69{
70    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
71        match self {
72            MapDelta::NoChange => {
73                f.write_str("NoChange")?;
74            }
75            MapDelta::RemoveAll => {
76                f.write_str("RemoveAll")?;
77            }
78            MapDelta::AddOneField(k, v) => {
79                f.debug_tuple("AddOneField").field(k).field(v).finish()?;
80            }
81            MapDelta::RemoveOneField(k) => {
82                f.debug_tuple("RemoveOneField").field(k).finish()?;
83            }
84            MapDelta::ChangeOneField(k, diff) => {
85                f.debug_tuple("ChangeOneField")
86                    .field(k)
87                    .field(diff)
88                    .finish()?;
89            }
90            MapDelta::ModifyMany {
91                added,
92                removed,
93                changed,
94            } => {
95                f.debug_struct("ModifyMany")
96                    .field("added", added)
97                    .field("removed", removed)
98                    .field("changed", changed)
99                    .finish()?;
100            }
101        };
102
103        Ok(())
104    }
105}
106
107// Used by DipaImplTester
108impl<'s, 'e, K, V> PartialEq for MapDelta<'s, 'e, K, V>
109where
110    V: Diffable<'s, 'e, V>,
111    <V as Diffable<'s, 'e, V>>::Delta: PartialEq,
112    <V as Diffable<'s, 'e, V>>::Delta: Serialize,
113    K: PartialEq,
114    V: PartialEq,
115{
116    fn eq(&self, other: &Self) -> bool {
117        // Matched exhaustive so that we remember to add new variants.
118        match (self, other) {
119            (Self::NoChange, Self::NoChange) => true,
120            (Self::NoChange, _) => false,
121
122            (
123                Self::ModifyMany {
124                    added: left_added,
125                    removed: left_removed,
126                    changed: left_changed,
127                },
128                Self::ModifyMany {
129                    added: right_added,
130                    removed: right_removed,
131                    changed: right_changed,
132                },
133            ) => {
134                left_added == right_added
135                    && left_removed == right_removed
136                    && left_changed == right_changed
137            }
138            (Self::ModifyMany { .. }, _) => false,
139
140            (
141                Self::ChangeOneField(left_k, left_diff),
142                Self::ChangeOneField(right_k, right_diff),
143            ) => left_k == right_k && left_diff == right_diff,
144            (Self::ChangeOneField(..), _) => false,
145
146            (Self::RemoveOneField(left_k), Self::RemoveOneField(right_k)) => left_k == right_k,
147            (Self::RemoveOneField(_), _) => false,
148
149            (Self::AddOneField(left_k, left_v), Self::AddOneField(right_k, right_v)) => {
150                left_k == right_k && left_v == right_v
151            }
152            (Self::AddOneField(..), _) => false,
153
154            (Self::RemoveAll, Self::RemoveAll) => true,
155            (Self::RemoveAll, _) => false,
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::DipaImplTester;
164    use std::collections::{BTreeMap, HashMap};
165
166    /// Verify that we properly handle an unchanged empty HashMap
167    #[test]
168    fn unchanged_empty_hashmap() {
169        DipaImplTester {
170            label: None,
171            start: &mut HashMap::<(), ()>::new(),
172            end: &HashMap::new(),
173            expected_delta: MapDelta::NoChange,
174            expected_serialized_patch_size: 1,
175            expected_did_change: false,
176        }
177        .test();
178    }
179
180    /// Verify that we properly handle an unchanged HashMap that has fields.
181    #[test]
182    fn unchanged_hashmap() {
183        DipaImplTester {
184            label: None,
185            start: &mut vec![(1u32, 2u64)].into_iter().collect::<HashMap<_, _>>(),
186            end: &vec![(1, 2)].into_iter().collect(),
187            expected_delta: MapDelta::NoChange,
188            expected_serialized_patch_size: 1,
189            expected_did_change: false,
190        }
191        .test();
192    }
193
194    /// Verify that we can remove a field from the original HashMap
195    #[test]
196    fn all_fields_removed() {
197        DipaImplTester {
198            label: None,
199            start: &mut vec![(1u32, 2u64)].into_iter().collect(),
200            end: &HashMap::new(),
201            expected_delta: MapDelta::RemoveAll,
202            expected_serialized_patch_size: 1,
203            expected_did_change: true,
204        }
205        .test();
206    }
207
208    /// Verify that we can add a field to the original HashMap
209    #[test]
210    fn one_field_added() {
211        DipaImplTester {
212            label: None,
213            start: &mut HashMap::new(),
214            end: &vec![(1u32, 2u64)].into_iter().collect(),
215            expected_delta: MapDelta::AddOneField(&1, &2),
216            expected_serialized_patch_size: 3,
217            expected_did_change: true,
218        }
219        .test();
220    }
221
222    /// Verify that we can remove a field to the original HashMap
223    #[test]
224    fn one_field_removed() {
225        DipaImplTester {
226            label: None,
227            start: &mut vec![(1u32, 2u64), (3, 4)]
228                .into_iter()
229                .collect::<HashMap<_, _>>(),
230            end: &vec![(1, 2)].into_iter().collect(),
231            expected_delta: MapDelta::RemoveOneField(&3),
232            expected_serialized_patch_size: 2,
233            expected_did_change: true,
234        }
235        .test();
236    }
237
238    /// Verify that we can remove a field to the original HashMap
239    #[test]
240    fn one_field_changed() {
241        DipaImplTester {
242            label: None,
243            start: &mut vec![(1u32, 2u64), (3, 4)]
244                .into_iter()
245                .collect::<HashMap<_, _>>(),
246            end: &vec![(1, 2), (3, 9)].into_iter().collect(),
247            expected_delta: MapDelta::ChangeOneField(&3, Some(9)),
248            expected_serialized_patch_size: 4,
249            expected_did_change: true,
250        }
251        .test();
252    }
253
254    /// Verify that we can add multiple fields to the map.
255    #[test]
256    fn many_fields_added() {
257        DipaImplTester {
258            label: None,
259            start: &mut BTreeMap::new(),
260            end: &vec![(1u32, 2u64), (3, 4)].into_iter().collect(),
261            expected_delta: MapDelta::ModifyMany {
262                added: vec![(&1, &2), (&3, &4)],
263                removed: vec![],
264                changed: vec![],
265            },
266            expected_serialized_patch_size: 8,
267            expected_did_change: true,
268        }
269        .test();
270    }
271
272    /// Verify that we can remove multiple entries from the map.
273    #[test]
274    fn many_fields_removed() {
275        DipaImplTester {
276            label: None,
277            start: &mut vec![(1u32, 2u64), (3, 4), (5, 6)]
278                .into_iter()
279                .collect::<BTreeMap<_, _>>(),
280            end: &vec![(1, 2)].into_iter().collect(),
281            expected_delta: MapDelta::ModifyMany {
282                added: vec![],
283                removed: vec![&3, &5],
284                changed: vec![],
285            },
286            expected_serialized_patch_size: 6,
287            expected_did_change: true,
288        }
289        .test();
290    }
291
292    /// Verify that we can change multiple entries within the map.
293    #[test]
294    fn many_fields_changed() {
295        DipaImplTester {
296            label: None,
297            start: &mut vec![(1u32, 2u64), (3, 4)]
298                .into_iter()
299                .collect::<BTreeMap<_, _>>(),
300            end: &vec![(1, 15), (3, 16)].into_iter().collect(),
301            expected_delta: MapDelta::ModifyMany {
302                added: vec![],
303                removed: vec![],
304                changed: vec![(&1, Some(15)), (&3, Some(16))],
305            },
306            expected_serialized_patch_size: 10,
307            expected_did_change: true,
308        }
309        .test();
310    }
311}