1use super::Field;
4use std::ops::BitOr;
5
6use super::{Collection, Kind};
7
8impl Kind {
9 pub fn merge(&mut self, other: Self, strategy: Strategy) {
11 self.merge_keep(other, strategy.collisions.is_shallow());
12 }
13
14 fn merge_primitives(&mut self, other: &Self) {
15 self.bytes = self.bytes.or(other.bytes);
16 self.integer = self.integer.or(other.integer);
17 self.float = self.float.or(other.float);
18 self.boolean = self.boolean.or(other.boolean);
19 self.timestamp = self.timestamp.or(other.timestamp);
20 self.regex = self.regex.or(other.regex);
21 self.null = self.null.or(other.null);
22 self.undefined = self.undefined.or(other.undefined);
23 }
24
25 fn merge_objects(&mut self, other: Option<Collection<Field>>, overwrite: bool) {
26 match (self.object.as_mut(), other) {
27 (None, rhs @ Some(_)) => self.object = rhs,
28 (Some(lhs), Some(rhs)) => lhs.merge(rhs, overwrite),
29 _ => {}
30 }
31 }
32
33 #[must_use]
35 pub fn union(&self, other: Self) -> Self {
36 let mut kind = self.clone();
37 kind.merge_keep(other, false);
38 kind
39 }
40
41 pub fn merge_keep(&mut self, other: Self, overwrite: bool) {
44 self.merge_primitives(&other);
45 self.merge_objects(other.object, overwrite);
46
47 match (self.array.as_mut(), other.array) {
48 (None, Some(rhs)) => self.array = Some(rhs),
49 (Some(lhs), Some(rhs)) => lhs.merge(rhs, overwrite),
50 _ => {}
51 }
52 }
53}
54
55#[derive(Debug, Clone, Copy, Eq, PartialEq)]
57pub struct Strategy {
58 pub collisions: CollisionStrategy,
61}
62
63#[derive(Debug, Clone, Copy, Eq, PartialEq)]
65pub enum CollisionStrategy {
66 Overwrite,
69
70 Union,
72}
73
74impl CollisionStrategy {
75 #[must_use]
77 pub const fn is_shallow(self) -> bool {
78 matches!(self, Self::Overwrite)
79 }
80
81 #[must_use]
83 pub const fn is_deep(self) -> bool {
84 matches!(self, Self::Union)
85 }
86}
87
88impl BitOr for Kind {
89 type Output = Self;
90
91 fn bitor(self, rhs: Self) -> Self::Output {
92 self.union(rhs)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use std::collections::{BTreeMap, HashMap};
100
101 #[test]
102 #[allow(clippy::too_many_lines)]
103 fn test_merge() {
104 struct TestCase {
105 this: Kind,
106 other: Kind,
107 strategy: Strategy,
108 merged: Kind,
109 }
110
111 for (
112 title,
113 TestCase {
114 mut this,
115 other,
116 strategy,
117 merged,
118 },
119 ) in HashMap::from([
120 (
121 "object field with unknown",
122 TestCase {
123 this: Kind::object(Collection::any()),
124 other: Kind::object(BTreeMap::from([("x".into(), Kind::integer())])),
125 strategy: Strategy {
126 collisions: CollisionStrategy::Union,
127 },
128 merged: {
129 let mut collection =
130 Collection::from(BTreeMap::from([("x".into(), Kind::any())]));
131 collection.set_unknown(Kind::any());
132 Kind::object(collection)
133 },
134 },
135 ),
136 (
137 "primitives shallow",
138 TestCase {
139 this: Kind::bytes(),
140 other: Kind::integer(),
141 strategy: Strategy {
142 collisions: CollisionStrategy::Overwrite,
143 },
144 merged: Kind::bytes().or_integer(),
145 },
146 ),
147 (
148 "primitives deep",
149 TestCase {
150 this: Kind::bytes(),
151 other: Kind::integer(),
152 strategy: Strategy {
153 collisions: CollisionStrategy::Union,
154 },
155 merged: Kind::bytes().or_integer(),
156 },
157 ),
158 (
159 "mixed unknown shallow",
160 TestCase {
161 this: Kind::bytes().or_object(Collection::from_unknown(Kind::integer())),
162 other: Kind::bytes().or_object(Collection::from_unknown(Kind::bytes())),
163 strategy: Strategy {
164 collisions: CollisionStrategy::Overwrite,
165 },
166 merged: Kind::bytes()
167 .or_object(Collection::from_unknown(Kind::integer().or_bytes())),
168 },
169 ),
170 (
171 "mixed unknown deep",
172 TestCase {
173 this: Kind::bytes().or_object(Collection::from_unknown(Kind::integer())),
174 other: Kind::bytes().or_object(Collection::from_unknown(Kind::bytes())),
175 strategy: Strategy {
176 collisions: CollisionStrategy::Union,
177 },
178 merged: Kind::bytes()
179 .or_object(Collection::from_unknown(Kind::integer().or_bytes())),
180 },
181 ),
182 (
183 "mixed known shallow",
184 TestCase {
185 this: Kind::bytes().or_object(BTreeMap::from([
186 (
187 "foo".into(),
188 Kind::object(BTreeMap::from([
189 ("qux".into(), Kind::bytes()),
190 ("quux".into(), Kind::boolean()),
191 ("this".into(), Kind::timestamp()),
192 ])),
193 ),
194 ("bar".into(), Kind::integer()),
195 ])),
196 other: Kind::integer().or_object(BTreeMap::from([
197 (
198 "foo".into(),
199 Kind::object(BTreeMap::from([
200 ("qux".into(), Kind::integer()),
201 ("quux".into(), Kind::regex()),
202 ("that".into(), Kind::null()),
203 ])),
204 ),
205 ("baz".into(), Kind::boolean()),
206 ])),
207 strategy: Strategy {
208 collisions: CollisionStrategy::Overwrite,
209 },
210 merged: Kind::bytes().or_integer().or_object(BTreeMap::from([
211 (
212 "foo".into(),
213 Kind::object(BTreeMap::from([
214 ("qux".into(), Kind::integer()),
215 ("quux".into(), Kind::regex()),
216 ("that".into(), Kind::null()),
217 ])),
218 ),
219 ("bar".into(), Kind::integer()),
220 ("baz".into(), Kind::boolean()),
221 ])),
222 },
223 ),
224 (
225 "mixed known deep",
226 TestCase {
227 this: Kind::bytes().or_object(BTreeMap::from([
228 (
229 "foo".into(),
230 Kind::object(BTreeMap::from([
231 ("qux".into(), Kind::bytes()),
232 ("quux".into(), Kind::boolean()),
233 ("this".into(), Kind::timestamp()),
234 ])),
235 ),
236 ("bar".into(), Kind::integer()),
237 ])),
238 other: Kind::integer().or_object(BTreeMap::from([
239 (
240 "foo".into(),
241 Kind::object(BTreeMap::from([
242 ("qux".into(), Kind::integer()),
243 ("quux".into(), Kind::regex()),
244 ("that".into(), Kind::null()),
245 ])),
246 ),
247 ("baz".into(), Kind::boolean()),
248 ])),
249 strategy: Strategy {
250 collisions: CollisionStrategy::Union,
251 },
252 merged: Kind::bytes().or_integer().or_object(BTreeMap::from([
253 (
254 "foo".into(),
255 Kind::object(BTreeMap::from([
256 ("qux".into(), Kind::bytes().or_integer()),
257 ("quux".into(), Kind::boolean().or_regex()),
258 ("this".into(), Kind::timestamp().or_undefined()),
259 ("that".into(), Kind::null().or_undefined()),
260 ])),
261 ),
262 ("bar".into(), Kind::integer().or_undefined()),
263 ("baz".into(), Kind::boolean().or_undefined()),
264 ])),
265 },
266 ),
267 ]) {
268 this.merge(other, strategy);
269 assert_eq!(this, merged, "{title}");
270 }
271 }
272}