1use crate::traits::{AsView, CreateView, UpdateView};
2use candid::CandidType;
3use serde::{Deserialize, Serialize};
4
5pub 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#[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#[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#[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#[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}