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