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 fail loudly.
57/// Missing-key operations are considered programmer errors and will pani
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.merge(patches);
101        assert_eq!(values, vec![11, 99, 30]);
102    }
103
104    #[test]
105    fn vec_overwrite_replaces_contents() {
106        let mut values = vec![1u8, 2, 3];
107        let patches = vec![ListPatch::Overwrite {
108            values: vec![9u8, 8],
109        }];
110
111        values.merge(patches);
112        assert_eq!(values, vec![9, 8]);
113    }
114
115    #[test]
116    fn set_insert_remove_without_clear() {
117        let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
118        let patches = vec![SetPatch::Remove(2), SetPatch::Insert(4)];
119
120        set.merge(patches);
121        let expected: HashSet<u8> = [1, 3, 4].into_iter().collect();
122        assert_eq!(set, expected);
123    }
124
125    #[test]
126    fn set_overwrite_replaces_contents() {
127        let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
128        let patches = vec![SetPatch::Overwrite {
129            values: vec![3u8, 4, 5],
130        }];
131
132        set.merge(patches);
133        let expected: HashSet<u8> = [3, 4, 5].into_iter().collect();
134        assert_eq!(set, expected);
135    }
136
137    #[test]
138    fn map_insert_in_place_and_remove() {
139        let mut map: HashMap<String, u8> = [("a".into(), 1u8), ("keep".into(), 9u8)]
140            .into_iter()
141            .collect();
142
143        let patches = vec![
144            MapPatch::Insert {
145                key: "a".to_string(),
146                value: 5u8,
147            },
148            MapPatch::Remove {
149                key: "keep".to_string(),
150            },
151            MapPatch::Insert {
152                key: "insert".to_string(),
153                value: 7u8,
154            },
155        ];
156
157        map.merge(patches);
158
159        assert_eq!(map.get("a"), Some(&5));
160        assert_eq!(map.get("insert"), Some(&7));
161        assert!(!map.contains_key("keep"));
162    }
163
164    #[test]
165    fn map_replace_updates_existing_entry() {
166        let mut map: HashMap<String, u8> = [("keep".into(), 1u8), ("replace".into(), 2u8)]
167            .into_iter()
168            .collect();
169
170        let patches = vec![MapPatch::Replace {
171            key: "replace".to_string(),
172            value: 8u8,
173        }];
174
175        map.merge(patches);
176
177        assert_eq!(map.get("keep"), Some(&1));
178        assert_eq!(map.get("replace"), Some(&8));
179    }
180
181    #[test]
182    fn btree_map_clear_replaces_with_empty_map() {
183        let mut map: BTreeMap<String, u8> =
184            [("a".into(), 1u8), ("b".into(), 2u8)].into_iter().collect();
185
186        map.merge(vec![MapPatch::Clear]);
187
188        assert!(map.is_empty());
189    }
190
191    #[test]
192    #[should_panic(expected = "map patch remove target missing")]
193    fn map_remove_missing_key_panics() {
194        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
195        map.merge(vec![MapPatch::Remove {
196            key: "missing".to_string(),
197        }]);
198    }
199
200    #[test]
201    #[should_panic(expected = "map patch replace target missing")]
202    fn map_replace_missing_key_panics() {
203        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
204        map.merge(vec![MapPatch::Replace {
205            key: "missing".to_string(),
206            value: 3u8,
207        }]);
208    }
209
210    #[test]
211    #[should_panic(expected = "map patch batch cannot combine Clear with key operations")]
212    fn map_clear_with_other_operations_panics() {
213        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
214        map.merge(vec![
215            MapPatch::Clear,
216            MapPatch::Insert {
217                key: "b".to_string(),
218                value: 2u8,
219            },
220        ]);
221    }
222
223    #[test]
224    #[should_panic(expected = "map patch batch contains duplicate key operations")]
225    fn map_duplicate_key_operations_panics() {
226        let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
227        map.merge(vec![
228            MapPatch::Insert {
229                key: "a".to_string(),
230                value: 3u8,
231            },
232            MapPatch::Replace {
233                key: "a".to_string(),
234                value: 4u8,
235            },
236        ]);
237    }
238}