1use crate::view::{ListPatch, MapPatch, SetPatch};
2use candid::CandidType;
3use std::{
4 collections::{
5 BTreeMap, BTreeSet, HashMap, HashSet, btree_map::Entry as BTreeMapEntry,
6 hash_map::Entry as HashMapEntry,
7 },
8 hash::{BuildHasher, Hash},
9 iter::IntoIterator,
10};
11use thiserror::Error as ThisError;
12
13pub trait AsView: Sized {
21 type ViewType: Default;
22
23 fn as_view(&self) -> Self::ViewType;
24 fn from_view(view: Self::ViewType) -> Self;
25}
26
27impl AsView for () {
28 type ViewType = Self;
29
30 fn as_view(&self) -> Self::ViewType {}
31
32 fn from_view((): Self::ViewType) -> Self {}
33}
34
35impl AsView for String {
36 type ViewType = Self;
37
38 fn as_view(&self) -> Self::ViewType {
39 self.clone()
40 }
41
42 fn from_view(view: Self::ViewType) -> Self {
43 view
44 }
45}
46
47impl<T: AsView> AsView for Box<T> {
49 type ViewType = T::ViewType;
50
51 fn as_view(&self) -> Self::ViewType {
52 T::as_view(self.as_ref())
54 }
55
56 fn from_view(view: Self::ViewType) -> Self {
57 Self::new(T::from_view(view))
59 }
60}
61
62impl<T: AsView> AsView for Option<T> {
63 type ViewType = Option<T::ViewType>;
64
65 fn as_view(&self) -> Self::ViewType {
66 self.as_ref().map(AsView::as_view)
67 }
68
69 fn from_view(view: Self::ViewType) -> Self {
70 view.map(T::from_view)
71 }
72}
73
74impl<T: AsView> AsView for Vec<T> {
75 type ViewType = Vec<T::ViewType>;
76
77 fn as_view(&self) -> Self::ViewType {
78 self.iter().map(AsView::as_view).collect()
79 }
80
81 fn from_view(view: Self::ViewType) -> Self {
82 view.into_iter().map(T::from_view).collect()
83 }
84}
85
86impl<T, S> AsView for HashSet<T, S>
87where
88 T: AsView + Eq + Hash + Clone,
89 S: BuildHasher + Default,
90{
91 type ViewType = Vec<T::ViewType>;
92
93 fn as_view(&self) -> Self::ViewType {
94 self.iter().map(AsView::as_view).collect()
95 }
96
97 fn from_view(view: Self::ViewType) -> Self {
98 view.into_iter().map(T::from_view).collect()
99 }
100}
101
102impl<K, V, S> AsView for HashMap<K, V, S>
103where
104 K: AsView + Eq + Hash + Clone,
105 V: AsView,
106 S: BuildHasher + Default,
107{
108 type ViewType = Vec<(K::ViewType, V::ViewType)>;
109
110 fn as_view(&self) -> Self::ViewType {
111 self.iter()
112 .map(|(k, v)| (k.as_view(), v.as_view()))
113 .collect()
114 }
115
116 fn from_view(view: Self::ViewType) -> Self {
117 view.into_iter()
118 .map(|(k, v)| (K::from_view(k), V::from_view(v)))
119 .collect()
120 }
121}
122
123impl<T> AsView for BTreeSet<T>
124where
125 T: AsView + Ord + Clone,
126{
127 type ViewType = Vec<T::ViewType>;
128
129 fn as_view(&self) -> Self::ViewType {
130 self.iter().map(AsView::as_view).collect()
131 }
132
133 fn from_view(view: Self::ViewType) -> Self {
134 view.into_iter().map(T::from_view).collect()
135 }
136}
137
138impl<K, V> AsView for BTreeMap<K, V>
139where
140 K: AsView + Ord + Clone,
141 V: AsView,
142{
143 type ViewType = Vec<(K::ViewType, V::ViewType)>;
144
145 fn as_view(&self) -> Self::ViewType {
146 self.iter()
147 .map(|(k, v)| (k.as_view(), v.as_view()))
148 .collect()
149 }
150
151 fn from_view(view: Self::ViewType) -> Self {
152 view.into_iter()
153 .map(|(k, v)| (K::from_view(k), V::from_view(v)))
154 .collect()
155 }
156}
157
158#[macro_export]
159macro_rules! impl_view {
160 ($($type:ty),*) => {
161 $(
162 impl AsView for $type {
163 type ViewType = Self;
164
165 fn as_view(&self) -> Self::ViewType {
166 *self
167 }
168
169 fn from_view(view: Self::ViewType) -> Self {
170 view
171 }
172 }
173 )*
174 };
175}
176
177impl_view!(bool, i8, i16, i32, i64, u8, u16, u32, u64);
178
179impl AsView for f32 {
180 type ViewType = Self;
181
182 fn as_view(&self) -> Self::ViewType {
183 *self
184 }
185
186 fn from_view(view: Self::ViewType) -> Self {
187 if view.is_finite() {
188 if view == 0.0 { 0.0 } else { view }
189 } else {
190 0.0
191 }
192 }
193}
194
195impl AsView for f64 {
196 type ViewType = Self;
197
198 fn as_view(&self) -> Self::ViewType {
199 *self
200 }
201
202 fn from_view(view: Self::ViewType) -> Self {
203 if view.is_finite() {
204 if view == 0.0 { 0.0 } else { view }
205 } else {
206 0.0
207 }
208 }
209}
210
211pub trait CreateView: AsView {
216 type CreateViewType: CandidType + Default;
221
222 fn from_create_view(view: Self::CreateViewType) -> Self;
223}
224
225#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
231pub enum ViewPatchError {
232 #[error("invalid patch shape: expected {expected}, found {actual}")]
233 InvalidPatchShape {
234 expected: &'static str,
235 actual: &'static str,
236 },
237
238 #[error("missing key for map operation: {operation}")]
239 MissingKey { operation: &'static str },
240
241 #[error("invalid patch cardinality: expected {expected}, found {actual}")]
242 CardinalityViolation { expected: usize, actual: usize },
243
244 #[error("patch merge failed at {path}: {source}")]
245 Context {
246 path: String,
247 #[source]
248 source: Box<Self>,
249 },
250}
251
252impl ViewPatchError {
253 #[must_use]
255 pub fn with_field(self, field: impl AsRef<str>) -> Self {
256 self.with_path_segment(field.as_ref())
257 }
258
259 #[must_use]
261 pub fn with_index(self, index: usize) -> Self {
262 self.with_path_segment(format!("[{index}]"))
263 }
264
265 #[must_use]
267 pub const fn path(&self) -> Option<&str> {
268 match self {
269 Self::Context { path, .. } => Some(path.as_str()),
270 _ => None,
271 }
272 }
273
274 #[must_use]
276 pub fn leaf(&self) -> &Self {
277 match self {
278 Self::Context { source, .. } => source.leaf(),
279 _ => self,
280 }
281 }
282
283 #[must_use]
284 fn with_path_segment(self, segment: impl Into<String>) -> Self {
285 let segment = segment.into();
286 match self {
287 Self::Context { path, source } => Self::Context {
288 path: Self::join_segments(segment.as_str(), path.as_str()),
289 source,
290 },
291 source => Self::Context {
292 path: segment,
293 source: Box::new(source),
294 },
295 }
296 }
297
298 #[must_use]
299 fn join_segments(prefix: &str, suffix: &str) -> String {
300 if suffix.starts_with('[') {
301 format!("{prefix}{suffix}")
302 } else {
303 format!("{prefix}.{suffix}")
304 }
305 }
306}
307
308pub trait UpdateView: AsView {
313 type UpdateViewType: CandidType + Default;
315
316 fn merge(&mut self, _update: Self::UpdateViewType) -> Result<(), ViewPatchError> {
318 Ok(())
319 }
320}
321
322impl<T> UpdateView for Option<T>
323where
324 T: UpdateView + Default,
325{
326 type UpdateViewType = Option<T::UpdateViewType>;
327
328 fn merge(&mut self, update: Self::UpdateViewType) -> Result<(), ViewPatchError> {
329 match update {
330 None => {
331 *self = None;
333 }
334 Some(inner_update) => {
335 if let Some(inner_value) = self.as_mut() {
336 inner_value
337 .merge(inner_update)
338 .map_err(|err| err.with_field("value"))?;
339 } else {
340 let mut new_value = T::default();
341 new_value
342 .merge(inner_update)
343 .map_err(|err| err.with_field("value"))?;
344 *self = Some(new_value);
345 }
346 }
347 }
348
349 Ok(())
350 }
351}
352
353impl<T> UpdateView for Vec<T>
354where
355 T: UpdateView + Default,
356{
357 type UpdateViewType = Vec<ListPatch<T::UpdateViewType>>;
359
360 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
361 for patch in patches {
362 match patch {
363 ListPatch::Update { index, patch } => {
364 if let Some(elem) = self.get_mut(index) {
365 elem.merge(patch).map_err(|err| err.with_index(index))?;
366 }
367 }
368 ListPatch::Insert { index, value } => {
369 let idx = index.min(self.len());
370 let mut elem = T::default();
371 elem.merge(value).map_err(|err| err.with_index(idx))?;
372 self.insert(idx, elem);
373 }
374 ListPatch::Push { value } => {
375 let idx = self.len();
376 let mut elem = T::default();
377 elem.merge(value).map_err(|err| err.with_index(idx))?;
378 self.push(elem);
379 }
380 ListPatch::Overwrite { values } => {
381 self.clear();
382 self.reserve(values.len());
383
384 for (index, value) in values.into_iter().enumerate() {
385 let mut elem = T::default();
386 elem.merge(value).map_err(|err| err.with_index(index))?;
387 self.push(elem);
388 }
389 }
390 ListPatch::Remove { index } => {
391 if index < self.len() {
392 self.remove(index);
393 }
394 }
395 ListPatch::Clear => self.clear(),
396 }
397 }
398
399 Ok(())
400 }
401}
402
403impl<T, S> UpdateView for HashSet<T, S>
404where
405 T: UpdateView + Clone + Default + Eq + Hash,
406 S: BuildHasher + Default,
407{
408 type UpdateViewType = Vec<SetPatch<T::UpdateViewType>>;
409
410 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
411 for patch in patches {
412 match patch {
413 SetPatch::Insert(value) => {
414 let mut elem = T::default();
415 elem.merge(value).map_err(|err| err.with_field("insert"))?;
416 self.insert(elem);
417 }
418 SetPatch::Remove(value) => {
419 let mut elem = T::default();
420 elem.merge(value).map_err(|err| err.with_field("remove"))?;
421 self.remove(&elem);
422 }
423 SetPatch::Overwrite { values } => {
424 self.clear();
425
426 for (index, value) in values.into_iter().enumerate() {
427 let mut elem = T::default();
428 elem.merge(value)
429 .map_err(|err| err.with_field("overwrite").with_index(index))?;
430 self.insert(elem);
431 }
432 }
433 SetPatch::Clear => self.clear(),
434 }
435 }
436
437 Ok(())
438 }
439}
440
441enum MapPatchOp<K, V> {
443 Insert { key: K, value: V },
444 Remove { key: K },
445 Replace { key: K, value: V },
446 Clear,
447}
448
449impl<K, V, S> UpdateView for HashMap<K, V, S>
450where
451 K: UpdateView + Clone + Default + Eq + Hash,
452 V: UpdateView + Default,
453 S: BuildHasher + Default,
454{
455 type UpdateViewType = Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>;
456
457 #[expect(clippy::too_many_lines)]
458 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
459 let mut ops = Vec::with_capacity(patches.len());
461 for patch in patches {
462 match patch {
463 MapPatch::Insert { key, value } => {
464 let mut key_value = K::default();
465 key_value
466 .merge(key)
467 .map_err(|err| err.with_field("insert").with_field("key"))?;
468 ops.push(MapPatchOp::Insert {
469 key: key_value,
470 value,
471 });
472 }
473 MapPatch::Remove { key } => {
474 let mut key_value = K::default();
475 key_value
476 .merge(key)
477 .map_err(|err| err.with_field("remove").with_field("key"))?;
478 ops.push(MapPatchOp::Remove { key: key_value });
479 }
480 MapPatch::Replace { key, value } => {
481 let mut key_value = K::default();
482 key_value
483 .merge(key)
484 .map_err(|err| err.with_field("replace").with_field("key"))?;
485 ops.push(MapPatchOp::Replace {
486 key: key_value,
487 value,
488 });
489 }
490 MapPatch::Clear => ops.push(MapPatchOp::Clear),
491 }
492 }
493
494 let mut saw_clear = false;
496 let mut touched = HashSet::with_capacity(ops.len());
497 for op in &ops {
498 match op {
499 MapPatchOp::Clear => {
500 if saw_clear {
501 return Err(ViewPatchError::InvalidPatchShape {
502 expected: "at most one Clear operation per map patch batch",
503 actual: "duplicate Clear operations",
504 });
505 }
506 saw_clear = true;
507 if ops.len() != 1 {
508 return Err(ViewPatchError::CardinalityViolation {
509 expected: 1,
510 actual: ops.len(),
511 });
512 }
513 }
514 MapPatchOp::Insert { key, .. }
515 | MapPatchOp::Remove { key }
516 | MapPatchOp::Replace { key, .. } => {
517 if saw_clear {
518 return Err(ViewPatchError::InvalidPatchShape {
519 expected: "Clear must be the only operation in a map patch batch",
520 actual: "Clear combined with key operation",
521 });
522 }
523 if !touched.insert(key.clone()) {
524 return Err(ViewPatchError::InvalidPatchShape {
525 expected: "unique key operations per map patch batch",
526 actual: "duplicate key operation",
527 });
528 }
529 }
530 }
531 }
532 if saw_clear {
533 self.clear();
534 return Ok(());
535 }
536
537 for op in ops {
539 match op {
540 MapPatchOp::Insert { key, value } => match self.entry(key) {
541 HashMapEntry::Occupied(mut slot) => {
542 slot.get_mut()
543 .merge(value)
544 .map_err(|err| err.with_field("insert").with_field("value"))?;
545 }
546 HashMapEntry::Vacant(slot) => {
547 let mut value_value = V::default();
548 value_value
549 .merge(value)
550 .map_err(|err| err.with_field("insert").with_field("value"))?;
551 slot.insert(value_value);
552 }
553 },
554 MapPatchOp::Remove { key } => {
555 if self.remove(&key).is_none() {
556 return Err(ViewPatchError::MissingKey {
557 operation: "remove",
558 });
559 }
560 }
561 MapPatchOp::Replace { key, value } => match self.entry(key) {
562 HashMapEntry::Occupied(mut slot) => {
563 slot.get_mut()
564 .merge(value)
565 .map_err(|err| err.with_field("replace").with_field("value"))?;
566 }
567 HashMapEntry::Vacant(_) => {
568 return Err(ViewPatchError::MissingKey {
569 operation: "replace",
570 });
571 }
572 },
573 MapPatchOp::Clear => {
574 return Err(ViewPatchError::InvalidPatchShape {
575 expected: "Clear to be handled before apply phase",
576 actual: "Clear reached apply phase",
577 });
578 }
579 }
580 }
581
582 Ok(())
583 }
584}
585
586impl<T> UpdateView for BTreeSet<T>
587where
588 T: UpdateView + Clone + Default + Ord,
589{
590 type UpdateViewType = Vec<SetPatch<T::UpdateViewType>>;
591
592 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
593 for patch in patches {
594 match patch {
595 SetPatch::Insert(value) => {
596 let mut elem = T::default();
597 elem.merge(value).map_err(|err| err.with_field("insert"))?;
598 self.insert(elem);
599 }
600 SetPatch::Remove(value) => {
601 let mut elem = T::default();
602 elem.merge(value).map_err(|err| err.with_field("remove"))?;
603 self.remove(&elem);
604 }
605 SetPatch::Overwrite { values } => {
606 self.clear();
607
608 for (index, value) in values.into_iter().enumerate() {
609 let mut elem = T::default();
610 elem.merge(value)
611 .map_err(|err| err.with_field("overwrite").with_index(index))?;
612 self.insert(elem);
613 }
614 }
615 SetPatch::Clear => self.clear(),
616 }
617 }
618
619 Ok(())
620 }
621}
622
623impl<K, V> UpdateView for BTreeMap<K, V>
624where
625 K: UpdateView + Clone + Default + Ord,
626 V: UpdateView + Default,
627{
628 type UpdateViewType = Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>;
629
630 #[expect(clippy::too_many_lines)]
631 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
632 let mut ops = Vec::with_capacity(patches.len());
634 for patch in patches {
635 match patch {
636 MapPatch::Insert { key, value } => {
637 let mut key_value = K::default();
638 key_value
639 .merge(key)
640 .map_err(|err| err.with_field("insert").with_field("key"))?;
641 ops.push(MapPatchOp::Insert {
642 key: key_value,
643 value,
644 });
645 }
646 MapPatch::Remove { key } => {
647 let mut key_value = K::default();
648 key_value
649 .merge(key)
650 .map_err(|err| err.with_field("remove").with_field("key"))?;
651 ops.push(MapPatchOp::Remove { key: key_value });
652 }
653 MapPatch::Replace { key, value } => {
654 let mut key_value = K::default();
655 key_value
656 .merge(key)
657 .map_err(|err| err.with_field("replace").with_field("key"))?;
658 ops.push(MapPatchOp::Replace {
659 key: key_value,
660 value,
661 });
662 }
663 MapPatch::Clear => ops.push(MapPatchOp::Clear),
664 }
665 }
666
667 let mut saw_clear = false;
669 let mut touched = BTreeSet::new();
670 for op in &ops {
671 match op {
672 MapPatchOp::Clear => {
673 if saw_clear {
674 return Err(ViewPatchError::InvalidPatchShape {
675 expected: "at most one Clear operation per map patch batch",
676 actual: "duplicate Clear operations",
677 });
678 }
679 saw_clear = true;
680 if ops.len() != 1 {
681 return Err(ViewPatchError::CardinalityViolation {
682 expected: 1,
683 actual: ops.len(),
684 });
685 }
686 }
687 MapPatchOp::Insert { key, .. }
688 | MapPatchOp::Remove { key }
689 | MapPatchOp::Replace { key, .. } => {
690 if saw_clear {
691 return Err(ViewPatchError::InvalidPatchShape {
692 expected: "Clear must be the only operation in a map patch batch",
693 actual: "Clear combined with key operation",
694 });
695 }
696 if !touched.insert(key.clone()) {
697 return Err(ViewPatchError::InvalidPatchShape {
698 expected: "unique key operations per map patch batch",
699 actual: "duplicate key operation",
700 });
701 }
702 }
703 }
704 }
705 if saw_clear {
706 self.clear();
707 return Ok(());
708 }
709
710 for op in ops {
712 match op {
713 MapPatchOp::Insert { key, value } => match self.entry(key) {
714 BTreeMapEntry::Occupied(mut slot) => {
715 slot.get_mut()
716 .merge(value)
717 .map_err(|err| err.with_field("insert").with_field("value"))?;
718 }
719 BTreeMapEntry::Vacant(slot) => {
720 let mut value_value = V::default();
721 value_value
722 .merge(value)
723 .map_err(|err| err.with_field("insert").with_field("value"))?;
724 slot.insert(value_value);
725 }
726 },
727 MapPatchOp::Remove { key } => {
728 if self.remove(&key).is_none() {
729 return Err(ViewPatchError::MissingKey {
730 operation: "remove",
731 });
732 }
733 }
734 MapPatchOp::Replace { key, value } => match self.entry(key) {
735 BTreeMapEntry::Occupied(mut slot) => {
736 slot.get_mut()
737 .merge(value)
738 .map_err(|err| err.with_field("replace").with_field("value"))?;
739 }
740 BTreeMapEntry::Vacant(_) => {
741 return Err(ViewPatchError::MissingKey {
742 operation: "replace",
743 });
744 }
745 },
746 MapPatchOp::Clear => {
747 return Err(ViewPatchError::InvalidPatchShape {
748 expected: "Clear to be handled before apply phase",
749 actual: "Clear reached apply phase",
750 });
751 }
752 }
753 }
754
755 Ok(())
756 }
757}
758
759macro_rules! impl_update_view {
760 ($($type:ty),*) => {
761 $(
762 impl UpdateView for $type {
763 type UpdateViewType = Self;
764
765 fn merge(
766 &mut self,
767 update: Self::UpdateViewType,
768 ) -> Result<(), ViewPatchError> {
769 *self = update;
770
771 Ok(())
772 }
773 }
774 )*
775 };
776}
777
778impl_update_view!(bool, i8, i16, i32, i64, u8, u16, u32, u64, String);