deltoid/collections/
hashset.rs

1//! A newtype wrapping [`HashSet`] that provides extra functionality in
2//! the form of delta support, de/serialization, partial equality and more.
3//!
4//! [`HashSet`]: https://doc.rust-lang.org/std/collections/struct.HashSet.html
5
6use crate::{Apply, Core, Delta, DeltaResult, FromDelta, IntoDelta};
7use serde::{Deserialize, Serialize};
8use std::collections::HashSet;
9use std::fmt::Debug;
10use std::hash::Hash;
11
12
13impl<T> Core for HashSet<T>
14where T: Clone + Debug + PartialEq + Ord + Core
15    + for<'de> Deserialize<'de>
16    + Serialize,
17{
18    type Delta = HashSetDelta<T>;
19}
20
21impl<T> Apply for HashSet<T>
22where T: Clone + Debug + PartialEq + Ord + Hash + Apply + FromDelta
23    + for<'de> Deserialize<'de>
24    + Serialize,
25{
26    fn apply(&self, delta: Self::Delta) -> DeltaResult<Self> {
27        match delta.0 {
28            None => Ok(self.clone()),
29            Some(entry_deltas) => {
30                let mut new: Self = self.clone();
31                for entry_delta in entry_deltas { match entry_delta {
32                    EntryDelta::Add { item } => {
33                        new.insert(<T>::from_delta(item)?);
34                    },
35                    EntryDelta::Remove { item } => {
36                        new.remove(&(<T>::from_delta(item)?));
37                    },
38                }}
39                Ok(new)
40            },
41        }
42    }
43}
44
45impl<T> Delta for HashSet<T>
46where T: Clone + Debug + PartialEq + Ord + Hash + Delta + IntoDelta
47    + for<'de> Deserialize<'de>
48    + Serialize,
49{
50    fn delta(&self, rhs: &Self) -> DeltaResult<Self::Delta> {
51        Ok(HashSetDelta(if self == rhs {
52            None
53        } else {
54            let mut entry_deltas: Vec<EntryDelta<T>> = vec![];
55            for addition in rhs.difference(&self) {
56                let addition = addition.clone().into_delta()?;
57                entry_deltas.push(EntryDelta::Add { item: addition });
58            }
59            for removal in self.difference(&rhs) {
60                let removal = removal.clone().into_delta()?;
61                entry_deltas.push(EntryDelta::Remove { item: removal });
62            }
63            Some(entry_deltas)
64        }))
65    }
66}
67
68impl<T> FromDelta for HashSet<T>
69where T: Clone + Debug + PartialEq + Ord + Hash + FromDelta
70    + for<'de> Deserialize<'de>
71    + Serialize,
72{
73    fn from_delta(delta: Self::Delta) -> DeltaResult<Self> {
74        let mut map = Self::new();
75        if let Some(delta_entries) = delta.0 {
76            for entry in delta_entries { match entry {
77                EntryDelta::Add { item } => {
78                    map.insert(<T>::from_delta(item)?);
79                },
80                EntryDelta::Remove { item } => {
81                    let item = <T>::from_delta(item)?;
82                    map.remove(&item);
83                },
84            }}
85        }
86        Ok(map)
87    }
88}
89
90impl<T> IntoDelta for HashSet<T>
91where T: Clone + Debug + PartialEq + Ord + IntoDelta
92    + for<'de> Deserialize<'de>
93    + Serialize,
94{
95    fn into_delta(self) -> DeltaResult<Self::Delta> {
96        Ok(HashSetDelta(if self.is_empty() {
97            None
98        } else {
99            let mut changes: Vec<EntryDelta<T>> = vec![];
100            for item in self {
101                changes.push(EntryDelta::Add { item: item.into_delta()? });
102            }
103            Some(changes)
104        }))
105    }
106}
107
108
109
110
111#[derive(Clone, PartialEq)]
112#[derive(serde_derive::Deserialize, serde_derive::Serialize)]
113pub struct HashSetDelta<T: Core>(
114    #[doc(hidden)] pub Option<Vec<EntryDelta<T>>>,
115);
116
117impl<T> HashSetDelta<T>
118where T: Clone + Debug + PartialEq + Ord + Core
119    + for<'de> Deserialize<'de>
120    + Serialize,
121{
122    pub fn iter<'d>(&'d self) -> Box<dyn Iterator<Item = &EntryDelta<T>> + 'd> {
123        match &self.0 {
124            Some(deltas) => Box::new(deltas.iter()),
125            None => Box::new(std::iter::empty()),
126        }
127    }
128
129    pub fn into_iter<'d>(self) -> Box<dyn Iterator<Item = EntryDelta<T>> + 'd>
130    where Self: 'd {
131        match self.0 {
132            Some(delta) => Box::new(delta.into_iter()),
133            None => Box::new(std::iter::empty()),
134        }
135    }
136
137    pub fn len(&self) -> usize {
138        match &self.0 {
139            Some(deltas) => deltas.len(),
140            None => 0,
141        }
142    }
143}
144
145impl<T> std::fmt::Debug for HashSetDelta<T>
146where T: std::fmt::Debug + Core {
147    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
148        write!(f, "HashSetDelta ")?;
149        let mut buf = f.debug_list();
150        if let Some(d) = &self.0 {
151            buf.entries(d.iter());
152        } else {
153            buf.entries(std::iter::empty::<Vec<EntryDelta<T>>>());
154        }
155        buf.finish()
156    }
157}
158
159
160
161#[derive(Clone, PartialEq)]
162#[derive(serde_derive::Deserialize, serde_derive::Serialize)]
163pub enum EntryDelta<T: Core> {
164    Add { item: <T as Core>::Delta },
165    Remove { item: <T as Core>::Delta },
166}
167
168impl<T> std::fmt::Debug for EntryDelta<T>
169where T: std::fmt::Debug + Core {
170    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
171        match &self {
172            Self::Add { item } => f.debug_struct("Add")
173                .field("item", item)
174                .finish(),
175            Self::Remove { item } => f.debug_struct("Remove")
176                .field("item", item)
177                .finish(),
178        }
179    }
180}
181
182
183
184
185#[allow(non_snake_case)]
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use std::collections::HashSet;
190
191    macro_rules! set {
192        ($($val:expr),* $(,)?) => {{ #[allow(redundant_semicolons)] {
193            let mut set = HashSet::new();
194            $( set.insert($val); )* ;
195            set
196        }}}
197    }
198
199    #[test]
200    fn HashSet__delta__same_values() -> DeltaResult<()> {
201        let set0: HashSet<String> = set! {
202            "bar".into(),
203            "foo".into(),
204            "floozie".into(),
205            "quux".into(),
206        };
207        let set1: HashSet<String> = set! {
208            "bar".into(),
209            "foo".into(),
210            "floozie".into(),
211            "quux".into(),
212        };
213        let delta = set0.delta(&set1)?;
214        let expected = HashSetDelta(None);
215        assert_eq!(delta, expected);
216        let set2 = set0.apply(delta)?;
217        assert_eq!(set0, set2);
218        assert_eq!(set1, set2);
219
220        let delta = set1.delta(&set0)?;
221        assert_eq!(delta, HashSetDelta(None));
222        let set3 = set1.apply(delta)?;
223        assert_eq!(set0, set3);
224        assert_eq!(set1, set3);
225
226        Ok(())
227    }
228
229    #[test]
230    fn HashSet__delta__different_values() -> DeltaResult<()> {
231        let set0: HashSet<String> = set! {
232            "bar".into(),
233            "foo".into(),
234            "floozie".into(),
235            "quux".into(),
236        };
237        let set1: HashSet<String> = set! {
238            "bar".into(),
239            "baz".into(),
240            "foo".into(),
241            "quux".into(),
242        };
243        let delta0 = set0.delta(&set1)?;
244        let expected = HashSetDelta(Some(vec![
245            EntryDelta::Add { item: "baz".to_string().into_delta()? },
246            EntryDelta::Remove { item: "floozie".to_string().into_delta()? },
247        ]));
248        assert_eq!(delta0, expected);
249        let set2 = set0.apply(delta0)?;
250        assert_eq!(set1, set2);
251
252        let delta1 = set1.delta(&set0)?;
253        assert_eq!(delta1, HashSetDelta(Some(vec![
254            EntryDelta::Add { item: "floozie".to_string().into_delta()? },
255            EntryDelta::Remove { item: "baz".to_string().into_delta()? },
256        ])));
257        let set3 = set1.apply(delta1)?;
258        assert_eq!(set0, set3);
259
260        Ok(())
261    }
262
263    #[test]
264    fn HashSet__apply__same_values() -> DeltaResult<()> {
265        let set0: HashSet<String> = set! {
266            "bar".into(),
267            "foo".into(),
268            "floozie".into(),
269            "quux".into(),
270        };
271        let set1: HashSet<String> = set! {
272            "bar".into(),
273            "foo".into(),
274            "floozie".into(),
275            "quux".into(),
276        };
277        let delta = set0.delta(&set1)?;
278        assert_eq!(delta, HashSetDelta(None));
279        let set2 = set0.apply(delta)?;
280        assert_eq!(set1, set2);
281        Ok(())
282    }
283
284    #[test]
285    fn HashSet__apply__different_values() -> DeltaResult<()> {
286        let set0: HashSet<String> = set! {
287            "bar".into(),
288            "foo".into(),
289            "floozie".into(),
290            "quux".into(),
291        };
292        let set1: HashSet<String> = set! {
293            "bar".into(),
294            "baz".into(),
295            "foo".into(),
296            "quux".into(),
297        };
298        let delta = set0.delta(&set1)?;
299        assert_eq!(delta, HashSetDelta(Some(vec![
300            EntryDelta::Add { item: "baz".to_string().into_delta()? },
301            EntryDelta::Remove { item: "floozie".to_string().into_delta()? },
302        ])));
303        let set2 = set0.apply(delta)?;
304        assert_eq!(set1, set2);
305        Ok(())
306    }
307}