Skip to main content

icydb_core/
view.rs

1use crate::traits::{CreateView, UpdateView, View as OtherView};
2use candid::CandidType;
3use serde::{Deserialize, Serialize};
4
5///
6/// Type Aliases
7///
8
9pub type View<T> = <T as OtherView>::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
49/// Map mutations applied sequentially; later operations win. `Overwrite` replaces
50/// the entire map with the provided entries.
51#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
52pub enum MapPatch<K, V> {
53    Upsert { key: K, value: V },
54    Remove { key: K },
55    Overwrite { entries: Vec<(K, V)> },
56    Clear,
57}
58
59impl<K, V> From<(K, Option<V>)> for MapPatch<K, V> {
60    fn from((key, value): (K, Option<V>)) -> Self {
61        match value {
62            Some(value) => Self::Upsert { key, value },
63            None => Self::Remove { key },
64        }
65    }
66}
67
68///
69/// TESTS
70///
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use std::collections::{HashMap, HashSet};
76
77    #[test]
78    fn vec_partial_patches() {
79        let mut values = vec![10u8, 20, 30];
80        let patches = vec![
81            ListPatch::Update {
82                index: 1,
83                patch: 99,
84            },
85            ListPatch::Insert {
86                index: 1,
87                value: 11,
88            },
89            ListPatch::Remove { index: 0 },
90        ];
91
92        values.merge(patches);
93        assert_eq!(values, vec![11, 99, 30]);
94    }
95
96    #[test]
97    fn vec_overwrite_replaces_contents() {
98        let mut values = vec![1u8, 2, 3];
99        let patches = vec![ListPatch::Overwrite {
100            values: vec![9u8, 8],
101        }];
102
103        values.merge(patches);
104        assert_eq!(values, vec![9, 8]);
105    }
106
107    #[test]
108    fn set_insert_remove_without_clear() {
109        let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
110        let patches = vec![SetPatch::Remove(2), SetPatch::Insert(4)];
111
112        set.merge(patches);
113        let expected: HashSet<u8> = [1, 3, 4].into_iter().collect();
114        assert_eq!(set, expected);
115    }
116
117    #[test]
118    fn set_overwrite_replaces_contents() {
119        let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
120        let patches = vec![SetPatch::Overwrite {
121            values: vec![3u8, 4, 5],
122        }];
123
124        set.merge(patches);
125        let expected: HashSet<u8> = [3, 4, 5].into_iter().collect();
126        assert_eq!(set, expected);
127    }
128
129    #[test]
130    fn map_upsert_in_place_and_remove() {
131        let mut map: HashMap<String, u8> = [("a".into(), 1u8), ("keep".into(), 9u8)]
132            .into_iter()
133            .collect();
134
135        let patches = vec![
136            MapPatch::Upsert {
137                key: "a".to_string(),
138                value: 5u8,
139            },
140            MapPatch::Remove {
141                key: "keep".to_string(),
142            },
143            MapPatch::Upsert {
144                key: "insert".to_string(),
145                value: 7u8,
146            },
147        ];
148
149        map.merge(patches);
150
151        assert_eq!(map.get("a"), Some(&5));
152        assert_eq!(map.get("insert"), Some(&7));
153        assert!(!map.contains_key("keep"));
154    }
155
156    #[test]
157    fn map_overwrite_replaces_contents() {
158        let mut map: HashMap<String, u8> = [("keep".into(), 1u8), ("drop".into(), 2u8)]
159            .into_iter()
160            .collect();
161
162        let patches = vec![MapPatch::Overwrite {
163            entries: vec![
164                ("first".to_string(), 9u8),
165                ("second".to_string(), 5u8),
166                ("first".to_string(), 1u8),
167            ],
168        }];
169
170        map.merge(patches);
171
172        assert_eq!(map.get("first"), Some(&1));
173        assert_eq!(map.get("second"), Some(&5));
174        assert!(!map.contains_key("keep"));
175        assert!(!map.contains_key("drop"));
176    }
177}