Skip to main content

trellis_core/
collection_diff.rs

1use crate::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 + 'static,
81{
82    fn clone_box(&self) -> Box<dyn StoredDiff> {
83        Box::new(self.clone())
84    }
85
86    fn as_any(&self) -> &dyn Any {
87        self
88    }
89}
90
91#[derive(Clone, Debug, Eq, PartialEq)]
92/// Deterministic structural diff for a map collection.
93pub struct MapDiff<K, V> {
94    /// Entries added in stable key order.
95    pub added: Vec<Added<(K, V)>>,
96    /// Entries removed in stable key order.
97    pub removed: Vec<Removed<(K, V)>>,
98    /// Entries updated in stable key order.
99    pub updated: Vec<Updated<K, V>>,
100    /// Entries retained in stable key order.
101    pub unchanged: Vec<Unchanged<(K, V)>>,
102}
103
104impl<K, V> MapDiff<K, V>
105where
106    K: Clone + Ord,
107    V: Clone + PartialEq,
108{
109    pub(crate) fn between(previous: &BTreeMap<K, V>, current: &BTreeMap<K, V>) -> Self {
110        let mut added = Vec::new();
111        let mut removed = Vec::new();
112        let mut updated = Vec::new();
113        let mut unchanged = Vec::new();
114
115        for (key, previous_value) in previous {
116            match current.get(key) {
117                Some(current_value) if current_value == previous_value => {
118                    unchanged.push(Unchanged {
119                        value: (key.clone(), current_value.clone()),
120                    });
121                }
122                Some(current_value) => updated.push(Updated {
123                    key: key.clone(),
124                    previous: previous_value.clone(),
125                    current: current_value.clone(),
126                }),
127                None => removed.push(Removed {
128                    value: (key.clone(), previous_value.clone()),
129                }),
130            }
131        }
132
133        for (key, value) in current {
134            if !previous.contains_key(key) {
135                added.push(Added {
136                    value: (key.clone(), value.clone()),
137                });
138            }
139        }
140
141        Self {
142            added,
143            removed,
144            updated,
145            unchanged,
146        }
147    }
148
149    /// Returns true when the diff has no structural changes.
150    pub fn is_empty(&self) -> bool {
151        self.added.is_empty() && self.removed.is_empty() && self.updated.is_empty()
152    }
153}
154
155impl<K, V> StoredDiff for MapDiff<K, V>
156where
157    K: Clone + Ord + 'static,
158    V: Clone + PartialEq + 'static,
159{
160    fn clone_box(&self) -> Box<dyn StoredDiff> {
161        Box::new(self.clone())
162    }
163
164    fn as_any(&self) -> &dyn Any {
165        self
166    }
167}