vector/config/
diff.rs

1use std::collections::HashSet;
2
3use indexmap::IndexMap;
4use vector_lib::config::OutputId;
5
6use super::{ComponentKey, Config, EnrichmentTableOuter};
7
8#[derive(Debug)]
9pub struct ConfigDiff {
10    pub sources: Difference,
11    pub transforms: Difference,
12    pub sinks: Difference,
13    /// This difference does not only contain the actual enrichment_tables keys, but also keys that
14    /// may be used for their source and sink components (if available).
15    pub enrichment_tables: Difference,
16    pub components_to_reload: HashSet<ComponentKey>,
17}
18
19impl ConfigDiff {
20    pub fn initial(initial: &Config) -> Self {
21        Self::new(&Config::default(), initial, HashSet::new())
22    }
23
24    pub fn new(old: &Config, new: &Config, components_to_reload: HashSet<ComponentKey>) -> Self {
25        ConfigDiff {
26            sources: Difference::new(&old.sources, &new.sources, &components_to_reload),
27            transforms: Difference::new(&old.transforms, &new.transforms, &components_to_reload),
28            sinks: Difference::new(&old.sinks, &new.sinks, &components_to_reload),
29            enrichment_tables: Difference::new_tables(
30                &old.enrichment_tables,
31                &new.enrichment_tables,
32            ),
33            components_to_reload,
34        }
35    }
36
37    /// Swaps removed with added in Differences.
38    pub const fn flip(mut self) -> Self {
39        self.sources.flip();
40        self.transforms.flip();
41        self.sinks.flip();
42        self.enrichment_tables.flip();
43        self
44    }
45
46    /// Checks whether the given component is present at all.
47    pub fn contains(&self, key: &ComponentKey) -> bool {
48        self.sources.contains(key)
49            || self.transforms.contains(key)
50            || self.sinks.contains(key)
51            || self.enrichment_tables.contains(key)
52    }
53
54    /// Checks whether the given component is changed.
55    pub fn is_changed(&self, key: &ComponentKey) -> bool {
56        self.sources.is_changed(key)
57            || self.transforms.is_changed(key)
58            || self.sinks.is_changed(key)
59            || self.enrichment_tables.contains(key)
60    }
61
62    /// Checks whether the given component is removed.
63    pub fn is_removed(&self, key: &ComponentKey) -> bool {
64        self.sources.is_removed(key)
65            || self.transforms.is_removed(key)
66            || self.sinks.is_removed(key)
67            || self.enrichment_tables.contains(key)
68    }
69}
70
71#[derive(Debug)]
72pub struct Difference {
73    pub to_remove: HashSet<ComponentKey>,
74    pub to_change: HashSet<ComponentKey>,
75    pub to_add: HashSet<ComponentKey>,
76}
77
78impl Difference {
79    fn new<C>(
80        old: &IndexMap<ComponentKey, C>,
81        new: &IndexMap<ComponentKey, C>,
82        need_change: &HashSet<ComponentKey>,
83    ) -> Self
84    where
85        C: serde::Serialize + serde::Deserialize<'static>,
86    {
87        let old_names = old.keys().cloned().collect::<HashSet<_>>();
88        let new_names = new.keys().cloned().collect::<HashSet<_>>();
89
90        let to_change = old_names
91            .intersection(&new_names)
92            .filter(|&n| {
93                // This is a hack around the issue of comparing two
94                // trait objects. Json is used here over toml since
95                // toml does not support serializing `None`
96                // to_value is used specifically (instead of string)
97                // to avoid problems comparing serialized HashMaps,
98                // which can iterate in varied orders.
99                let old_value = serde_json::to_value(&old[n]).unwrap();
100                let new_value = serde_json::to_value(&new[n]).unwrap();
101                old_value != new_value || need_change.contains(n)
102            })
103            .cloned()
104            .collect::<HashSet<_>>();
105
106        let to_remove = &old_names - &new_names;
107        let to_add = &new_names - &old_names;
108
109        Self {
110            to_remove,
111            to_change,
112            to_add,
113        }
114    }
115
116    fn new_tables(
117        old: &IndexMap<ComponentKey, EnrichmentTableOuter<OutputId>>,
118        new: &IndexMap<ComponentKey, EnrichmentTableOuter<OutputId>>,
119    ) -> Self {
120        let old_names = old
121            .iter()
122            .flat_map(|(k, t)| vec![t.as_source(k).map(|(k, _)| k), t.as_sink(k).map(|(k, _)| k)])
123            .flatten()
124            .collect::<HashSet<_>>();
125        let new_names = new
126            .iter()
127            .flat_map(|(k, t)| vec![t.as_source(k).map(|(k, _)| k), t.as_sink(k).map(|(k, _)| k)])
128            .flatten()
129            .collect::<HashSet<_>>();
130
131        let to_change = old_names
132            .intersection(&new_names)
133            .filter(|&n| {
134                // This is a hack around the issue of comparing two
135                // trait objects. Json is used here over toml since
136                // toml does not support serializing `None`
137                // to_value is used specifically (instead of string)
138                // to avoid problems comparing serialized HashMaps,
139                // which can iterate in varied orders.
140                let old_value = serde_json::to_value(&old[n]).unwrap();
141                let new_value = serde_json::to_value(&new[n]).unwrap();
142                old_value != new_value
143            })
144            .cloned()
145            .collect::<HashSet<_>>();
146
147        let to_remove = &old_names - &new_names;
148        let to_add = &new_names - &old_names;
149
150        Self {
151            to_remove,
152            to_change,
153            to_add,
154        }
155    }
156
157    /// Checks whether or not any components are being changed or added.
158    pub fn any_changed_or_added(&self) -> bool {
159        !(self.to_change.is_empty() && self.to_add.is_empty())
160    }
161
162    /// Checks whether or not any components are being changed or removed.
163    pub fn any_changed_or_removed(&self) -> bool {
164        !(self.to_change.is_empty() && self.to_remove.is_empty())
165    }
166
167    /// Checks whether the given component is present at all.
168    pub fn contains(&self, id: &ComponentKey) -> bool {
169        self.to_add.contains(id) || self.to_change.contains(id) || self.to_remove.contains(id)
170    }
171
172    /// Checks whether the given component is present as a change or addition.
173    pub fn contains_new(&self, id: &ComponentKey) -> bool {
174        self.to_add.contains(id) || self.to_change.contains(id)
175    }
176
177    /// Checks whether or not the given component is changed.
178    pub fn is_changed(&self, key: &ComponentKey) -> bool {
179        self.to_change.contains(key)
180    }
181
182    /// Checks whether the given component is present as an addition.
183    pub fn is_added(&self, id: &ComponentKey) -> bool {
184        self.to_add.contains(id)
185    }
186
187    /// Checks whether or not the given component is removed.
188    pub fn is_removed(&self, key: &ComponentKey) -> bool {
189        self.to_remove.contains(key)
190    }
191
192    const fn flip(&mut self) {
193        std::mem::swap(&mut self.to_remove, &mut self.to_add);
194    }
195
196    pub fn changed_and_added(&self) -> impl Iterator<Item = &ComponentKey> {
197        self.to_change.iter().chain(self.to_add.iter())
198    }
199
200    pub fn removed_and_changed(&self) -> impl Iterator<Item = &ComponentKey> {
201        self.to_change.iter().chain(self.to_remove.iter())
202    }
203}