saluki_components/config_registry/mod.rs
1//! Configuration key registry.
2//!
3//! A programmatic registry of all recognized configuration keys. Each entry describes the key
4//! purely from the configuration system's perspective: its canonical YAML path, the environment
5//! variables that map to it, the shape of its value, and which internal config structs consume it.
6//!
7//! This registry is intentionally free of Rust field names and struct internals — it models the
8//! configuration surface as an operator would see it, and can be used at runtime to detect
9//! unknown or unsupported keys in a loaded configuration file.
10
11pub mod datadog;
12/// Generated schema entries from the vendored Datadog Agent config schema.
13pub mod generated;
14
15pub use self::datadog::{ALL_ANNOTATIONS, ALL_KEYS};
16
17/// Declares a set of [`SalukiAnnotation`] constants and generates a companion `ALL` slice.
18///
19/// Each entry declares one named `pub const` annotation. The macro also emits:
20///
21/// ```ignore
22/// pub const ALL: &[&SalukiAnnotation] = &[&NAME1, &NAME2, ...];
23/// ```
24///
25/// so that `datadog/mod.rs` can aggregate submodules with a single
26/// `v.extend_from_slice(my_module::ALL)` line rather than listing every constant by name.
27///
28/// # Example
29///
30/// ```ignore
31/// declare_annotations! {
32/// /// Doc comment for PROXY_HTTP.
33/// PROXY_HTTP = SalukiAnnotation { ... };
34/// PROXY_HTTPS = SalukiAnnotation { ... };
35/// }
36/// ```
37#[macro_export]
38macro_rules! declare_annotations {
39 ( $( $(#[$attr:meta])* $name:ident = $val:expr ;)+ ) => {
40 $(
41 $(#[$attr])*
42 pub const $name: $crate::config_registry::SalukiAnnotation = $val;
43 )+
44
45 /// All annotations declared in this module, in declaration order.
46 pub const ALL: &[$crate::config_registry::SalukiAnnotationRef] = &[
47 $( &$name, )+
48 ];
49 };
50}
51
52/// Shared helpers for config smoke tests.
53#[cfg(test)]
54pub mod test_support;
55
56/// Identifiers for known configuration structs.
57///
58/// Used as values in [`SalukiAnnotation::used_by`] to declare which structs consume a given key.
59/// Adding a new struct here is the first step when registering its configuration keys.
60pub mod structs {
61 /// Identifier for `ProxyConfiguration`.
62 pub const PROXY_CONFIGURATION: &str = "ProxyConfiguration";
63 /// Identifier for `ForwarderConfiguration`.
64 pub const FORWARDER_CONFIGURATION: &str = "ForwarderConfiguration";
65 /// Identifier for `DogStatsDConfiguration`.
66 pub const DOGSTATSD_CONFIGURATION: &str = "DogStatsDConfiguration";
67 /// Identifier for `OtlpConfiguration`.
68 pub const OTLP_CONFIGURATION: &str = "OtlpConfiguration";
69 /// Identifier for `AggregateConfiguration`.
70 pub const AGGREGATE_CONFIGURATION: &str = "AggregateConfiguration";
71 /// Identifier for `DogStatsDMapperConfiguration`.
72 pub const DOGSTATSD_MAPPER_CONFIGURATION: &str = "DogStatsDMapperConfiguration";
73 /// Identifier for `DogStatsDDebugLogConfiguration`.
74 pub const DOGSTATSD_DEBUG_LOG_CONFIGURATION: &str = "DogStatsDDebugLogConfiguration";
75 /// Identifier for `DogStatsDPrefixFilterConfiguration`.
76 pub const DOGSTATSD_PREFIX_FILTER_CONFIGURATION: &str = "DogStatsDPrefixFilterConfiguration";
77 /// Identifier for `DatadogMetricsConfiguration`.
78 pub const DATADOG_METRICS_CONFIGURATION: &str = "DatadogMetricsConfiguration";
79 /// Identifier for `DatadogTraceConfiguration`.
80 pub const DATADOG_TRACE_CONFIGURATION: &str = "DatadogTraceConfiguration";
81 /// Identifier for `DatadogLogsConfiguration`.
82 pub const DATADOG_LOGS_CONFIGURATION: &str = "DatadogLogsConfiguration";
83 /// Identifier for `DatadogEventsConfiguration`.
84 pub const DATADOG_EVENTS_CONFIGURATION: &str = "DatadogEventsConfiguration";
85 /// Identifier for `DatadogServiceChecksConfiguration`.
86 pub const DATADOG_SERVICE_CHECKS_CONFIGURATION: &str = "DatadogServiceChecksConfiguration";
87 /// Identifier for `DatadogApmStatsEncoderConfiguration`.
88 pub const DATADOG_APM_STATS_ENCODER_CONFIGURATION: &str = "DatadogApmStatsEncoderConfiguration";
89 /// Identifier for `OtlpDecoderConfiguration`.
90 pub const OTLP_DECODER_CONFIGURATION: &str = "OtlpDecoderConfiguration";
91 /// Identifier for `OtlpRelayConfiguration`.
92 pub const OTLP_RELAY_CONFIGURATION: &str = "OtlpRelayConfiguration";
93 /// Identifier for `TraceObfuscationConfiguration`.
94 pub const TRACE_OBFUSCATION_CONFIGURATION: &str = "TraceObfuscationConfiguration";
95}
96
97/// How well saluki supports a given configuration key.
98///
99/// Used in [`SalukiAnnotation`] to classify each key from saluki's perspective. `Ignored` is
100/// reserved for keys in the schema that have no annotation at all — it must not appear in any
101/// hand-written annotation.
102///
103/// Invariants enforced at test time:
104/// - `Full` and `Partial` require a non-empty `used_by` list.
105/// - `Incompatible` requires an empty `used_by` list.
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub enum SupportLevel {
108 /// Fully supported. The key is wired up end-to-end and must be consumed by at least one struct.
109 Full,
110 /// Partially supported. The key is consumed by at least one struct, but support is incomplete.
111 Partial,
112 /// Explicitly incompatible. Saluki intentionally does not support this key; `used_by` must be
113 /// empty.
114 Incompatible,
115 /// Not annotated. Applied implicitly at runtime to any schema key that has no
116 /// [`SalukiAnnotation`]. Must not be set on a hand-written annotation.
117 Ignored,
118}
119
120/// The shape of a configuration value.
121///
122/// Describes how a value should be parsed from both YAML and environment variables.
123/// `StringList` values are represented as a YAML sequence or a space-separated string in env vars.
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub enum ValueType {
126 /// A boolean (`true` / `false`).
127 Bool,
128 /// A UTF-8 string.
129 String,
130 /// An unsigned integer.
131 Integer,
132 /// A floating-point number.
133 Float,
134 /// A list of strings (YAML sequence or space-separated env var string).
135 StringList,
136}
137
138/// Schema-derived metadata for a single configuration key.
139///
140/// Generated from the vendored Datadog Agent config schema. Contains only what the schema
141/// knows: the canonical YAML path, declared environment variables, and value type. Saluki-specific
142/// fields (`used_by`, etc.) live in [`SalukiAnnotation`] instead.
143///
144/// Do not construct these manually — they are produced by `cargo xtask gen-config-schema` and
145/// live in `config_registry::generated::schema`.
146#[derive(Debug)]
147pub struct SchemaEntry {
148 /// Canonical dot-separated YAML path for this key (e.g. `"proxy.http"`).
149 pub yaml_path: &'static str,
150
151 /// Environment variables that deliver this value, as declared in the schema.
152 ///
153 /// Empty when the schema marks the key `no-env` or lists no env vars. Annotations may
154 /// override this with additional or corrected env vars.
155 pub env_vars: &'static [&'static str],
156
157 /// Shape of the value.
158 pub value_type: ValueType,
159
160 /// JSON-encoded default value from the Agent schema, if present.
161 ///
162 /// Examples: `Some("true")`, `Some("8125")`, `Some("\"datadoghq.com\"")`, `None`.
163 /// Used by smoke tests to auto-detect when a generic test value matches the default
164 /// and flip it so the test actually exercises the field.
165 pub default: Option<&'static str>,
166}
167
168/// Saluki-specific annotation for a single configuration key.
169///
170/// Pairs a [`SchemaEntry`] (generated from the vendored schema) with the metadata that only
171/// saluki knows: which internal config structs consume the key, and any corrections to the
172/// schema's env var list.
173///
174/// These are hand-written constants, one per key saluki cares about, and live in
175/// `config_registry::datadog::*` submodules. They are never overwritten by codegen.
176#[derive(Debug)]
177pub struct SalukiAnnotation {
178 /// The schema entry this annotation enriches.
179 pub schema: &'static SchemaEntry,
180
181 /// How well saluki supports this key.
182 ///
183 /// Must not be [`SupportLevel::Ignored`] — that level is reserved for unannotated schema keys.
184 pub support_level: SupportLevel,
185
186 /// Additional YAML paths beyond the canonical one in the schema (aliases).
187 ///
188 /// Most keys have no aliases; leave this as `&[]` unless the config system recognises
189 /// the key under more than one dot-separated path.
190 pub additional_yaml_paths: &'static [&'static str],
191
192 /// Overrides the schema's `env_vars` list entirely when `Some`.
193 ///
194 /// Use when the schema marks a key `no-env` but env vars are actually supported, or when
195 /// the schema's list is incorrect or incomplete (e.g. the proxy sub-keys).
196 pub env_var_override: Option<&'static [&'static str]>,
197
198 /// Config structs that incorporate this key, as [`structs`] constants.
199 ///
200 /// Must be non-empty for [`SupportLevel::Full`] and [`SupportLevel::Partial`].
201 /// Must be empty for [`SupportLevel::Incompatible`].
202 pub used_by: &'static [&'static str],
203
204 /// Overrides the schema's `value_type` when `Some`.
205 ///
206 /// Use when the schema declares a key as `Float` but the consuming Rust field is `i32` (or
207 /// similarly mismatched), so that smoke tests inject a value the struct can actually parse.
208 pub value_type_override: Option<ValueType>,
209
210 /// Overrides the smoke-test injected value entirely when `Some`.
211 ///
212 /// Must be a valid JSON literal (e.g. `Some("[{\"name\":\"test\"}]")`). Use when the field
213 /// requires a structured value that the generic `ValueType`-derived test values cannot satisfy
214 /// (e.g. JSON-encoded arrays or objects).
215 pub test_json: Option<&'static str>,
216}
217
218/// A reference to a [`SalukiAnnotation`], used as the element type of `ALL` slices generated by
219/// [`declare_annotations!`].
220pub type SalukiAnnotationRef = &'static SalukiAnnotation;
221
222impl SalukiAnnotation {
223 /// The canonical YAML path for this key (from the schema).
224 pub fn yaml_path(&self) -> &'static str {
225 self.schema.yaml_path
226 }
227
228 /// All YAML paths for this key: canonical first, then any aliases.
229 pub fn all_yaml_paths(&self) -> impl Iterator<Item = &'static str> {
230 std::iter::once(self.schema.yaml_path).chain(self.additional_yaml_paths.iter().copied())
231 }
232
233 /// Effective env vars: the override list if set, otherwise the schema's list.
234 pub fn effective_env_vars(&self) -> &'static [&'static str] {
235 self.env_var_override.unwrap_or(self.schema.env_vars)
236 }
237
238 /// Shape of the value: override if set, otherwise from the schema.
239 pub fn value_type(&self) -> ValueType {
240 self.value_type_override.unwrap_or(self.schema.value_type)
241 }
242}
243
244/// A fully resolved configuration key, derived from a [`SalukiAnnotation`] at registry init time.
245///
246/// Used for runtime unknown-key detection and anywhere a flattened, owned view of a key is
247/// needed. For test infrastructure, prefer working with [`SalukiAnnotation`] directly.
248#[derive(Debug)]
249pub struct ConfigKey {
250 /// All dot-separated YAML paths that deliver this value.
251 pub yaml_paths: Vec<&'static str>,
252
253 /// All environment variables that deliver this value.
254 pub env_vars: Vec<&'static str>,
255
256 /// Shape of the value.
257 pub value_type: ValueType,
258
259 /// Config structs that incorporate this key, as [`structs`] constants.
260 pub used_by: &'static [&'static str],
261}
262
263impl From<&SalukiAnnotation> for ConfigKey {
264 fn from(a: &SalukiAnnotation) -> Self {
265 ConfigKey {
266 yaml_paths: a.all_yaml_paths().collect(),
267 env_vars: a.effective_env_vars().to_vec(),
268 value_type: a.value_type(),
269 used_by: a.used_by,
270 }
271 }
272}