Skip to main content

icydb_core/traits/
view.rs

1use crate::view::{ListPatch, MapPatch, SetPatch};
2use candid::CandidType;
3use std::{
4    collections::{HashMap, HashSet, hash_map::Entry as HashMapEntry},
5    hash::{BuildHasher, Hash},
6    iter::IntoIterator,
7};
8
9///
10/// View
11/// Recursive for all field/value nodes
12/// `from_view` is infallible; view values are treated as canonical.
13///
14
15pub trait View: Sized {
16    type ViewType: Default;
17
18    fn to_view(&self) -> Self::ViewType;
19    fn from_view(view: Self::ViewType) -> Self;
20}
21
22impl View for String {
23    type ViewType = Self;
24
25    fn to_view(&self) -> Self::ViewType {
26        self.clone()
27    }
28
29    fn from_view(view: Self::ViewType) -> Self {
30        view
31    }
32}
33
34// Make Box<T> *not* appear in the view type
35impl<T: View> View for Box<T> {
36    type ViewType = T::ViewType;
37
38    fn to_view(&self) -> Self::ViewType {
39        // Delegate to inner value
40        T::to_view(self.as_ref())
41    }
42
43    fn from_view(view: Self::ViewType) -> Self {
44        // Re-box after reconstructing inner
45        Self::new(T::from_view(view))
46    }
47}
48
49impl<T: View> View for Option<T> {
50    type ViewType = Option<T::ViewType>;
51
52    fn to_view(&self) -> Self::ViewType {
53        self.as_ref().map(View::to_view)
54    }
55
56    fn from_view(view: Self::ViewType) -> Self {
57        view.map(T::from_view)
58    }
59}
60
61impl<T: View> View for Vec<T> {
62    type ViewType = Vec<T::ViewType>;
63
64    fn to_view(&self) -> Self::ViewType {
65        self.iter().map(View::to_view).collect()
66    }
67
68    fn from_view(view: Self::ViewType) -> Self {
69        view.into_iter().map(T::from_view).collect()
70    }
71}
72
73impl<T, S> View for HashSet<T, S>
74where
75    T: View + Eq + Hash + Clone,
76    S: BuildHasher + Default,
77{
78    type ViewType = Vec<T::ViewType>;
79
80    fn to_view(&self) -> Self::ViewType {
81        self.iter().map(View::to_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<K, V, S> View for HashMap<K, V, S>
90where
91    K: View + Eq + Hash + Clone,
92    V: View,
93    S: BuildHasher + Default,
94{
95    type ViewType = Vec<(K::ViewType, V::ViewType)>;
96
97    fn to_view(&self) -> Self::ViewType {
98        self.iter()
99            .map(|(k, v)| (k.to_view(), v.to_view()))
100            .collect()
101    }
102
103    fn from_view(view: Self::ViewType) -> Self {
104        view.into_iter()
105            .map(|(k, v)| (K::from_view(k), V::from_view(v)))
106            .collect()
107    }
108}
109
110#[macro_export]
111macro_rules! impl_view {
112    ($($type:ty),*) => {
113        $(
114            impl View for $type {
115                type ViewType = Self;
116
117                fn to_view(&self) -> Self::ViewType {
118                    *self
119                }
120
121                fn from_view(view: Self::ViewType) -> Self {
122                    view
123                }
124            }
125        )*
126    };
127}
128
129impl_view!(bool, i8, i16, i32, i64, u8, u16, u32, u64);
130
131impl View for f32 {
132    type ViewType = Self;
133
134    fn to_view(&self) -> Self::ViewType {
135        *self
136    }
137
138    fn from_view(view: Self::ViewType) -> Self {
139        if view.is_finite() {
140            if view == 0.0 { 0.0 } else { view }
141        } else {
142            0.0
143        }
144    }
145}
146
147impl View for f64 {
148    type ViewType = Self;
149
150    fn to_view(&self) -> Self::ViewType {
151        *self
152    }
153
154    fn from_view(view: Self::ViewType) -> Self {
155        if view.is_finite() {
156            if view == 0.0 { 0.0 } else { view }
157        } else {
158            0.0
159        }
160    }
161}
162
163///
164/// CreateView
165///
166
167pub trait CreateView {
168    type CreateViewType: CandidType + Default;
169}
170
171///
172/// UpdateView
173///
174
175pub trait UpdateView {
176    type UpdateViewType: CandidType + Default;
177
178    /// merge the updateview into self
179    fn merge(&mut self, _: Self::UpdateViewType) {}
180}
181
182impl<T> UpdateView for Option<T>
183where
184    T: UpdateView + Default,
185{
186    type UpdateViewType = Option<T::UpdateViewType>;
187
188    fn merge(&mut self, update: Self::UpdateViewType) {
189        match update {
190            None => {
191                // Field was provided (outer Some), inner None means explicit delete
192                *self = None;
193            }
194            Some(inner_update) => {
195                if let Some(inner_value) = self.as_mut() {
196                    inner_value.merge(inner_update);
197                } else {
198                    let mut new_value = T::default();
199                    new_value.merge(inner_update);
200                    *self = Some(new_value);
201                }
202            }
203        }
204    }
205}
206
207impl<T> UpdateView for Vec<T>
208where
209    T: UpdateView + Default,
210{
211    // Payload is T::UpdateViewType, which *is* CandidType
212    type UpdateViewType = Vec<ListPatch<T::UpdateViewType>>;
213
214    fn merge(&mut self, patches: Self::UpdateViewType) {
215        for patch in patches {
216            match patch {
217                ListPatch::Update { index, patch } => {
218                    if let Some(elem) = self.get_mut(index) {
219                        elem.merge(patch);
220                    }
221                }
222                ListPatch::Insert { index, value } => {
223                    let mut elem = T::default();
224                    elem.merge(value);
225                    let idx = index.min(self.len());
226                    self.insert(idx, elem);
227                }
228                ListPatch::Push { value } => {
229                    let mut elem = T::default();
230                    elem.merge(value);
231                    self.push(elem);
232                }
233                ListPatch::Overwrite { values } => {
234                    self.clear();
235                    self.reserve(values.len());
236
237                    for value in values {
238                        let mut elem = T::default();
239                        elem.merge(value);
240                        self.push(elem);
241                    }
242                }
243                ListPatch::Remove { index } => {
244                    if index < self.len() {
245                        self.remove(index);
246                    }
247                }
248                ListPatch::Clear => self.clear(),
249            }
250        }
251    }
252}
253
254impl<T, S> UpdateView for HashSet<T, S>
255where
256    T: UpdateView + Default + Eq + Hash,
257    S: BuildHasher + Default,
258{
259    type UpdateViewType = Vec<SetPatch<T::UpdateViewType>>;
260
261    fn merge(&mut self, patches: Self::UpdateViewType) {
262        for patch in patches {
263            match patch {
264                SetPatch::Insert(value) => {
265                    let mut elem = T::default();
266                    elem.merge(value);
267                    self.insert(elem);
268                }
269                SetPatch::Remove(value) => {
270                    let mut elem = T::default();
271                    elem.merge(value);
272                    self.remove(&elem);
273                }
274                SetPatch::Overwrite { values } => {
275                    self.clear();
276
277                    for value in values {
278                        let mut elem = T::default();
279                        elem.merge(value);
280                        self.insert(elem);
281                    }
282                }
283                SetPatch::Clear => self.clear(),
284            }
285        }
286    }
287}
288
289impl<K, V, S> UpdateView for HashMap<K, V, S>
290where
291    K: UpdateView + Default + Eq + Hash,
292    V: UpdateView + Default,
293    S: BuildHasher + Default,
294{
295    type UpdateViewType = Vec<MapPatch<K::UpdateViewType, V::UpdateViewType>>;
296
297    fn merge(&mut self, patches: Self::UpdateViewType) {
298        for patch in patches {
299            match patch {
300                MapPatch::Upsert { key, value } => {
301                    let mut key_value = K::default();
302                    key_value.merge(key);
303
304                    match self.entry(key_value) {
305                        HashMapEntry::Occupied(mut slot) => {
306                            slot.get_mut().merge(value);
307                        }
308                        HashMapEntry::Vacant(slot) => {
309                            let mut value_value = V::default();
310                            value_value.merge(value);
311                            slot.insert(value_value);
312                        }
313                    }
314                }
315                MapPatch::Remove { key } => {
316                    let mut key_value = K::default();
317                    key_value.merge(key);
318                    self.remove(&key_value);
319                }
320                MapPatch::Overwrite { entries } => {
321                    self.clear();
322                    self.reserve(entries.len());
323
324                    for (key, value) in entries {
325                        let mut key_value = K::default();
326                        key_value.merge(key);
327
328                        let mut value_value = V::default();
329                        value_value.merge(value);
330                        self.insert(key_value, value_value);
331                    }
332                }
333                MapPatch::Clear => self.clear(),
334            }
335        }
336    }
337}
338
339macro_rules! impl_update_view {
340    ($($type:ty),*) => {
341        $(
342            impl UpdateView for $type {
343                type UpdateViewType = Self;
344
345                fn merge(&mut self, update: Self::UpdateViewType) {
346                    *self = update;
347                }
348            }
349        )*
350    };
351}
352
353impl_update_view!(bool, i8, i16, i32, i64, u8, u16, u32, u64, String);