Skip to main content

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}