1use candid::CandidType;
2use serde::{Deserialize, Serialize};
3
4#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
17pub enum MapPatch<K, V> {
18 Insert { key: K, value: V },
19 Remove { key: K },
20 Replace { key: K, value: V },
21 Clear,
22}
23
24impl<K, V> From<(K, Option<V>)> for MapPatch<K, V> {
25 fn from((key, value): (K, Option<V>)) -> Self {
26 match value {
27 Some(value) => Self::Insert { key, value },
28 None => Self::Remove { key },
29 }
30 }
31}
32
33#[cfg(test)]
38mod tests {
39 use super::*;
40 use crate::{patch::MergePatchError, traits::UpdateView};
41 use std::collections::{BTreeMap, HashMap};
42
43 #[test]
44 fn map_replace_updates_existing_entry() {
45 let mut map: HashMap<String, u8> = [("keep".into(), 1u8), ("replace".into(), 2u8)]
46 .into_iter()
47 .collect();
48
49 let patches = vec![MapPatch::Replace {
50 key: "replace".to_string(),
51 value: 8u8,
52 }];
53
54 map.merge(patches).expect("map patch merge should succeed");
55
56 assert_eq!(map.get("keep"), Some(&1));
57 assert_eq!(map.get("replace"), Some(&8));
58 }
59
60 #[test]
61 fn btree_map_clear_replaces_with_empty_map() {
62 let mut map: BTreeMap<String, u8> =
63 [("a".into(), 1u8), ("b".into(), 2u8)].into_iter().collect();
64
65 map.merge(vec![MapPatch::Clear])
66 .expect("map clear patch should succeed");
67
68 assert!(map.is_empty());
69 }
70
71 #[test]
72 fn map_remove_missing_key_is_noop() {
73 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
74 map.merge(vec![MapPatch::Remove {
75 key: "missing".to_string(),
76 }])
77 .expect("missing remove key should be ignored");
78 assert_eq!(map.get("a"), Some(&1));
79 }
80
81 #[test]
82 fn map_replace_missing_key_is_noop() {
83 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
84 map.merge(vec![MapPatch::Replace {
85 key: "missing".to_string(),
86 value: 3u8,
87 }])
88 .expect("missing replace key should be ignored");
89 assert_eq!(map.get("a"), Some(&1));
90 }
91
92 #[test]
93 fn map_clear_with_other_operations_returns_error() {
94 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
95 let err = map
96 .merge(vec![
97 MapPatch::Clear,
98 MapPatch::Insert {
99 key: "b".to_string(),
100 value: 2u8,
101 },
102 ])
103 .expect_err("clear combined with key ops should fail");
104 assert!(matches!(
105 err.leaf(),
106 MergePatchError::CardinalityViolation {
107 expected: 1,
108 actual: 2,
109 }
110 ));
111 }
112
113 #[test]
114 fn map_duplicate_key_operations_returns_error() {
115 let mut map: HashMap<String, u8> = std::iter::once(("a".into(), 1u8)).collect();
116 let err = map
117 .merge(vec![
118 MapPatch::Insert {
119 key: "a".to_string(),
120 value: 3u8,
121 },
122 MapPatch::Replace {
123 key: "a".to_string(),
124 value: 4u8,
125 },
126 ])
127 .expect_err("duplicate key operations should fail");
128 assert!(matches!(
129 err.leaf(),
130 MergePatchError::InvalidShape {
131 expected: "unique key operations per map patch batch",
132 actual: "duplicate key operation",
133 }
134 ));
135 }
136}