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