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
245pub trait UpdateView: AsView {
250 type UpdateViewType: CandidType + Default;
252
253 fn merge(&mut self, _update: Self::UpdateViewType) -> Result<(), ViewPatchError> {
255 Ok(())
256 }
257}
258
259impl<T> UpdateView for Option<T>
260where
261 T: UpdateView + Default,
262{
263 type UpdateViewType = Option<T::UpdateViewType>;
264
265 fn merge(&mut self, update: Self::UpdateViewType) -> Result<(), ViewPatchError> {
266 match update {
267 None => {
268 *self = None;
270 }
271 Some(inner_update) => {
272 if let Some(inner_value) = self.as_mut() {
273 inner_value.merge(inner_update)?;
274 } else {
275 let mut new_value = T::default();
276 new_value.merge(inner_update)?;
277 *self = Some(new_value);
278 }
279 }
280 }
281
282 Ok(())
283 }
284}
285
286impl<T> UpdateView for Vec<T>
287where
288 T: UpdateView + Default,
289{
290 type UpdateViewType = Vec<ListPatch<T::UpdateViewType>>;
292
293 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
294 for patch in patches {
295 match patch {
296 ListPatch::Update { index, patch } => {
297 if let Some(elem) = self.get_mut(index) {
298 elem.merge(patch)?;
299 }
300 }
301 ListPatch::Insert { index, value } => {
302 let mut elem = T::default();
303 elem.merge(value)?;
304 let idx = index.min(self.len());
305 self.insert(idx, elem);
306 }
307 ListPatch::Push { value } => {
308 let mut elem = T::default();
309 elem.merge(value)?;
310 self.push(elem);
311 }
312 ListPatch::Overwrite { values } => {
313 self.clear();
314 self.reserve(values.len());
315
316 for value in values {
317 let mut elem = T::default();
318 elem.merge(value)?;
319 self.push(elem);
320 }
321 }
322 ListPatch::Remove { index } => {
323 if index < self.len() {
324 self.remove(index);
325 }
326 }
327 ListPatch::Clear => self.clear(),
328 }
329 }
330
331 Ok(())
332 }
333}
334
335impl<T, S> UpdateView for HashSet<T, S>
336where
337 T: UpdateView + Clone + Default + Eq + Hash,
338 S: BuildHasher + Default,
339{
340 type UpdateViewType = Vec<SetPatch<T::UpdateViewType>>;
341
342 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
343 for patch in patches {
344 match patch {
345 SetPatch::Insert(value) => {
346 let mut elem = T::default();
347 elem.merge(value)?;
348 self.insert(elem);
349 }
350 SetPatch::Remove(value) => {
351 let mut elem = T::default();
352 elem.merge(value)?;
353 self.remove(&elem);
354 }
355 SetPatch::Overwrite { values } => {
356 self.clear();
357
358 for value in values {
359 let mut elem = T::default();
360 elem.merge(value)?;
361 self.insert(elem);
362 }
363 }
364 SetPatch::Clear => self.clear(),
365 }
366 }
367
368 Ok(())
369 }
370}
371
372enum MapPatchOp<K, V> {
374 Insert { key: K, value: V },
375 Remove { key: K },
376 Replace { key: K, value: V },
377 Clear,
378}
379
380impl<K, V, S> UpdateView for HashMap<K, V, S>
381where
382 K: UpdateView + Clone + Default + Eq + Hash,
383 V: UpdateView + Default,
384 S: BuildHasher + Default,
385{
386 type UpdateViewType = Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>;
387
388 #[expect(clippy::too_many_lines)]
389 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
390 let mut ops = Vec::with_capacity(patches.len());
392 for patch in patches {
393 match patch {
394 MapPatch::Insert { key, value } => {
395 let mut key_value = K::default();
396 key_value.merge(key)?;
397 ops.push(MapPatchOp::Insert {
398 key: key_value,
399 value,
400 });
401 }
402 MapPatch::Remove { key } => {
403 let mut key_value = K::default();
404 key_value.merge(key)?;
405 ops.push(MapPatchOp::Remove { key: key_value });
406 }
407 MapPatch::Replace { key, value } => {
408 let mut key_value = K::default();
409 key_value.merge(key)?;
410 ops.push(MapPatchOp::Replace {
411 key: key_value,
412 value,
413 });
414 }
415 MapPatch::Clear => ops.push(MapPatchOp::Clear),
416 }
417 }
418
419 let mut saw_clear = false;
421 let mut touched = HashSet::with_capacity(ops.len());
422 for op in &ops {
423 match op {
424 MapPatchOp::Clear => {
425 if saw_clear {
426 return Err(ViewPatchError::InvalidPatchShape {
427 expected: "at most one Clear operation per map patch batch",
428 actual: "duplicate Clear operations",
429 });
430 }
431 saw_clear = true;
432 if ops.len() != 1 {
433 return Err(ViewPatchError::CardinalityViolation {
434 expected: 1,
435 actual: ops.len(),
436 });
437 }
438 }
439 MapPatchOp::Insert { key, .. }
440 | MapPatchOp::Remove { key }
441 | MapPatchOp::Replace { key, .. } => {
442 if saw_clear {
443 return Err(ViewPatchError::InvalidPatchShape {
444 expected: "Clear must be the only operation in a map patch batch",
445 actual: "Clear combined with key operation",
446 });
447 }
448 if !touched.insert(key.clone()) {
449 return Err(ViewPatchError::InvalidPatchShape {
450 expected: "unique key operations per map patch batch",
451 actual: "duplicate key operation",
452 });
453 }
454 }
455 }
456 }
457 if saw_clear {
458 self.clear();
459 return Ok(());
460 }
461
462 for op in ops {
464 match op {
465 MapPatchOp::Insert { key, value } => match self.entry(key) {
466 HashMapEntry::Occupied(mut slot) => {
467 slot.get_mut().merge(value)?;
468 }
469 HashMapEntry::Vacant(slot) => {
470 let mut value_value = V::default();
471 value_value.merge(value)?;
472 slot.insert(value_value);
473 }
474 },
475 MapPatchOp::Remove { key } => {
476 if self.remove(&key).is_none() {
477 return Err(ViewPatchError::MissingKey {
478 operation: "remove",
479 });
480 }
481 }
482 MapPatchOp::Replace { key, value } => match self.entry(key) {
483 HashMapEntry::Occupied(mut slot) => {
484 slot.get_mut().merge(value)?;
485 }
486 HashMapEntry::Vacant(_) => {
487 return Err(ViewPatchError::MissingKey {
488 operation: "replace",
489 });
490 }
491 },
492 MapPatchOp::Clear => {
493 return Err(ViewPatchError::InvalidPatchShape {
494 expected: "Clear to be handled before apply phase",
495 actual: "Clear reached apply phase",
496 });
497 }
498 }
499 }
500
501 Ok(())
502 }
503}
504
505impl<T> UpdateView for BTreeSet<T>
506where
507 T: UpdateView + Clone + Default + Ord,
508{
509 type UpdateViewType = Vec<SetPatch<T::UpdateViewType>>;
510
511 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
512 for patch in patches {
513 match patch {
514 SetPatch::Insert(value) => {
515 let mut elem = T::default();
516 elem.merge(value)?;
517 self.insert(elem);
518 }
519 SetPatch::Remove(value) => {
520 let mut elem = T::default();
521 elem.merge(value)?;
522 self.remove(&elem);
523 }
524 SetPatch::Overwrite { values } => {
525 self.clear();
526
527 for value in values {
528 let mut elem = T::default();
529 elem.merge(value)?;
530 self.insert(elem);
531 }
532 }
533 SetPatch::Clear => self.clear(),
534 }
535 }
536
537 Ok(())
538 }
539}
540
541impl<K, V> UpdateView for BTreeMap<K, V>
542where
543 K: UpdateView + Clone + Default + Ord,
544 V: UpdateView + Default,
545{
546 type UpdateViewType = Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>;
547
548 #[expect(clippy::too_many_lines)]
549 fn merge(&mut self, patches: Self::UpdateViewType) -> Result<(), ViewPatchError> {
550 let mut ops = Vec::with_capacity(patches.len());
552 for patch in patches {
553 match patch {
554 MapPatch::Insert { key, value } => {
555 let mut key_value = K::default();
556 key_value.merge(key)?;
557 ops.push(MapPatchOp::Insert {
558 key: key_value,
559 value,
560 });
561 }
562 MapPatch::Remove { key } => {
563 let mut key_value = K::default();
564 key_value.merge(key)?;
565 ops.push(MapPatchOp::Remove { key: key_value });
566 }
567 MapPatch::Replace { key, value } => {
568 let mut key_value = K::default();
569 key_value.merge(key)?;
570 ops.push(MapPatchOp::Replace {
571 key: key_value,
572 value,
573 });
574 }
575 MapPatch::Clear => ops.push(MapPatchOp::Clear),
576 }
577 }
578
579 let mut saw_clear = false;
581 let mut touched = BTreeSet::new();
582 for op in &ops {
583 match op {
584 MapPatchOp::Clear => {
585 if saw_clear {
586 return Err(ViewPatchError::InvalidPatchShape {
587 expected: "at most one Clear operation per map patch batch",
588 actual: "duplicate Clear operations",
589 });
590 }
591 saw_clear = true;
592 if ops.len() != 1 {
593 return Err(ViewPatchError::CardinalityViolation {
594 expected: 1,
595 actual: ops.len(),
596 });
597 }
598 }
599 MapPatchOp::Insert { key, .. }
600 | MapPatchOp::Remove { key }
601 | MapPatchOp::Replace { key, .. } => {
602 if saw_clear {
603 return Err(ViewPatchError::InvalidPatchShape {
604 expected: "Clear must be the only operation in a map patch batch",
605 actual: "Clear combined with key operation",
606 });
607 }
608 if !touched.insert(key.clone()) {
609 return Err(ViewPatchError::InvalidPatchShape {
610 expected: "unique key operations per map patch batch",
611 actual: "duplicate key operation",
612 });
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 BTreeMapEntry::Occupied(mut slot) => {
627 slot.get_mut().merge(value)?;
628 }
629 BTreeMapEntry::Vacant(slot) => {
630 let mut value_value = V::default();
631 value_value.merge(value)?;
632 slot.insert(value_value);
633 }
634 },
635 MapPatchOp::Remove { key } => {
636 if self.remove(&key).is_none() {
637 return Err(ViewPatchError::MissingKey {
638 operation: "remove",
639 });
640 }
641 }
642 MapPatchOp::Replace { key, value } => match self.entry(key) {
643 BTreeMapEntry::Occupied(mut slot) => {
644 slot.get_mut().merge(value)?;
645 }
646 BTreeMapEntry::Vacant(_) => {
647 return Err(ViewPatchError::MissingKey {
648 operation: "replace",
649 });
650 }
651 },
652 MapPatchOp::Clear => {
653 return Err(ViewPatchError::InvalidPatchShape {
654 expected: "Clear to be handled before apply phase",
655 actual: "Clear reached apply phase",
656 });
657 }
658 }
659 }
660
661 Ok(())
662 }
663}
664
665macro_rules! impl_update_view {
666 ($($type:ty),*) => {
667 $(
668 impl UpdateView for $type {
669 type UpdateViewType = Self;
670
671 fn merge(
672 &mut self,
673 update: Self::UpdateViewType,
674 ) -> Result<(), ViewPatchError> {
675 *self = update;
676
677 Ok(())
678 }
679 }
680 )*
681 };
682}
683
684impl_update_view!(bool, i8, i16, i32, i64, u8, u16, u32, u64, String);