Skip to main content

icydb_core/traits/
view.rs

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
13///
14/// AsView
15///
16/// Recursive for all field/value nodes
17/// `from_view` is infallible; view values are treated as canonical.
18///
19
20pub 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
47// Make Box<T> *not* appear in the view type
48impl<T: AsView> AsView for Box<T> {
49    type ViewType = T::ViewType;
50
51    fn as_view(&self) -> Self::ViewType {
52        // Delegate to inner value
53        T::as_view(self.as_ref())
54    }
55
56    fn from_view(view: Self::ViewType) -> Self {
57        // Re-box after reconstructing inner
58        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
211///
212/// CreateView
213///
214
215pub trait CreateView: AsView {
216    /// Payload accepted when creating this value.
217    ///
218    /// This is often equal to ViewType, but may differ
219    /// (e.g. Option<T>, defaults, omissions).
220    type CreateViewType: CandidType + Default;
221
222    fn from_create_view(view: Self::CreateViewType) -> Self;
223}
224
225///
226/// ViewPatchError
227///
228/// Structured failures for user-driven patch application.
229///
230#[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
245///
246/// UpdateView
247///
248
249pub trait UpdateView: AsView {
250    /// Payload accepted when updating this value.
251    type UpdateViewType: CandidType + Default;
252
253    /// Merge the update payload into self.
254    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                // Field was provided (outer Some), inner None means explicit delete
269                *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    // Payload is T::UpdateViewType, which *is* CandidType
291    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
372/// Internal representation used to normalize map patches before application.
373enum 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        // Phase 1: decode patch payload into concrete keys.
391        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        // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
420        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        // Phase 3: apply deterministic map operations.
463        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        // Phase 1: decode patch payload into concrete keys.
551        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        // Phase 2: reject ambiguous patch batches to keep semantics deterministic.
580        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        // Phase 3: apply deterministic map operations.
623        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);