Skip to main content

trellis_core/
collection_diff.rs

1use crate::{CollectionDiffKind, CollectionDiffTrace, NodeId, collection::StoredDiff};
2use core::any::Any;
3use std::collections::{BTreeMap, BTreeSet};
4
5#[derive(Clone, Debug, Eq, PartialEq)]
6/// A value that was added to a structural diff.
7pub struct Added<T> {
8    /// Added value.
9    pub value: T,
10}
11
12#[derive(Clone, Debug, Eq, PartialEq)]
13/// A value that was removed from a structural diff.
14pub struct Removed<T> {
15    /// Removed value.
16    pub value: T,
17}
18
19#[derive(Clone, Debug, Eq, PartialEq)]
20/// A value that was unchanged in a structural diff.
21pub struct Unchanged<T> {
22    /// Unchanged value.
23    pub value: T,
24}
25
26#[derive(Clone, Debug, Eq, PartialEq)]
27/// A map entry that changed value without changing key identity.
28pub struct Updated<K, V> {
29    /// Updated key.
30    pub key: K,
31    /// Previously committed value.
32    pub previous: V,
33    /// Newly committed value.
34    pub current: V,
35}
36
37#[derive(Clone, Debug, Eq, PartialEq)]
38/// Deterministic structural diff for a set collection.
39pub struct SetDiff<K> {
40    /// Members added in stable key order.
41    pub added: Vec<Added<K>>,
42    /// Members removed in stable key order.
43    pub removed: Vec<Removed<K>>,
44    /// Members retained in stable key order.
45    pub unchanged: Vec<Unchanged<K>>,
46}
47
48impl<K> SetDiff<K>
49where
50    K: Clone + Ord,
51{
52    pub(crate) fn between(previous: &BTreeSet<K>, current: &BTreeSet<K>) -> Self {
53        Self {
54            added: current
55                .difference(previous)
56                .cloned()
57                .map(|value| Added { value })
58                .collect(),
59            removed: previous
60                .difference(current)
61                .cloned()
62                .map(|value| Removed { value })
63                .collect(),
64            unchanged: previous
65                .intersection(current)
66                .cloned()
67                .map(|value| Unchanged { value })
68                .collect(),
69        }
70    }
71
72    /// Returns true when the diff has no structural changes.
73    pub fn is_empty(&self) -> bool {
74        self.added.is_empty() && self.removed.is_empty()
75    }
76}
77
78impl<K> StoredDiff for SetDiff<K>
79where
80    K: Clone + Ord + Send + Sync + 'static,
81{
82    fn clone_box(&self) -> Box<dyn StoredDiff> {
83        Box::new(self.clone())
84    }
85
86    fn trace(&self, node: NodeId) -> CollectionDiffTrace {
87        CollectionDiffTrace {
88            node,
89            kind: CollectionDiffKind::Set,
90            added: self.added.len(),
91            removed: self.removed.len(),
92            updated: 0,
93            unchanged: self.unchanged.len(),
94        }
95    }
96
97    fn as_any(&self) -> &dyn Any {
98        self
99    }
100}
101
102#[derive(Clone, Debug, Eq, PartialEq)]
103/// Deterministic structural diff for a map collection.
104pub struct MapDiff<K, V> {
105    /// Entries added in stable key order.
106    pub added: Vec<Added<(K, V)>>,
107    /// Entries removed in stable key order.
108    pub removed: Vec<Removed<(K, V)>>,
109    /// Entries updated in stable key order.
110    pub updated: Vec<Updated<K, V>>,
111    /// Entries retained in stable key order.
112    pub unchanged: Vec<Unchanged<(K, V)>>,
113}
114
115impl<K, V> MapDiff<K, V>
116where
117    K: Clone + Ord,
118    V: Clone + PartialEq,
119{
120    pub(crate) fn between(previous: &BTreeMap<K, V>, current: &BTreeMap<K, V>) -> Self {
121        let mut added = Vec::new();
122        let mut removed = Vec::new();
123        let mut updated = Vec::new();
124        let mut unchanged = Vec::new();
125
126        for (key, previous_value) in previous {
127            match current.get(key) {
128                Some(current_value) if current_value == previous_value => {
129                    unchanged.push(Unchanged {
130                        value: (key.clone(), current_value.clone()),
131                    });
132                }
133                Some(current_value) => updated.push(Updated {
134                    key: key.clone(),
135                    previous: previous_value.clone(),
136                    current: current_value.clone(),
137                }),
138                None => removed.push(Removed {
139                    value: (key.clone(), previous_value.clone()),
140                }),
141            }
142        }
143
144        for (key, value) in current {
145            if !previous.contains_key(key) {
146                added.push(Added {
147                    value: (key.clone(), value.clone()),
148                });
149            }
150        }
151
152        Self {
153            added,
154            removed,
155            updated,
156            unchanged,
157        }
158    }
159
160    /// Returns true when the diff has no structural changes.
161    pub fn is_empty(&self) -> bool {
162        self.added.is_empty() && self.removed.is_empty() && self.updated.is_empty()
163    }
164}
165
166impl<K, V> StoredDiff for MapDiff<K, V>
167where
168    K: Clone + Ord + Send + Sync + 'static,
169    V: Clone + PartialEq + Send + Sync + 'static,
170{
171    fn clone_box(&self) -> Box<dyn StoredDiff> {
172        Box::new(self.clone())
173    }
174
175    fn trace(&self, node: NodeId) -> CollectionDiffTrace {
176        CollectionDiffTrace {
177            node,
178            kind: CollectionDiffKind::Map,
179            added: self.added.len(),
180            removed: self.removed.len(),
181            updated: self.updated.len(),
182            unchanged: self.unchanged.len(),
183        }
184    }
185
186    fn as_any(&self) -> &dyn Any {
187        self
188    }
189}