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)]
61pub enum MapPatch<K, V> {
62 Insert { key: K, value: V },
63 Remove { key: K },
64 Replace { key: K, value: V },
65 Clear,
66}
67
68impl<K, V> From<(K, Option<V>)> for MapPatch<K, V> {
69 fn from((key, value): (K, Option<V>)) -> Self {
70 match value {
71 Some(value) => Self::Insert { key, value },
72 None => Self::Remove { key },
73 }
74 }
75}
76
77#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::error::ErrorClass;
85 use crate::traits::ViewPatchError;
86 use std::collections::{BTreeMap, HashMap, HashSet};
87
88 #[test]
89 fn vec_partial_patches() {
90 let mut values = vec![10u8, 20, 30];
91 let patches = vec![
92 ListPatch::Update {
93 index: 1,
94 patch: 99,
95 },
96 ListPatch::Insert {
97 index: 1,
98 value: 11,
99 },
100 ListPatch::Remove { index: 0 },
101 ];
102
103 values
104 .merge(patches)
105 .expect("list patch merge should succeed");
106 assert_eq!(values, vec![11, 99, 30]);
107 }
108
109 #[test]
110 fn vec_overwrite_replaces_contents() {
111 let mut values = vec![1u8, 2, 3];
112 let patches = vec![ListPatch::Overwrite {
113 values: vec![9u8, 8],
114 }];
115
116 values
117 .merge(patches)
118 .expect("list patch merge should succeed");
119 assert_eq!(values, vec![9, 8]);
120 }
121
122 #[test]
123 fn set_insert_remove_without_clear() {
124 let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
125 let patches = vec![SetPatch::Remove(2), SetPatch::Insert(4)];
126
127 set.merge(patches).expect("set patch merge should succeed");
128 let expected: HashSet<u8> = [1, 3, 4].into_iter().collect();
129 assert_eq!(set, expected);
130 }
131
132 #[test]
133 fn set_overwrite_replaces_contents() {
134 let mut set: HashSet<u8> = [1, 2, 3].into_iter().collect();
135 let patches = vec![SetPatch::Overwrite {
136 values: vec![3u8, 4, 5],
137 }];
138
139 set.merge(patches).expect("set patch merge should succeed");
140 let expected: HashSet<u8> = [3, 4, 5].into_iter().collect();
141 assert_eq!(set, expected);
142 }
143
144 #[test]
145 fn map_insert_in_place_and_remove() {
146 let mut map: HashMap<String, u8> = [("a".into(), 1u8), ("keep".into(), 9u8)]
147 .into_iter()
148 .collect();
149
150 let patches = vec![
151 MapPatch::Insert {
152 key: "a".to_string(),
153 value: 5u8,
154 },
155 MapPatch::Remove {
156 key: "keep".to_string(),
157 },
158 MapPatch::Insert {
159 key: "insert".to_string(),
160 value: 7u8,
161 },
162 ];
163
164 map.merge(patches).expect("map patch merge should succeed");
165
166 assert_eq!(map.get("a"), Some(&5));
167 assert_eq!(map.get("insert"), Some(&7));
168 assert!(!map.contains_key("keep"));
169 }
170
171 #[test]
172 fn map_replace_updates_existing_entry() {
173 let mut map: HashMap<String, u8> = [("keep".into(), 1u8), ("replace".into(), 2u8)]
174 .into_iter()
175 .collect();
176
177 let patches = vec![MapPatch::Replace {
178 key: "replace".to_string(),
179 value: 8u8,
180 }];
181
182 map.merge(patches).expect("map patch merge should succeed");
183
184 assert_eq!(map.get("keep"), Some(&1));
185 assert_eq!(map.get("replace"), Some(&8));
186 }
187
188 #[test]
189 fn btree_map_clear_replaces_with_empty_map() {
190 let mut map: BTreeMap<String, u8> =
191 [("a".into(), 1u8), ("b".into(), 2u8)].into_iter().collect();
192
193 map.merge(vec![MapPatch::Clear])
194 .expect("map clear patch should succeed");
195
196 assert!(map.is_empty());
197 }
198
199 #[test]
200 fn map_remove_missing_key_returns_error() {
201 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
202 let err = map
203 .merge(vec![MapPatch::Remove {
204 key: "missing".to_string(),
205 }])
206 .expect_err("missing remove key should fail");
207 assert_eq!(err.class, ErrorClass::NotFound);
208 assert!(matches!(
209 err.leaf(),
210 Some(ViewPatchError::MissingKey {
211 operation: "remove"
212 })
213 ));
214 }
215
216 #[test]
217 fn map_replace_missing_key_returns_error() {
218 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
219 let err = map
220 .merge(vec![MapPatch::Replace {
221 key: "missing".to_string(),
222 value: 3u8,
223 }])
224 .expect_err("missing replace key should fail");
225 assert_eq!(err.class, ErrorClass::NotFound);
226 assert!(matches!(
227 err.leaf(),
228 Some(ViewPatchError::MissingKey {
229 operation: "replace"
230 })
231 ));
232 }
233
234 #[test]
235 fn map_clear_with_other_operations_returns_error() {
236 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
237 let err = map
238 .merge(vec![
239 MapPatch::Clear,
240 MapPatch::Insert {
241 key: "b".to_string(),
242 value: 2u8,
243 },
244 ])
245 .expect_err("clear combined with key ops should fail");
246 assert_eq!(err.class, ErrorClass::Unsupported);
247 assert!(matches!(
248 err.leaf(),
249 Some(ViewPatchError::CardinalityViolation {
250 expected: 1,
251 actual: 2,
252 })
253 ));
254 }
255
256 #[test]
257 fn map_duplicate_key_operations_returns_error() {
258 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
259 let err = map
260 .merge(vec![
261 MapPatch::Insert {
262 key: "a".to_string(),
263 value: 3u8,
264 },
265 MapPatch::Replace {
266 key: "a".to_string(),
267 value: 4u8,
268 },
269 ])
270 .expect_err("duplicate key operations should fail");
271 assert_eq!(err.class, ErrorClass::Unsupported);
272 assert!(matches!(
273 err.leaf(),
274 Some(ViewPatchError::InvalidPatchShape {
275 expected: "unique key operations per map patch batch",
276 actual: "duplicate key operation",
277 })
278 ));
279 }
280}