saluki_components/config_registry/datadog/
mod.rs1pub mod aggregate;
4pub mod containerd;
5pub mod dogstatsd;
6pub mod dogstatsd_mapper;
7pub mod dogstatsd_prefix_filter;
8pub mod encoders;
9pub mod forwarder;
10pub mod get_typed;
11pub mod otlp;
12pub mod proxy;
13pub mod trace_obfuscation;
14pub(super) mod unsupported;
15
16use std::sync::LazyLock;
17
18use super::SalukiAnnotation;
19
20pub(crate) static SUPPORTED_ANNOTATIONS: LazyLock<Vec<&'static SalukiAnnotation>> = LazyLock::new(|| {
25 let mut v = Vec::new();
26 v.extend_from_slice(aggregate::ALL);
27 v.extend_from_slice(containerd::ALL);
28 v.extend_from_slice(dogstatsd::ALL);
29 v.extend_from_slice(dogstatsd_mapper::ALL);
30 v.extend_from_slice(forwarder::ALL);
31 v.extend_from_slice(get_typed::ALL);
32 v.extend_from_slice(dogstatsd_prefix_filter::ALL);
33 v.extend_from_slice(encoders::ALL);
34 v.extend_from_slice(otlp::ALL);
35 v.extend_from_slice(proxy::ALL);
36 v.extend_from_slice(trace_obfuscation::ALL);
37 v
38});
39
40pub(crate) static UNSUPPORTED_ANNOTATIONS: LazyLock<Vec<&'static SalukiAnnotation>> = LazyLock::new(|| {
44 let mut v = Vec::new();
45 v.extend_from_slice(unsupported::ALL);
46 v
47});
48
49pub(crate) static ALL_ANNOTATIONS: LazyLock<Vec<&'static SalukiAnnotation>> = LazyLock::new(|| {
53 let mut v = SUPPORTED_ANNOTATIONS.clone();
54 v.extend_from_slice(&UNSUPPORTED_ANNOTATIONS);
55 v
56});
57
58#[cfg(test)]
59mod registry_tests {
60 use std::collections::HashSet;
61
62 use super::*;
63 use crate::config_registry::{PipelineAffinity, Schema, SupportLevel, ALL_SCHEMA_ENTRIES, IGNORED_ENTRIES};
64
65 fn check_for_duplicates(it: impl Iterator<Item = impl AsRef<str>>) -> Result<(), String> {
66 let mut seen = std::collections::HashSet::new();
67 let mut duplicates = Vec::new();
68 for item in it {
69 let s = item.as_ref().to_owned();
70 if !seen.insert(s.clone()) {
71 duplicates.push(s);
72 }
73 }
74
75 if duplicates.is_empty() {
76 Ok(())
77 } else {
78 Err(format!("\n{}", duplicates.join("\n")))
79 }
80 }
81
82 #[test]
83 fn pipelines_affinity_slice_is_non_empty() {
84 for annotation in ALL_ANNOTATIONS.iter() {
85 if let PipelineAffinity::Pipelines(ps) = annotation.pipeline_affinity {
86 assert!(
87 !ps.is_empty(),
88 "annotation '{}' has PipelineAffinity::Pipelines(&[]) - \
89 At least one affected Pipeline is required. If all pipelines are affected, \
90 use Pipeline::CrossCutting.",
91 annotation.yaml_path(),
92 );
93 }
94 }
95 }
96
97 #[test]
98 fn annotation_invariants() {
99 for annotation in SUPPORTED_ANNOTATIONS.iter() {
100 let path = annotation.yaml_path();
101 match annotation.support_level {
102 SupportLevel::Full | SupportLevel::Partial => {
103 assert!(
104 !annotation.used_by.is_empty(),
105 "annotation '{}' has support level {:?} but used_by is empty — \
106 add the consuming struct name(s) to used_by, or change the support level. \
107 See config_registry/mod.rs module docs for details.",
108 path,
109 annotation.support_level,
110 );
111 }
112 SupportLevel::Incompatible(_) => {
113 assert!(
114 annotation.used_by.is_empty(),
115 "annotation '{}' has support level Incompatible but used_by is not empty — \
116 remove the struct name(s) from used_by, or change the support level. \
117 See config_registry/mod.rs module docs for details.",
118 path,
119 );
120 }
121 SupportLevel::Ignored | SupportLevel::Unrecognized => {
122 panic!(
123 "annotation '{}' has support level {:?} — \
124 Ignored and Unrecognized are reserved for unannotated keys and must not \
125 appear in a handwritten SalukiAnnotation. \
126 See config_registry/mod.rs module docs for details.",
127 path, annotation.support_level
128 );
129 }
130 }
131 }
132 }
133
134 #[test]
137 fn no_overlap_between_supported_and_unsupported() {
138 let supported_paths: HashSet<&str> = SUPPORTED_ANNOTATIONS.iter().map(|a| a.yaml_path()).collect();
139
140 let duplicates: Vec<&str> = UNSUPPORTED_ANNOTATIONS
141 .iter()
142 .map(|a| a.yaml_path())
143 .filter(|p| supported_paths.contains(p))
144 .collect();
145
146 if !duplicates.is_empty() {
147 panic!(
148 "{} key(s) appear in both SUPPORTED_ANNOTATIONS and UNSUPPORTED_ANNOTATIONS:\n{}\n\
149 See config_registry/mod.rs module docs for details.",
150 duplicates.len(),
151 duplicates
152 .iter()
153 .map(|p| format!(" - {}", p))
154 .collect::<Vec<_>>()
155 .join("\n"),
156 );
157 }
158 }
159
160 #[test]
161 fn no_duplicates_in_supported_annotations() {
162 if let Err(dupes) = check_for_duplicates(SUPPORTED_ANNOTATIONS.iter().map(|&a| a.yaml_path())) {
163 panic!("Duplicates in SUPPORTED_ANNOTATIONS:{dupes}\nSee config_registry/mod.rs module docs for details.");
164 }
165 }
166
167 #[test]
168 fn no_duplicates_in_unsupported_annotations() {
169 if let Err(dupes) = check_for_duplicates(UNSUPPORTED_ANNOTATIONS.iter().map(|&a| a.yaml_path())) {
170 panic!(
171 "Duplicates in UNSUPPORTED_ANNOTATIONS:{dupes}\nSee config_registry/mod.rs module docs for details."
172 );
173 }
174 }
175
176 #[test]
177 fn no_duplicates_in_ignored_keys() {
178 if let Err(dupes) = check_for_duplicates(IGNORED_ENTRIES.iter()) {
179 panic!("Duplicates in IGNORED_ENTRIES:{dupes}\nSee config_registry/mod.rs module docs for details.");
180 }
181 }
182
183 #[test]
184 fn no_overlap_between_annotations_and_ignored() {
185 let annotation_paths: HashSet<&str> = ALL_ANNOTATIONS.iter().map(|a| a.yaml_path()).collect();
186 let overlaps: Vec<&&str> = IGNORED_ENTRIES
187 .iter()
188 .filter(|k| annotation_paths.contains(**k))
189 .collect();
190 if !overlaps.is_empty() {
191 panic!(
192 "{} key(s) appear in both ALL_ANNOTATIONS and IGNORED_ENTRIES:\n{}\n\
193 See config_registry/mod.rs module docs for details.",
194 overlaps.len(),
195 overlaps
196 .iter()
197 .map(|p| format!(" - {}", p))
198 .collect::<Vec<_>>()
199 .join("\n"),
200 );
201 }
202 }
203
204 #[test]
205 fn no_stale_entries() {
206 let schema_paths: HashSet<&str> = ALL_SCHEMA_ENTRIES.iter().map(|e| e.yaml_path).collect();
207
208 let stale_annotations: Vec<&str> = ALL_ANNOTATIONS
209 .iter()
210 .filter(|a| a.schema.schema == Schema::Datadog)
211 .map(|a| a.yaml_path())
212 .filter(|p| !schema_paths.contains(p))
213 .collect();
214
215 let stale_ignored: Vec<&&str> = IGNORED_ENTRIES.iter().filter(|k| !schema_paths.contains(**k)).collect();
216
217 if stale_annotations.is_empty() && stale_ignored.is_empty() {
218 return;
219 }
220
221 let mut msg = String::new();
222 if !stale_annotations.is_empty() {
223 msg.push_str(&format!(
224 "{} stale annotation(s) not in schema:\n{}\n",
225 stale_annotations.len(),
226 stale_annotations
227 .iter()
228 .map(|p| format!(" - {}", p))
229 .collect::<Vec<_>>()
230 .join("\n"),
231 ));
232 }
233 if !stale_ignored.is_empty() {
234 msg.push_str(&format!(
235 "{} stale ignored key(s) not in schema:\n{}",
236 stale_ignored.len(),
237 stale_ignored
238 .iter()
239 .map(|p| format!(" - {}", p))
240 .collect::<Vec<_>>()
241 .join("\n"),
242 ));
243 }
244 panic!("{msg}\nSee config_registry/mod.rs module docs for details.");
245 }
246
247 #[test]
248 fn saluki_schema_entries_not_in_vendored_schema() {
249 let schema_paths: HashSet<&str> = ALL_SCHEMA_ENTRIES.iter().map(|e| e.yaml_path).collect();
250
251 let misclassified: Vec<&str> = ALL_ANNOTATIONS
252 .iter()
253 .filter(|a| a.schema.schema == Schema::Saluki)
254 .map(|a| a.yaml_path())
255 .filter(|p| schema_paths.contains(p))
256 .collect();
257
258 if !misclassified.is_empty() {
259 panic!(
260 "{} annotation(s) marked Schema::Saluki but found in ALL_SCHEMA_ENTRIES \
261 (should reference the generated schema:: constant instead):\n{}\n\
262 See config_registry/mod.rs module docs for details.",
263 misclassified.len(),
264 misclassified
265 .iter()
266 .map(|p| format!(" - {}", p))
267 .collect::<Vec<_>>()
268 .join("\n"),
269 );
270 }
271 }
272
273 #[test]
274 fn all_schema_entries_are_annotated_or_ignored() {
275 let all_accounted_for_entries: HashSet<&str> = HashSet::from_iter(
277 ALL_ANNOTATIONS
278 .iter()
279 .map(|&annotation| annotation.yaml_path())
280 .chain(IGNORED_ENTRIES.iter().copied()),
281 );
282
283 let mut missing_keys = Vec::new();
284 for schema_key in ALL_SCHEMA_ENTRIES.iter().map(|&entry| entry.yaml_path) {
285 if !all_accounted_for_entries.contains(schema_key) {
286 missing_keys.push(schema_key);
287 }
288 }
289
290 if !missing_keys.is_empty() {
291 panic!(
292 "{} config key(s) are missing from the Saluki registry: \n\n{}\n\n\
293 See config_registry/mod.rs module docs for details.",
294 missing_keys.len(),
295 missing_keys.join("\n")
296 );
297 }
298 }
299}