datadog_agent_config/
lib.rs1mod cast_de;
3
4pub mod classifier;
5
6mod duration_de;
7
8pub mod env_decode;
10mod list_de;
11
12pub mod env_provider;
14
15pub mod env_reader;
18
19mod generated;
21
22mod schema_defaults;
24
25mod translate_error;
27
28pub use cast_de::cast_to_string;
29pub use env_decode::EnvDecode;
30pub use env_provider::DatadogEnvProvider;
31pub use env_reader::{apply_datadog_env, apply_datadog_env_vars, apply_env_at_path, datadog_leaf_paths, EnvKey};
32pub use generated::{drive, DatadogConfigWitness, DatadogConfiguration};
33pub use translate_error::{TranslateError, TranslateErrors};
34
35#[cfg(test)]
36mod string_list_shape_tests {
37 use super::DatadogConfiguration;
38
39 #[test]
46 fn string_list_leaf_accepts_a_space_separated_string() {
47 let config: DatadogConfiguration =
48 serde_json::from_value(serde_json::json!({ "dogstatsd_tags": "env:prod team:core" }))
49 .expect("space-separated string deserializes into the string-list leaf");
50 assert_eq!(config.dogstatsd_tags, vec!["env:prod", "team:core"]);
51 }
52
53 #[test]
54 fn string_list_leaf_accepts_a_sequence() {
55 let config: DatadogConfiguration =
56 serde_json::from_value(serde_json::json!({ "dogstatsd_tags": ["env:prod", "team:core"] }))
57 .expect("sequence deserializes into the string-list leaf");
58 assert_eq!(config.dogstatsd_tags, vec!["env:prod", "team:core"]);
59 }
60}
61
62#[cfg(test)]
63mod string_map_list_shape_tests {
64 use super::DatadogConfiguration;
65
66 #[test]
67 fn additional_endpoints_accept_scalar_values() {
68 let config: DatadogConfiguration = serde_json::from_value(serde_json::json!({
69 "additional_endpoints": {
70 "https://agent.datadoghq.com.": "ENC[vault://api-key]"
71 }
72 }))
73 .expect("scalar additional endpoint API key deserializes");
74
75 assert_eq!(
76 config.additional_endpoints["https://agent.datadoghq.com."],
77 ["ENC[vault://api-key]"]
78 );
79 }
80
81 #[test]
82 fn additional_endpoints_accept_sequence_values() {
83 let config: DatadogConfiguration = serde_json::from_value(serde_json::json!({
84 "additional_endpoints": {
85 "https://agent.datadoghq.com.": ["first", "second"]
86 }
87 }))
88 .expect("additional endpoint API key sequence deserializes");
89
90 assert_eq!(
91 config.additional_endpoints["https://agent.datadoghq.com."],
92 ["first", "second"]
93 );
94 }
95}
96
97#[cfg(test)]
98mod scalar_shape_tests {
99 use serde_json::{json, Value};
100
101 use super::env_decode::EnvDecode;
102 use super::generated::env_keys::DATADOG_ENV_KEYS;
103 use super::DatadogConfiguration;
104
105 #[test]
111 fn boolean_leaf_accepts_a_boolean_string() {
112 let config: DatadogConfiguration = serde_json::from_value(json!({ "dogstatsd_non_local_traffic": "true" }))
113 .expect("boolean string deserializes");
114 assert!(config.dogstatsd_non_local_traffic);
115 }
116
117 #[test]
118 fn integer_leaf_accepts_a_numeric_string() {
119 let config: DatadogConfiguration =
120 serde_json::from_value(json!({ "dogstatsd_port": "8125" })).expect("numeric string deserializes");
121 assert_eq!(config.dogstatsd_port, 8125);
122 }
123
124 #[test]
125 fn string_leaf_accepts_a_boolean() {
126 let config: DatadogConfiguration =
128 serde_json::from_value(json!({ "use_v3_api": { "series": { "enabled": true } } }))
129 .expect("boolean V3 series mode deserializes");
130 assert_eq!(config.use_v3_api.series.enabled, "true");
131 }
132
133 #[test]
134 fn string_leaf_accepts_a_numeric_byte_count() {
135 let config: DatadogConfiguration = serde_json::from_value(json!({ "dogstatsd_log_file_max_size": 10485760 }))
137 .expect("byte count deserializes");
138 assert_eq!(config.dogstatsd_log_file_max_size, "10485760");
139 }
140
141 #[test]
142 fn every_scalar_leaf_accepts_the_agent_castable_form_of_its_type() {
143 let defaults = serde_json::to_value(DatadogConfiguration::default()).expect("defaults serialize");
146
147 for key in DATADOG_ENV_KEYS {
148 let pointer = format!("/{}", key.path.join("/"));
149 let current = defaults.pointer(&pointer);
150
151 let (written, expected) = match key.decode {
156 EnvDecode::Bool => {
157 let flipped = !current.and_then(Value::as_bool).unwrap_or(false);
158 (json!(flipped.to_string()), json!(flipped))
159 }
160 EnvDecode::Integer => {
161 let bumped = current.and_then(Value::as_i64).unwrap_or(0) + 1;
162 (json!(bumped.to_string()), json!(bumped))
163 }
164 EnvDecode::Float => {
165 let bumped = current.and_then(Value::as_f64).unwrap_or(0.0) + 1.5;
166 (json!(bumped.to_string()), json!(bumped))
167 }
168 EnvDecode::RawString => {
169 let flag = current.and_then(Value::as_str) != Some("true");
170 (json!(flag), json!(flag.to_string()))
171 }
172 _ => continue,
173 };
174
175 let mut tree = written.clone();
176 for segment in key.path.iter().rev() {
177 tree = json!({ *segment: tree });
178 }
179
180 let leaf = key.path.join(".");
181 let config: DatadogConfiguration =
182 serde_json::from_value(tree).unwrap_or_else(|e| panic!("leaf `{leaf}` rejected {written}: {e}"));
183 let coerced = serde_json::to_value(config).expect("the configuration serializes");
184 assert_eq!(
185 coerced.pointer(&pointer),
186 Some(&expected),
187 "leaf `{leaf}` did not coerce {written}"
188 );
189 }
190 }
191
192 #[test]
193 fn a_malformed_scalar_is_still_rejected() {
194 for malformed in [
197 json!({ "dogstatsd_non_local_traffic": "yes" }),
198 json!({ "dogstatsd_port": "8125ms" }),
199 json!({ "dogstatsd_log_file_max_size": ["10MB"] }),
200 ] {
201 let result: Result<DatadogConfiguration, _> = serde_json::from_value(malformed.clone());
202 assert!(result.is_err(), "{malformed} should be rejected");
203 }
204 }
205
206 #[test]
207 fn a_null_scalar_reads_as_the_type_zero_value() {
208 let config: DatadogConfiguration =
209 serde_json::from_value(json!({ "api_key": Value::Null })).expect("an explicitly null leaf deserializes");
210 assert_eq!(config.api_key, "");
211 }
212}