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