delta_struct/map.rs
1//! Keyed diffing, behind the `unordered-delta` field type.
2//!
3//! A field marked `#[delta_struct(field_type = "unordered-delta")]` is treated
4//! as a bag of key/value entries and represented as a [`MapDelta`]. Entries
5//! are paired up by key, and a pair whose value changed is recorded as a
6//! [`KeyedDelta`] — the value's own [`Delta`] rather than a removal followed by
7//! a re-send of the whole thing. Reach for it when a map's values are
8//! themselves large structs that tend to change a field at a time.
9//!
10//! The derive emits calls to [`diff`] and [`apply`]; you only need this module
11//! directly to inspect or construct a delta by hand.
12
13use crate::{Delta, Mismatch, TryIndex, TryIndexMut};
14
15/// An entry that splits into a key and a value.
16///
17/// This is what lets the derive talk about the `K` and the `V` of a
18/// `HashMap<K, V>` when all it can name is the collection's
19/// [`Item`](IntoIterator::Item). It is implemented for `(K, V)`, which is what
20/// every std map iterates as; implement it yourself only if you have a map
21/// whose entry type is not a tuple.
22pub trait MapEntry {
23 /// The part identifying the entry. Entries with equal keys are the same
24 /// entry, and so are diffed against each other rather than swapped.
25 type Key;
26 /// The part that gets diffed.
27 type Value;
28
29 /// Splits the entry into its parts.
30 fn into_parts(self) -> (Self::Key, Self::Value);
31
32 /// Reassembles an entry from parts, so it can be put back into a
33 /// collection.
34 fn from_parts(key: Self::Key, value: Self::Value) -> Self;
35}
36
37impl<K, V> MapEntry for (K, V) {
38 type Key = K;
39 type Value = V;
40
41 fn into_parts(self) -> (K, V) {
42 self
43 }
44
45 fn from_parts(key: K, value: V) -> Self {
46 (key, value)
47 }
48}
49
50/// A keyed diff between two collections of entries.
51///
52/// The three parts are deliberately asymmetric, and that asymmetry is the
53/// whole point of the field type: [`add`] carries whole entries because the
54/// receiver has never seen them, while [`remove`] carries bare keys and
55/// [`change`] carries deltas, because for those the receiver already holds the
56/// rest.
57///
58/// Nothing here records position — see [`SeqDelta`](crate::SeqDelta) for that.
59///
60/// [`add`]: MapDelta::add
61/// [`remove`]: MapDelta::remove
62/// [`change`]: MapDelta::change
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct MapDelta<K, V, D> {
66 /// Entries whose keys are in the new collection but not the old one.
67 pub add: Vec<(K, V)>,
68 /// Keys that were in the old collection but are not in the new one.
69 pub remove: Vec<K>,
70 /// Keys in both collections whose values differ, and how.
71 pub change: Vec<KeyedDelta<K, D>>,
72}
73
74impl<K, V, D> MapDelta<K, V, D> {
75 /// Whether the two collections held the same entries, and so nothing needs
76 /// sending.
77 pub fn is_empty(&self) -> bool {
78 self.add.is_empty() && self.remove.is_empty() && self.change.is_empty()
79 }
80}
81
82impl<K, V, D> Default for MapDelta<K, V, D> {
83 fn default() -> Self {
84 MapDelta {
85 add: Vec::new(),
86 remove: Vec::new(),
87 change: Vec::new(),
88 }
89 }
90}
91
92/// A change to the value stored under one key.
93///
94/// Turn on the `serde` feature to get `Serialize` and `Deserialize` on this,
95/// as a delta struct with an `unordered-delta` field cannot derive them
96/// otherwise.
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98#[derive(Clone, Debug, PartialEq, Eq)]
99pub struct KeyedDelta<K, D> {
100 /// The key whose value changed. It is present in both the old and the new
101 /// collection — a key on only one side is an addition or a removal
102 /// instead.
103 pub key: K,
104 /// What changed about the value, as produced by [`Delta::delta`].
105 pub delta: D,
106}
107
108/// Pairs the entries of `old` and `new` by key and diffs the values that
109/// survived.
110///
111/// A key present on both sides with an equal value produces nothing at all, so
112/// the [`MapDelta`] is empty when the collections agree.
113///
114/// Each key of `old` is looked up in `new` exactly once, through the
115/// collection's own [`TryIndex`] implementation, so the cost is that of n
116/// lookups: O(n) for a [`HashMap`](std::collections::HashMap), O(n log n) for
117/// a [`BTreeMap`](std::collections::BTreeMap).
118///
119/// ```
120/// use delta_struct::{map, Delta, ScalarDelta};
121/// use std::collections::BTreeMap;
122///
123/// #[derive(Delta)]
124/// #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
125/// struct Service {
126/// port: u16,
127/// healthy: bool,
128/// }
129///
130/// let services = |port| {
131/// vec![("web", Service { port, healthy: true })]
132/// .into_iter()
133/// .collect::<BTreeMap<&str, Service>>()
134/// };
135///
136/// let delta = map::diff(services(80), services(8080));
137/// assert!(delta.add.is_empty() && delta.remove.is_empty());
138/// assert_eq!(delta.change[0].key, "web");
139/// assert_eq!(delta.change[0].delta.port, ScalarDelta::Changed(8080));
140/// assert_eq!(delta.change[0].delta.healthy, ScalarDelta::Unchanged);
141/// ```
142pub fn diff<C, E>(old: C, mut new: C) -> MapDelta<E::Key, E::Value, <E::Value as Delta>::Output>
143where
144 C: IntoIterator<Item = E> + TryIndex<E::Key, Output = E::Value>,
145 E: MapEntry,
146 E::Value: Delta,
147{
148 // Take each of `old`'s entries out of `new` as it is matched, so whatever
149 // is still standing at the end is exactly what was added. Taking rather
150 // than borrowing is also what makes the values below owned, which is what
151 // `Delta::delta` needs.
152 let mut remove = Vec::new();
153 let mut change = Vec::new();
154 for entry in old {
155 let (key, old_value) = entry.into_parts();
156 match new.try_remove(&key) {
157 Some(new_value) => {
158 if let Some(delta) = Delta::delta(old_value, new_value) {
159 change.push(KeyedDelta { key, delta });
160 }
161 }
162 None => remove.push(key),
163 }
164 }
165 MapDelta {
166 add: new.into_iter().map(MapEntry::into_parts).collect(),
167 remove,
168 change,
169 }
170}
171
172/// Applies a keyed diff to `target` in place.
173///
174/// Removals and changes are single lookups rather than scans, so this costs
175/// the same as [`diff`] does, and a changed value is updated where it sits
176/// rather than taken out and put back. As with an `unordered` field,
177/// membership is preserved but position is not.
178///
179/// A key in `remove` or `change` that `target` does not have is ignored.
180///
181/// This is the one collection helper that can fail, because it is the one that
182/// recurses into [`Delta::apply_delta`]: a map of enums can be handed a change
183/// whose value has since moved to another variant. The error propagates rather
184/// than being swallowed, and leaves the map partly updated.
185///
186/// ```
187/// use delta_struct::{map, Delta};
188/// use std::collections::BTreeMap;
189///
190/// #[derive(Delta)]
191/// struct Service {
192/// port: u16,
193/// }
194///
195/// let services = |port| {
196/// vec![("web", Service { port })]
197/// .into_iter()
198/// .collect::<BTreeMap<&str, Service>>()
199/// };
200///
201/// let delta = map::diff(services(80), services(8080));
202/// let mut target = services(80);
203/// map::apply(&mut target, delta)?;
204/// assert_eq!(target["web"].port, 8080);
205/// # Ok::<(), delta_struct::Mismatch>(())
206/// ```
207pub fn apply<C, E>(
208 target: &mut C,
209 delta: MapDelta<E::Key, E::Value, <E::Value as Delta>::Output>,
210) -> Result<(), Mismatch>
211where
212 C: IntoIterator<Item = E> + Extend<E> + TryIndexMut<E::Key, Output = E::Value>,
213 E: MapEntry,
214 E::Value: Delta,
215{
216 let MapDelta {
217 add,
218 remove,
219 change,
220 } = delta;
221 for key in remove {
222 target.try_remove(&key);
223 }
224 for KeyedDelta { key, delta } in change {
225 if let Some(value) = target.try_index_mut(&key) {
226 value.apply_delta(delta)?;
227 }
228 }
229 target.extend(
230 add.into_iter()
231 .map(|(key, value)| E::from_parts(key, value)),
232 );
233 Ok(())
234}