selene_core/changeset/
diff.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use smallvec::SmallVec;
5
6use crate::{CoreError, CoreResult, DbString, Value};
7
8#[derive(Clone, Debug, PartialEq)]
10pub struct LabelDiff {
11 pub added: SmallVec<[DbString; 2]>,
13 pub removed: SmallVec<[DbString; 2]>,
15}
16
17impl LabelDiff {
18 pub fn new(
26 added: impl IntoIterator<Item = DbString>,
27 removed: impl IntoIterator<Item = DbString>,
28 ) -> CoreResult<Self> {
29 let added = sorted_deduped(added);
30 let removed = sorted_deduped(removed);
31 ensure_disjoint("label", &added, &removed)?;
32 Ok(Self { added, removed })
33 }
34
35 #[must_use]
37 pub fn is_empty(&self) -> bool {
38 self.added.is_empty() && self.removed.is_empty()
39 }
40}
41
42#[derive(Deserialize, Serialize)]
43struct LabelDiffWire {
44 added: SmallVec<[DbString; 2]>,
45 removed: SmallVec<[DbString; 2]>,
46}
47
48impl Serialize for LabelDiff {
49 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50 where
51 S: Serializer,
52 {
53 let mut added = self.added.clone();
57 let mut removed = self.removed.clone();
58 added.sort_by(|lhs, rhs| lhs.as_str().cmp(rhs.as_str()));
59 removed.sort_by(|lhs, rhs| lhs.as_str().cmp(rhs.as_str()));
60 LabelDiffWire { added, removed }.serialize(serializer)
61 }
62}
63
64impl<'de> Deserialize<'de> for LabelDiff {
65 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66 where
67 D: Deserializer<'de>,
68 {
69 let wire = LabelDiffWire::deserialize(deserializer)?;
73 validate_sorted_unique(&wire.added, "LabelDiff.added")?;
74 validate_sorted_unique(&wire.removed, "LabelDiff.removed")?;
75 validate_disjoint(&wire.added, &wire.removed, "label")?;
76 Ok(Self {
77 added: wire.added,
78 removed: wire.removed,
79 })
80 }
81}
82
83#[derive(Clone, Debug, PartialEq)]
85pub struct PropertyDiff {
86 pub set: SmallVec<[(DbString, Value); 4]>,
88 pub removed: SmallVec<[DbString; 2]>,
90}
91
92impl PropertyDiff {
93 pub fn new(
101 set: impl IntoIterator<Item = (DbString, Value)>,
102 removed: impl IntoIterator<Item = DbString>,
103 ) -> CoreResult<Self> {
104 let mut set: SmallVec<[(DbString, Value); 4]> = set.into_iter().collect();
105 if set.len() > 1 {
106 set.sort_by(|(lhs, _), (rhs, _)| lhs.cmp(rhs));
107 let mut deduped = SmallVec::new();
108 for (key, value) in set {
109 if let Some((last_key, last_value)) = deduped.last_mut()
110 && last_key == &key
111 {
112 *last_value = value;
113 continue;
114 }
115 deduped.push((key, value));
116 }
117 set = deduped;
118 }
119 let removed = sorted_deduped(removed);
120 for (key, _) in set.iter() {
121 if removed.binary_search(key).is_ok() {
122 return Err(CoreError::OverlappingDiff {
123 kind: "property",
124 key: key.clone(),
125 });
126 }
127 }
128 Ok(Self { set, removed })
129 }
130
131 #[must_use]
133 pub fn is_empty(&self) -> bool {
134 self.set.is_empty() && self.removed.is_empty()
135 }
136}
137
138#[derive(Deserialize, Serialize)]
139struct PropertyDiffWire {
140 set: SmallVec<[(DbString, Value); 4]>,
141 removed: SmallVec<[DbString; 2]>,
142}
143
144impl Serialize for PropertyDiff {
145 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146 where
147 S: Serializer,
148 {
149 let mut set = self.set.clone();
153 let mut removed = self.removed.clone();
154 set.sort_by(|(lhs, _), (rhs, _)| lhs.as_str().cmp(rhs.as_str()));
155 removed.sort_by(|lhs, rhs| lhs.as_str().cmp(rhs.as_str()));
156 PropertyDiffWire { set, removed }.serialize(serializer)
157 }
158}
159
160impl<'de> Deserialize<'de> for PropertyDiff {
161 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162 where
163 D: Deserializer<'de>,
164 {
165 let wire = PropertyDiffWire::deserialize(deserializer)?;
169 for window in wire.set.windows(2) {
170 if window[0].0 >= window[1].0 {
171 return Err(serde::de::Error::custom(
172 "PropertyDiff.set entries must be sorted by DbString order with no duplicate keys",
173 ));
174 }
175 }
176 validate_sorted_unique(&wire.removed, "PropertyDiff.removed")?;
177 for (key, _) in wire.set.iter() {
178 if wire.removed.binary_search(key).is_ok() {
179 return Err(serde::de::Error::custom(format!(
180 "PropertyDiff: key {key} appears in both set and removed",
181 )));
182 }
183 }
184 Ok(Self {
185 set: wire.set,
186 removed: wire.removed,
187 })
188 }
189}
190
191fn sorted_deduped(values: impl IntoIterator<Item = DbString>) -> SmallVec<[DbString; 2]> {
192 let mut values: SmallVec<[DbString; 2]> = values.into_iter().collect();
193 values.sort();
194 values.dedup();
195 values
196}
197
198fn ensure_disjoint(
199 kind: &'static str,
200 added: &SmallVec<[DbString; 2]>,
201 removed: &SmallVec<[DbString; 2]>,
202) -> CoreResult<()> {
203 for label in added.iter() {
204 if removed.binary_search(label).is_ok() {
205 return Err(CoreError::OverlappingDiff {
206 kind,
207 key: label.clone(),
208 });
209 }
210 }
211 Ok(())
212}
213
214fn validate_sorted_unique<E: serde::de::Error>(
215 values: &SmallVec<[DbString; 2]>,
216 label: &'static str,
217) -> Result<(), E> {
218 for window in values.windows(2) {
219 if window[0] >= window[1] {
220 return Err(E::custom(format!(
221 "{label} must be sorted by DbString order with no duplicates"
222 )));
223 }
224 }
225 Ok(())
226}
227
228fn validate_disjoint<E: serde::de::Error>(
229 added: &SmallVec<[DbString; 2]>,
230 removed: &SmallVec<[DbString; 2]>,
231 kind: &'static str,
232) -> Result<(), E> {
233 for label in added.iter() {
234 if removed.binary_search(label).is_ok() {
235 return Err(E::custom(format!(
236 "overlapping {kind} diff: {label} appears in both add/set and remove",
237 )));
238 }
239 }
240 Ok(())
241}