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.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}