Skip to main content

icydb_core/
view.rs

1use crate::traits::{AsView, CreateView, UpdateView};
2use candid::CandidType;
3use serde::{Deserialize, Serialize};
4
5///
6/// Type Aliases
7///
8
9pub type View<T> = <T as AsView>::ViewType;
10pub type Create<T> = <T as CreateView>::CreateViewType;
11pub type Update<T> = <T as UpdateView>::UpdateViewType;
12
13///
14/// ListPatch
15///
16
17/// Positional list patches applied in order.
18/// Indices refer to the list state at the time each patch executes.
19/// `Insert` clamps out-of-bounds indices to the tail; `Remove` ignores invalid indices.
20/// `Update` only applies to existing elements and never creates new entries.
21/// `Overwrite` replaces the entire list with the provided values.
22#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
23pub enum ListPatch<U> {
24    Update { index: usize, patch: U },
25    Insert { index: usize, value: U },
26    Push { value: U },
27    Overwrite { values: Vec<U> },
28    Remove { index: usize },
29    Clear,
30}
31
32///
33/// SetPatch
34///
35
36/// Set operations applied in-order; `Overwrite` replaces the entire set.
37#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
38pub enum SetPatch<U> {
39    Insert(U),
40    Remove(U),
41    Overwrite { values: Vec<U> },
42    Clear,
43}
44
45///
46/// MapPatch
47///
48/// Deterministic map mutations.
49///
50/// - Maps are unordered values; insertion order is discarded.
51/// - `Insert` is an upsert.
52/// - `Replace` requires an existing key.
53/// - `Remove` requires an existing key.
54/// - `Clear` must be the only patch in the batch.
55///
56/// Invalid patch shapes and missing-key operations are returned as typed
57/// `ViewPatchError` values by `UpdateView::merge`.
58///
59#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
60pub enum MapPatch<K, V> {
61    Insert { key: K, value: V },
62    Remove { key: K },
63    Replace { key: K, value: V },
64    Clear,
65}
66
67impl<K, V> From<(K, Option<V>)> for MapPatch<K, V> {
68    fn from((key, value): (K, Option<V>)) -> Self {
69        match value {
70            Some(value) => Self::Insert { key, value },
71            None => Self::Remove { key },
72        }
73    }
74}
75
76///
77/// TESTS
78///
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use std::collections::{BTreeMap, HashMap, HashSet};
84
85    #[test]
86    fn vec_partial_patches() {
87        let mut values = vec![10u8, 20, 30];
88        let patches = vec![
89            ListPatch::Update {
90                index: 1,
91                patch: 99,
92            },
93            ListPatch::Insert {
94                index: 1,
95                value: 11,
96            },
97            ListPatch::Remove { index: 0 },
98        ];
99
100        values
101            .merge(patches)
102            .expect("list patch merge should succeed");
103        assert_eq!(values, vec![11, 99, 30]);
104    }
105
106    #[test]
107    fn vec_overwrite_replaces_contents() {
108        let mut values = vec![1u8, 2, 3];
109        let patches = vec![ListPatch::Overwrite {
110            values: vec![9u8, 8],
111        }];
112
113        values
114            .merge(patches)
115            .expect("list patch merge should succeed");
116        assert_eq!(values, vec![9, 8]);
117    }
118
119    #[test]
120    fn set_insert_remove_without_clear() {
121        let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
122        let patches = vec![SetPatch::Remove(2), SetPatch::Insert(4)];
123
124        set.merge(patches).expect("set patch merge should succeed");
125        let expected: HashSet<u8> = [1, 3, 4].into_iter().collect();
126        assert_eq!(set, expected);
127    }
128
129    #[test]
130    fn set_overwrite_replaces_contents() {
131        let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
132        let patches = vec![SetPatch::Overwrite {
133            values: vec![3u8, 4, 5],
134        }];
135
136        set.merge(patches).expect("set patch merge should succeed");
137        let expected: HashSet<u8> = [3, 4, 5].into_iter().collect();
138        assert_eq!(set, expected);
139    }
140
141    #[test]
142    fn map_insert_in_place_and_remove() {
143        let mut map: HashMap<String, u8> = [("a".into(), 1u8), ("keep".into(), 9u8)]
144            .into_iter()
145            .collect();
146
147        let patches = vec![
148            MapPatch::Insert {
149                key: "a".to_string(),
150                value: 5u8,
151            },
152            MapPatch::Remove {
153                key: "keep".to_string(),
154            },
155            MapPatch::Insert {
156                key: "insert".to_string(),
157                value: 7u8,
158            },
159        ];
160
161        map.merge(patches).expect("map patch merge should succeed");
162
163        assert_eq!(map.get("a"), Some(&5));
164        assert_eq!(map.get("insert"), Some(&7));
165        assert!(!map.contains_key("keep"));
166    }
167
168    #[test]
169    fn map_replace_updates_existing_entry() {
170        let mut map: HashMap<String, u8> = [("keep".into(), 1u8), ("replace".into(), 2u8)]
171            .into_iter()
172            .collect();
173
174        let patches = vec![MapPatch::Replace {
175            key: "replace".to_string(),
176            value: 8u8,
177        }];
178
179        map.merge(patches).expect("map patch merge should succeed");
180
181        assert_eq!(map.get("keep"), Some(&1));
182        assert_eq!(map.get("replace"), Some(&8));
183    }
184
185    #[test]
186    fn btree_map_clear_replaces_with_empty_map() {
187        let mut map: BTreeMap<String, u8> =
188            [("a".into(), 1u8), ("b".into(), 2u8)].into_iter().collect();
189
190        map.merge(vec![MapPatch::Clear])
191            .expect("map clear patch should succeed");
192
193        assert!(map.is_empty());
194    }
195
196    #[test]
197    fn map_remove_missing_key_returns_error() {
198        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
199        let err = map
200            .merge(vec![MapPatch::Remove {
201                key: "missing".to_string(),
202            }])
203            .expect_err("missing remove key should fail");
204        assert_eq!(
205            err,
206            crate::traits::ViewPatchError::MissingKey {
207                operation: "remove"
208            }
209        );
210    }
211
212    #[test]
213    fn map_replace_missing_key_returns_error() {
214        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
215        let err = map
216            .merge(vec![MapPatch::Replace {
217                key: "missing".to_string(),
218                value: 3u8,
219            }])
220            .expect_err("missing replace key should fail");
221        assert_eq!(
222            err,
223            crate::traits::ViewPatchError::MissingKey {
224                operation: "replace"
225            }
226        );
227    }
228
229    #[test]
230    fn map_clear_with_other_operations_returns_error() {
231        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
232        let err = map
233            .merge(vec![
234                MapPatch::Clear,
235                MapPatch::Insert {
236                    key: "b".to_string(),
237                    value: 2u8,
238                },
239            ])
240            .expect_err("clear combined with key ops should fail");
241        assert_eq!(
242            err,
243            crate::traits::ViewPatchError::CardinalityViolation {
244                expected: 1,
245                actual: 2,
246            }
247        );
248    }
249
250    #[test]
251    fn map_duplicate_key_operations_returns_error() {
252        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
253        let err = map
254            .merge(vec![
255                MapPatch::Insert {
256                    key: "a".to_string(),
257                    value: 3u8,
258                },
259                MapPatch::Replace {
260                    key: "a".to_string(),
261                    value: 4u8,
262                },
263            ])
264            .expect_err("duplicate key operations should fail");
265        assert_eq!(
266            err,
267            crate::traits::ViewPatchError::InvalidPatchShape {
268                expected: "unique key operations per map patch batch",
269                actual: "duplicate key operation",
270            }
271        );
272    }
273}