k8s_openapi/
deep_merge.rs

1/// A trait applies to types that support deep merging.
2///
3/// `current.merge_from(new)` behaves in the following ways:
4///
5/// ## `struct`s
6///
7/// Structs are merged by individually merging each of their fields. For example, given:
8///
9/// ```rust,ignore
10/// struct S {
11///     a: i32,
12///     b: String,
13/// }
14/// ```
15///
16/// ... the expected impl of `DeepMerge` for `S` would be:
17///
18/// ```rust,ignore
19/// impl DeepMerge for S {
20///     fn merge_from(&mut self, other: Self) {
21///         self.a.merge_from(other.a);
22///         self.b.merge_from(other.b);
23///     }
24/// }
25/// ```
26///
27/// The structs in the `k8s-openapi` crate behave this way. If you are implementing this trait for your own types, it is recommended to impl it in the same way.
28///
29/// ## `Option`
30///
31/// - If `new` is a `None`, `current` is unchanged.
32///
33/// - If `new` is a `Some(new_inner)`:
34///
35///   - If `current` is a `None`, `current` becomes `Some(new_inner)`.
36///
37///   - If `current` is a `Some(current_inner)`, `current_inner` is merged with `new_inner`.
38///
39/// ## `Vec`
40///
41/// Use an [explicit merge strategy.](`strategies::list`)
42///
43/// ## `BTreeMap`
44///
45/// Use an [explicit merge strategy.](`strategies::map`)
46///
47/// ## `serde_json::Value`
48///
49/// `serde_json::Value` is merged using the JSON merge algorithm (RFC 7396).
50///
51/// ## Other types
52///
53/// `self` is just replaced by `other`.
54pub trait DeepMerge {
55    /// Merge `other` into `self`.
56    fn merge_from(&mut self, other: Self);
57}
58
59macro_rules! default_overwriting_impl {
60    () => {
61        fn merge_from(&mut self, other: Self) {
62            *self = other;
63        }
64    };
65}
66
67impl DeepMerge for bool { default_overwriting_impl! {} }
68impl DeepMerge for i32 { default_overwriting_impl! {} }
69impl DeepMerge for i64 { default_overwriting_impl! {} }
70impl DeepMerge for f64 { default_overwriting_impl! {} }
71impl DeepMerge for String { default_overwriting_impl! {} }
72impl DeepMerge for crate::ByteString { default_overwriting_impl! {} }
73impl<Tz> DeepMerge for chrono::DateTime<Tz> where Tz: chrono::TimeZone { default_overwriting_impl! {} }
74
75impl DeepMerge for serde_json::Value {
76    fn merge_from(&mut self, other: Self) {
77        if let serde_json::Value::Object(this) = self {
78            if let serde_json::Value::Object(other) = other {
79                for (k, v) in other {
80                    if v.is_null() {
81                        this.remove(&k);
82                    }
83                    else {
84                        this.entry(k).or_insert(serde_json::Value::Null).merge_from(v);
85                    }
86                }
87
88                return;
89            }
90        }
91
92        *self = other;
93    }
94}
95
96impl<T> DeepMerge for Box<T> where T: DeepMerge {
97    fn merge_from(&mut self, other: Self) {
98        (**self).merge_from(*other);
99    }
100}
101
102impl<T> DeepMerge for Option<T> where T: DeepMerge {
103    fn merge_from(&mut self, other: Self) {
104        if let Some(other) = other {
105            if let Some(s) = self {
106                s.merge_from(other);
107            }
108            else {
109                *self = Some(other);
110            }
111        }
112    }
113}
114
115/// Strategies for merging collections.
116pub mod strategies {
117    /// Strategies for merging [`Vec`]s.
118    ///
119    /// These correspond to [`JSONSchemaProps.x-kubernetes-list-type`](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#jsonschemaprops-v1-apiextensions-k8s-io).
120    pub mod list {
121        mod private {
122            pub trait AsOptVec {
123                type Item;
124
125                fn set_if_some(&mut self, new: Self);
126                fn as_mut_opt(&mut self) -> Option<&mut Vec<Self::Item>>;
127                fn into_opt(self) -> Option<Vec<Self::Item>>;
128            }
129
130            impl<T> AsOptVec for Vec<T> {
131                type Item = T;
132
133                fn set_if_some(&mut self, new: Self) {
134                    *self = new;
135                }
136
137                fn as_mut_opt(&mut self) -> Option<&mut Self> {
138                    Some(self)
139                }
140
141                fn into_opt(self) -> Option<Self> {
142                    Some(self)
143                }
144            }
145
146            impl<T> AsOptVec for Option<Vec<T>> {
147                type Item = T;
148
149                fn set_if_some(&mut self, new: Self) {
150                    if new.is_some() {
151                        *self = new;
152                    }
153                }
154
155                fn as_mut_opt(&mut self) -> Option<&mut Vec<T>> {
156                    self.as_mut()
157                }
158
159                fn into_opt(self) -> Self {
160                    self
161                }
162            }
163        }
164        use private::AsOptVec;
165
166        /// The whole list is treated as one scalar value, and will be replaced with the new (non-[`None`]) value.
167        pub fn atomic<V>(current: &mut V, new: V) where V: AsOptVec {
168            current.set_if_some(new);
169        }
170
171        /// The list is treated as a map.
172        ///
173        /// Any items with matching keys will be deep-merged. Any items in `new` that are not in `current` will be appended to `current`.
174        pub fn map<V>(
175            current: &mut V,
176            new: V,
177            key_comparators: &[fn(&V::Item, &V::Item) -> bool],
178            merge_item: fn(&mut V::Item, V::Item),
179        )
180        where
181            V: AsOptVec,
182        {
183            if let Some(current) = current.as_mut_opt() {
184                for new_item in new.into_opt().into_iter().flatten() {
185                    if let Some(current_item) = current.iter_mut().find(|current_item| key_comparators.iter().all(|&f| f(&**current_item, &new_item))) {
186                        merge_item(current_item, new_item);
187                    }
188                    else {
189                        current.push(new_item);
190                    }
191                }
192            }
193            else {
194                current.set_if_some(new);
195            }
196        }
197
198        /// The list is treated as a set.
199        ///
200        /// Items from `new` will be appended to `current`, _unless_ `current` already contains an equal item.
201        pub fn set<V>(current: &mut V, new: V) where V: AsOptVec, V::Item: PartialEq {
202            if let Some(current) = current.as_mut_opt() {
203                for item in new.into_opt().into_iter().flatten() {
204                    if !current.contains(&item) {
205                        current.push(item);
206                    }
207                }
208            }
209            else {
210                current.set_if_some(new);
211            }
212        }
213    }
214
215    /// Strategies for merging [`BTreeMap`s.](std::collections::BTreeMap)
216    ///
217    /// These correspond to [`JSONSchemaProps.x-kubernetes-map-type`](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#jsonschemaprops-v1-apiextensions-k8s-io).
218    pub mod map {
219        mod private {
220            use std::collections::BTreeMap;
221
222            pub trait AsOptMap {
223                type Key;
224                type Value;
225
226                fn set_if_some(&mut self, new: Self);
227                fn as_mut_opt(&mut self) -> Option<&mut BTreeMap<Self::Key, Self::Value>>;
228                fn into_opt(self) -> Option<BTreeMap<Self::Key, Self::Value>>;
229            }
230
231            impl<K, V> AsOptMap for BTreeMap<K, V> {
232                type Key = K;
233                type Value = V;
234
235                fn set_if_some(&mut self, new: Self) {
236                    *self = new;
237                }
238
239                fn as_mut_opt(&mut self) -> Option<&mut Self> {
240                    Some(self)
241                }
242
243                fn into_opt(self) -> Option<Self> {
244                    Some(self)
245                }
246            }
247
248            impl<K, V> AsOptMap for Option<BTreeMap<K, V>> {
249                type Key = K;
250                type Value = V;
251
252                fn set_if_some(&mut self, new: Self) {
253                    if new.is_some() {
254                        *self = new;
255                    }
256                }
257
258                fn as_mut_opt(&mut self) -> Option<&mut BTreeMap<K, V>> {
259                    self.as_mut()
260                }
261
262                fn into_opt(self) -> Self {
263                    self
264                }
265            }
266        }
267        use private::AsOptMap;
268
269        /// The whole map is treated as one scalar value, and will be replaced with the new (non-[`None`]) value.
270        pub fn atomic<M>(current: &mut M, new: M) where M: AsOptMap {
271            current.set_if_some(new);
272        }
273
274        /// Each value will be merged separately.
275        pub fn granular<M>(current: &mut M, new: M, merge_value: fn(&mut M::Value, M::Value))
276        where
277            M: AsOptMap,
278            M::Key: Ord,
279        {
280            if let Some(current) = current.as_mut_opt() {
281                for (k, new_v) in new.into_opt().into_iter().flatten() {
282                    match current.entry(k) {
283                        std::collections::btree_map::Entry::Vacant(entry) => { entry.insert(new_v); }
284                        std::collections::btree_map::Entry::Occupied(entry) => merge_value(entry.into_mut(), new_v),
285                    }
286                }
287            }
288            else {
289                current.set_if_some(new);
290            }
291        }
292    }
293}