1use crate::{
2 traits::AsView,
3 view::{ListPatch, MapPatch, SetPatch},
4};
5use candid::CandidType;
6use std::{
7 collections::{
8 BTreeMap, BTreeSet, HashMap, HashSet, btree_map::Entry as BTreeMapEntry,
9 hash_map::Entry as HashMapEntry,
10 },
11 hash::{BuildHasher, Hash},
12};
13use thiserror::Error as ThisError;
14
15#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
22pub enum ViewPatchError {
23 #[error("invalid patch shape: expected {expected}, found {actual}")]
24 InvalidPatchShape {
25 expected: &'static str,
26 actual: &'static str,
27 },
28
29 #[error("missing key for map operation: {operation}")]
30 MissingKey { operation: &'static str },
31
32 #[error("invalid patch cardinality: expected {expected}, found {actual}")]
33 CardinalityViolation { expected: usize, actual: usize },
34
35 #[error("patch merge failed at {path}: {source}")]
36 Context {
37 path: String,
38 #[source]
39 source: Box<Self>,
40 },
41}
42
43impl ViewPatchError {
44 #[must_use]
46 pub fn with_field(self, field: impl AsRef<str>) -> Self {
47 self.with_path_segment(field.as_ref())
48 }
49
50 #[must_use]
52 pub fn with_index(self, index: usize) -> Self {
53 self.with_path_segment(format!("[{index}]"))
54 }
55
56 #[must_use]
58 pub const fn path(&self) -> Option<&str> {
59 match self {
60 Self::Context { path, .. } => Some(path.as_str()),
61 _ => None,
62 }
63 }
64
65 #[must_use]
67 pub fn leaf(&self) -> &Self {
68 match self {
69 Self::Context { source, .. } => source.leaf(),
70 _ => self,
71 }
72 }
73
74 #[must_use]
75 fn with_path_segment(self, segment: impl Into<String>) -> Self {
76 let segment = segment.into();
77 match self {
78 Self::Context { path, source } => Self::Context {
79 path: Self::join_segments(segment.as_str(), path.as_str()),
80 source,
81 },
82 source => Self::Context {
83 path: segment,
84 source: Box::new(source),
85 },
86 }
87 }
88
89 #[must_use]
90 fn join_segments(prefix: &str, suffix: &str) -> String {
91 if suffix.starts_with('[') {
92 format!("{prefix}{suffix}")
93 } else {
94 format!("{prefix}.{suffix}")
95 }
96 }
97}
98
99pub trait UpdateView: AsView {
104 type UpdateViewType: CandidType + Default;
106
107 fn merge(&mut self, _update: Self::UpdateViewType) -> Result<(), ViewPatchError> {
109 Ok(())
110 }
111}
112
113impl<T> UpdateView for Option<T>
114where
115 T: UpdateView + Default,
116{
117 type UpdateViewType = Option<T::UpdateViewType>;
118
119 fn merge(&mut self, update: Self::UpdateViewType) -> Result<(), ViewPatchError> {
120 match update {
121 None => {
122 *self = None;
124 }
125 Some(inner_update) => {
126 if let Some(inner_value) = self.as_mut() {
127 inner_value
128 .merge(inner_update)
129 .map_err(|err| err.with_field("value"))?;
130 } else {
131 let mut new_value = T::default();
132 new_value
133 .merge(inner_update)
134 .map_err(|err| err.with_field("value"))?;
135 *self = Some(new_value);
136 }
137 }
138 }
139
140 Ok(())
141 }
142}
143
144impl<T> UpdateView for Vec<T>
145where
146 T: UpdateView + Default,
147{
148 type UpdateViewType = Vec<ListPatch<T::UpdateViewType>>;
150
151 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
152 for patch in patches {
153 match patch {
154 ListPatch::Update { index, patch } => {
155 if let Some(elem) = self.get_mut(index) {
156 elem.merge(patch).map_err(|err| err.with_index(index))?;
157 }
158 }
159 ListPatch::Insert { index, value } => {
160 let idx = index.min(self.len());
161 let mut elem = T::default();
162 elem.merge(value).map_err(|err| err.with_index(idx))?;
163 self.insert(idx, elem);
164 }
165 ListPatch::Push { value } => {
166 let idx = self.len();
167 let mut elem = T::default();
168 elem.merge(value).map_err(|err| err.with_index(idx))?;
169 self.push(elem);
170 }
171 ListPatch::Overwrite { values } => {
172 self.clear();
173 self.reserve(values.len());
174
175 for (index, value) in values.into_iter().enumerate() {
176 let mut elem = T::default();
177 elem.merge(value).map_err(|err| err.with_index(index))?;
178 self.push(elem);
179 }
180 }
181 ListPatch::Remove { index } => {
182 if index < self.len() {
183 self.remove(index);
184 }
185 }
186 ListPatch::Clear => self.clear(),
187 }
188 }
189
190 Ok(())
191 }
192}
193
194impl<T, S> UpdateView for HashSet<T, S>
195where
196 T: UpdateView + Clone + Default + Eq + Hash,
197 S: BuildHasher + Default,
198{
199 type UpdateViewType = Vec<SetPatch<T::UpdateViewType>>;
200
201 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
202 for patch in patches {
203 match patch {
204 SetPatch::Insert(value) => {
205 let mut elem = T::default();
206 elem.merge(value).map_err(|err| err.with_field("insert"))?;
207 self.insert(elem);
208 }
209 SetPatch::Remove(value) => {
210 let mut elem = T::default();
211 elem.merge(value).map_err(|err| err.with_field("remove"))?;
212 self.remove(&elem);
213 }
214 SetPatch::Overwrite { values } => {
215 self.clear();
216
217 for (index, value) in values.into_iter().enumerate() {
218 let mut elem = T::default();
219 elem.merge(value)
220 .map_err(|err| err.with_field("overwrite").with_index(index))?;
221 self.insert(elem);
222 }
223 }
224 SetPatch::Clear => self.clear(),
225 }
226 }
227
228 Ok(())
229 }
230}
231
232enum MapPatchOp<K, V> {
234 Insert { key: K, value: V },
235 Remove { key: K },
236 Replace { key: K, value: V },
237 Clear,
238}
239
240impl<K, V, S> UpdateView for HashMap<K, V, S>
241where
242 K: UpdateView + Clone + Default + Eq + Hash,
243 V: UpdateView + Default,
244 S: BuildHasher + Default,
245{
246 type UpdateViewType = Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>;
247
248 #[expect(clippy::too_many_lines)]
249 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
250 let mut ops = Vec::with_capacity(patches.len());
252 for patch in patches {
253 match patch {
254 MapPatch::Insert { key, value } => {
255 let mut key_value = K::default();
256 key_value
257 .merge(key)
258 .map_err(|err| err.with_field("insert").with_field("key"))?;
259 ops.push(MapPatchOp::Insert {
260 key: key_value,
261 value,
262 });
263 }
264 MapPatch::Remove { key } => {
265 let mut key_value = K::default();
266 key_value
267 .merge(key)
268 .map_err(|err| err.with_field("remove").with_field("key"))?;
269 ops.push(MapPatchOp::Remove { key: key_value });
270 }
271 MapPatch::Replace { key, value } => {
272 let mut key_value = K::default();
273 key_value
274 .merge(key)
275 .map_err(|err| err.with_field("replace").with_field("key"))?;
276 ops.push(MapPatchOp::Replace {
277 key: key_value,
278 value,
279 });
280 }
281 MapPatch::Clear => ops.push(MapPatchOp::Clear),
282 }
283 }
284
285 let mut saw_clear = false;
287 let mut touched = HashSet::with_capacity(ops.len());
288 for op in &ops {
289 match op {
290 MapPatchOp::Clear => {
291 if saw_clear {
292 return Err(ViewPatchError::InvalidPatchShape {
293 expected: "at most one Clear operation per map patch batch",
294 actual: "duplicate Clear operations",
295 });
296 }
297 saw_clear = true;
298 if ops.len() != 1 {
299 return Err(ViewPatchError::CardinalityViolation {
300 expected: 1,
301 actual: ops.len(),
302 });
303 }
304 }
305 MapPatchOp::Insert { key, .. }
306 | MapPatchOp::Remove { key }
307 | MapPatchOp::Replace { key, .. } => {
308 if saw_clear {
309 return Err(ViewPatchError::InvalidPatchShape {
310 expected: "Clear must be the only operation in a map patch batch",
311 actual: "Clear combined with key operation",
312 });
313 }
314 if !touched.insert(key.clone()) {
315 return Err(ViewPatchError::InvalidPatchShape {
316 expected: "unique key operations per map patch batch",
317 actual: "duplicate key operation",
318 });
319 }
320 }
321 }
322 }
323 if saw_clear {
324 self.clear();
325 return Ok(());
326 }
327
328 for op in ops {
330 match op {
331 MapPatchOp::Insert { key, value } => match self.entry(key) {
332 HashMapEntry::Occupied(mut slot) => {
333 slot.get_mut()
334 .merge(value)
335 .map_err(|err| err.with_field("insert").with_field("value"))?;
336 }
337 HashMapEntry::Vacant(slot) => {
338 let mut value_value = V::default();
339 value_value
340 .merge(value)
341 .map_err(|err| err.with_field("insert").with_field("value"))?;
342 slot.insert(value_value);
343 }
344 },
345 MapPatchOp::Remove { key } => {
346 if self.remove(&key).is_none() {
347 return Err(ViewPatchError::MissingKey {
348 operation: "remove",
349 });
350 }
351 }
352 MapPatchOp::Replace { key, value } => match self.entry(key) {
353 HashMapEntry::Occupied(mut slot) => {
354 slot.get_mut()
355 .merge(value)
356 .map_err(|err| err.with_field("replace").with_field("value"))?;
357 }
358 HashMapEntry::Vacant(_) => {
359 return Err(ViewPatchError::MissingKey {
360 operation: "replace",
361 });
362 }
363 },
364 MapPatchOp::Clear => {
365 return Err(ViewPatchError::InvalidPatchShape {
366 expected: "Clear to be handled before apply phase",
367 actual: "Clear reached apply phase",
368 });
369 }
370 }
371 }
372
373 Ok(())
374 }
375}
376
377impl<T> UpdateView for BTreeSet<T>
378where
379 T: UpdateView + Clone + Default + Ord,
380{
381 type UpdateViewType = Vec<SetPatch<T::UpdateViewType>>;
382
383 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
384 for patch in patches {
385 match patch {
386 SetPatch::Insert(value) => {
387 let mut elem = T::default();
388 elem.merge(value).map_err(|err| err.with_field("insert"))?;
389 self.insert(elem);
390 }
391 SetPatch::Remove(value) => {
392 let mut elem = T::default();
393 elem.merge(value).map_err(|err| err.with_field("remove"))?;
394 self.remove(&elem);
395 }
396 SetPatch::Overwrite { values } => {
397 self.clear();
398
399 for (index, value) in values.into_iter().enumerate() {
400 let mut elem = T::default();
401 elem.merge(value)
402 .map_err(|err| err.with_field("overwrite").with_index(index))?;
403 self.insert(elem);
404 }
405 }
406 SetPatch::Clear => self.clear(),
407 }
408 }
409
410 Ok(())
411 }
412}
413
414impl<K, V> UpdateView for BTreeMap<K, V>
415where
416 K: UpdateView + Clone + Default + Ord,
417 V: UpdateView + Default,
418{
419 type UpdateViewType = Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>;
420
421 #[expect(clippy::too_many_lines)]
422 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
423 let mut ops = Vec::with_capacity(patches.len());
425 for patch in patches {
426 match patch {
427 MapPatch::Insert { key, value } => {
428 let mut key_value = K::default();
429 key_value
430 .merge(key)
431 .map_err(|err| err.with_field("insert").with_field("key"))?;
432 ops.push(MapPatchOp::Insert {
433 key: key_value,
434 value,
435 });
436 }
437 MapPatch::Remove { key } => {
438 let mut key_value = K::default();
439 key_value
440 .merge(key)
441 .map_err(|err| err.with_field("remove").with_field("key"))?;
442 ops.push(MapPatchOp::Remove { key: key_value });
443 }
444 MapPatch::Replace { key, value } => {
445 let mut key_value = K::default();
446 key_value
447 .merge(key)
448 .map_err(|err| err.with_field("replace").with_field("key"))?;
449 ops.push(MapPatchOp::Replace {
450 key: key_value,
451 value,
452 });
453 }
454 MapPatch::Clear => ops.push(MapPatchOp::Clear),
455 }
456 }
457
458 let mut saw_clear = false;
460 let mut touched = BTreeSet::new();
461 for op in &ops {
462 match op {
463 MapPatchOp::Clear => {
464 if saw_clear {
465 return Err(ViewPatchError::InvalidPatchShape {
466 expected: "at most one Clear operation per map patch batch",
467 actual: "duplicate Clear operations",
468 });
469 }
470 saw_clear = true;
471 if ops.len() != 1 {
472 return Err(ViewPatchError::CardinalityViolation {
473 expected: 1,
474 actual: ops.len(),
475 });
476 }
477 }
478 MapPatchOp::Insert { key, .. }
479 | MapPatchOp::Remove { key }
480 | MapPatchOp::Replace { key, .. } => {
481 if saw_clear {
482 return Err(ViewPatchError::InvalidPatchShape {
483 expected: "Clear must be the only operation in a map patch batch",
484 actual: "Clear combined with key operation",
485 });
486 }
487 if !touched.insert(key.clone()) {
488 return Err(ViewPatchError::InvalidPatchShape {
489 expected: "unique key operations per map patch batch",
490 actual: "duplicate key operation",
491 });
492 }
493 }
494 }
495 }
496 if saw_clear {
497 self.clear();
498 return Ok(());
499 }
500
501 for op in ops {
503 match op {
504 MapPatchOp::Insert { key, value } => match self.entry(key) {
505 BTreeMapEntry::Occupied(mut slot) => {
506 slot.get_mut()
507 .merge(value)
508 .map_err(|err| err.with_field("insert").with_field("value"))?;
509 }
510 BTreeMapEntry::Vacant(slot) => {
511 let mut value_value = V::default();
512 value_value
513 .merge(value)
514 .map_err(|err| err.with_field("insert").with_field("value"))?;
515 slot.insert(value_value);
516 }
517 },
518 MapPatchOp::Remove { key } => {
519 if self.remove(&key).is_none() {
520 return Err(ViewPatchError::MissingKey {
521 operation: "remove",
522 });
523 }
524 }
525 MapPatchOp::Replace { key, value } => match self.entry(key) {
526 BTreeMapEntry::Occupied(mut slot) => {
527 slot.get_mut()
528 .merge(value)
529 .map_err(|err| err.with_field("replace").with_field("value"))?;
530 }
531 BTreeMapEntry::Vacant(_) => {
532 return Err(ViewPatchError::MissingKey {
533 operation: "replace",
534 });
535 }
536 },
537 MapPatchOp::Clear => {
538 return Err(ViewPatchError::InvalidPatchShape {
539 expected: "Clear to be handled before apply phase",
540 actual: "Clear reached apply phase",
541 });
542 }
543 }
544 }
545
546 Ok(())
547 }
548}
549
550macro_rules! impl_update_view {
551 ($($type:ty),*) => {
552 $(
553 impl UpdateView for $type {
554 type UpdateViewType = Self;
555
556 fn merge(
557 &mut self,
558 update: Self::UpdateViewType,
559 ) -> Result<(), ViewPatchError> {
560 *self = update;
561
562 Ok(())
563 }
564 }
565 )*
566 };
567}
568
569impl_update_view!(bool, i8, i16, i32, i64, u8, u16, u32, u64, String);