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