vector_config/schema/visitors/
merge.rs

1#![allow(clippy::borrowed_box)]
2
3use std::mem::discriminant;
4
5use serde_json::Value;
6use vector_config_common::schema::*;
7
8/// A type that can be merged with itself.
9pub trait Mergeable {
10    fn merge(&mut self, other: &Self);
11}
12
13impl Mergeable for SchemaObject {
14    fn merge(&mut self, other: &Self) {
15        // The logic is pretty straightforward: we merge `other` into `self`, with `self` having the
16        // higher precedence.
17        //
18        // Additionally, we only merge logical schema chunks: if the destination schema has object
19        // properties defined, and the source schema has some object properties that don't exist in
20        // the destination, they will be merged, but if there is any overlap, then the object
21        // properties in the destination would remain untouched. This merging logic applies to all
22        // map-based types.
23        //
24        // For standalone fields, such as title or description, the destination always has higher
25        // precedence. For optional fields, whichever version (destination or source) is present
26        // will win, except for when both are present, then the individual fields within the
27        // optional type will be merged according to the normal precedence rules.
28        merge_optional(&mut self.reference, other.reference.as_ref());
29        merge_schema_metadata(&mut self.metadata, other.metadata.as_ref());
30        merge_schema_instance_type(&mut self.instance_type, other.instance_type.as_ref());
31        merge_schema_format(&mut self.format, other.format.as_ref());
32        merge_schema_enum_values(&mut self.enum_values, other.enum_values.as_ref());
33        merge_schema_const_value(&mut self.const_value, other.const_value.as_ref());
34        merge_schema_subschemas(&mut self.subschemas, other.subschemas.as_ref());
35        merge_schema_number_validation(&mut self.number, other.number.as_ref());
36        merge_schema_string_validation(&mut self.string, other.string.as_ref());
37        merge_schema_array_validation(&mut self.array, other.array.as_ref());
38        merge_schema_object_validation(&mut self.object, other.object.as_ref());
39        merge_schema_extensions(&mut self.extensions, &other.extensions);
40    }
41}
42
43impl Mergeable for Value {
44    fn merge(&mut self, other: &Self) {
45        // We do a check here for ensuring both value discriminants are the same type. This is
46        // specific to `Value` but we should never really be merging identical keys together that
47        // have differing value types, as that is indicative of a weird overlap in keys between
48        // different schemas.
49        //
50        // We _may_ need to relax this in practice/in the future, but it's a solid invariant to
51        // enforce for the time being.
52        if discriminant(self) != discriminant(other) {
53            panic!(
54                "Tried to merge two `Value` types together with differing types!\n\nSelf: {self:?}\n\nOther: {other:?}"
55            );
56        }
57
58        match (self, other) {
59            // Maps get merged recursively.
60            (Value::Object(self_map), Value::Object(other_map)) => {
61                self_map.merge(other_map);
62            }
63            // Arrays get merged together indiscriminately.
64            (Value::Array(self_array), Value::Array(other_array)) => {
65                self_array.extend(other_array.iter().cloned());
66            }
67            // We don't merge any other value types together.
68            _ => {}
69        }
70    }
71}
72
73impl Mergeable for Schema {
74    fn merge(&mut self, other: &Self) {
75        match (self, other) {
76            // We don't merge schemas together if either of them is a boolean schema.
77            (Schema::Bool(_), _) | (_, Schema::Bool(_)) => {}
78            (Schema::Object(self_schema), Schema::Object(other_schema)) => {
79                self_schema.merge(other_schema);
80            }
81        }
82    }
83}
84
85impl Mergeable for serde_json::Map<String, Value> {
86    fn merge(&mut self, other: &Self) {
87        for (key, value) in other {
88            match self.get_mut(key) {
89                None => {
90                    self.insert(key.clone(), value.clone());
91                }
92                Some(existing) => existing.merge(value),
93            }
94        }
95    }
96}
97
98impl<K, V> Mergeable for Map<K, V>
99where
100    K: Clone + Eq + Ord,
101    V: Clone + Mergeable,
102{
103    fn merge(&mut self, other: &Self) {
104        for (key, value) in other {
105            match self.get_mut(key) {
106                None => {
107                    self.insert(key.clone(), value.clone());
108                }
109                Some(existing) => existing.merge(value),
110            }
111        }
112    }
113}
114
115fn merge_schema_metadata(destination: &mut Option<Box<Metadata>>, source: Option<&Box<Metadata>>) {
116    merge_optional_with(destination, source, |existing, new| {
117        merge_optional(&mut existing.id, new.id.as_ref());
118        merge_optional(&mut existing.title, new.title.as_ref());
119        merge_optional(&mut existing.description, new.description.as_ref());
120        merge_optional(&mut existing.default, new.default.as_ref());
121        merge_bool(&mut existing.deprecated, new.deprecated);
122        merge_bool(&mut existing.read_only, new.read_only);
123        merge_bool(&mut existing.write_only, new.write_only);
124        merge_collection(&mut existing.examples, &new.examples);
125    });
126}
127
128fn merge_schema_instance_type(
129    destination: &mut Option<SingleOrVec<InstanceType>>,
130    source: Option<&SingleOrVec<InstanceType>>,
131) {
132    merge_optional_with(destination, source, |existing, new| {
133        let mut deduped = existing.into_iter().chain(new).cloned().collect::<Vec<_>>();
134        deduped.dedup();
135
136        *existing = deduped.into();
137    });
138}
139
140fn merge_schema_format(destination: &mut Option<String>, source: Option<&String>) {
141    merge_optional(destination, source);
142}
143
144fn merge_schema_enum_values(destination: &mut Option<Vec<Value>>, source: Option<&Vec<Value>>) {
145    merge_optional_with(destination, source, merge_collection);
146}
147
148fn merge_schema_const_value(destination: &mut Option<Value>, source: Option<&Value>) {
149    merge_optional(destination, source);
150}
151
152fn merge_schema_subschemas(
153    destination: &mut Option<Box<SubschemaValidation>>,
154    source: Option<&Box<SubschemaValidation>>,
155) {
156    merge_optional_with(destination, source, |existing, new| {
157        merge_optional_with(&mut existing.all_of, new.all_of.as_ref(), merge_collection);
158        merge_optional_with(&mut existing.any_of, new.any_of.as_ref(), merge_collection);
159        merge_optional_with(&mut existing.one_of, new.one_of.as_ref(), merge_collection);
160        merge_optional(&mut existing.if_schema, new.if_schema.as_ref());
161        merge_optional(&mut existing.then_schema, new.then_schema.as_ref());
162        merge_optional(&mut existing.else_schema, new.else_schema.as_ref());
163        merge_optional(&mut existing.not, new.not.as_ref());
164    });
165}
166
167fn merge_schema_number_validation(
168    destination: &mut Option<Box<NumberValidation>>,
169    source: Option<&Box<NumberValidation>>,
170) {
171    merge_optional_with(destination, source, |existing, new| {
172        merge_optional(&mut existing.multiple_of, new.multiple_of.as_ref());
173        merge_optional(&mut existing.maximum, new.maximum.as_ref());
174        merge_optional(
175            &mut existing.exclusive_maximum,
176            new.exclusive_minimum.as_ref(),
177        );
178        merge_optional(&mut existing.minimum, new.minimum.as_ref());
179        merge_optional(
180            &mut existing.exclusive_minimum,
181            new.exclusive_minimum.as_ref(),
182        );
183    });
184}
185
186fn merge_schema_string_validation(
187    destination: &mut Option<Box<StringValidation>>,
188    source: Option<&Box<StringValidation>>,
189) {
190    merge_optional_with(destination, source, |existing, new| {
191        merge_optional(&mut existing.max_length, new.max_length.as_ref());
192        merge_optional(&mut existing.min_length, new.min_length.as_ref());
193        merge_optional(&mut existing.pattern, new.pattern.as_ref());
194    });
195}
196
197fn merge_schema_array_validation(
198    destination: &mut Option<Box<ArrayValidation>>,
199    source: Option<&Box<ArrayValidation>>,
200) {
201    merge_optional_with(destination, source, |existing, new| {
202        merge_optional_with(&mut existing.items, new.items.as_ref(), merge_collection);
203        merge_optional(
204            &mut existing.additional_items,
205            new.additional_items.as_ref(),
206        );
207        merge_optional(
208            &mut existing.unevaluated_items,
209            new.unevaluated_items.as_ref(),
210        );
211        merge_optional(&mut existing.max_items, new.max_items.as_ref());
212        merge_optional(&mut existing.min_items, new.min_items.as_ref());
213        merge_optional(&mut existing.unique_items, new.unique_items.as_ref());
214        merge_optional(&mut existing.contains, new.contains.as_ref());
215    });
216}
217
218fn merge_schema_object_validation(
219    destination: &mut Option<Box<ObjectValidation>>,
220    source: Option<&Box<ObjectValidation>>,
221) {
222    merge_optional_with(destination, source, |existing, new| {
223        merge_optional(&mut existing.max_properties, new.max_properties.as_ref());
224        merge_optional(&mut existing.min_properties, new.min_properties.as_ref());
225        merge_collection(&mut existing.required, &new.required);
226        merge_map(&mut existing.properties, &new.properties);
227        merge_map(&mut existing.pattern_properties, &new.pattern_properties);
228        merge_optional(
229            &mut existing.additional_properties,
230            new.additional_properties.as_ref(),
231        );
232        merge_optional(
233            &mut existing.unevaluated_properties,
234            new.unevaluated_properties.as_ref(),
235        );
236        merge_optional(&mut existing.property_names, new.property_names.as_ref());
237    });
238}
239
240fn merge_schema_extensions(destination: &mut Map<String, Value>, source: &Map<String, Value>) {
241    merge_map(destination, source);
242}
243
244fn merge_bool(destination: &mut bool, source: bool) {
245    // We only treat `true` as a merge-worthy value.
246    if source {
247        *destination = true;
248    }
249}
250
251fn merge_collection<'a, E, I, T>(destination: &mut E, source: I)
252where
253    E: Extend<T>,
254    I: IntoIterator<Item = &'a T>,
255    T: Clone + 'a,
256{
257    destination.extend(source.into_iter().cloned());
258}
259
260fn merge_map<K, V>(destination: &mut Map<K, V>, source: &Map<K, V>)
261where
262    K: Clone + Eq + Ord,
263    V: Clone + Mergeable,
264{
265    destination.merge(source);
266}
267
268fn merge_optional<T: Clone>(destination: &mut Option<T>, source: Option<&T>) {
269    merge_optional_with(destination, source, |_, _| {});
270}
271
272fn merge_optional_with<'a, T, F>(destination: &'a mut Option<T>, source: Option<&'a T>, f: F)
273where
274    T: Clone,
275    F: Fn(&'a mut T, &'a T),
276{
277    match destination {
278        // If the destination is empty, we use whatever we have in `source`. Otherwise, we leave
279        // `destination` as-is.
280        None => *destination = source.cloned(),
281        // If `destination` isn't empty, and neither is `source`, then pass them both to `f` to
282        // let it handle the actual merge logic.
283        Some(destination) => {
284            if let Some(source) = source {
285                f(destination, source);
286            }
287        }
288    }
289}