Skip to content

Commit a4a35ef

Browse files
committed
refactor(agent-config): move inline deserializer helpers to deserializers/helpers.rs
Extracts all generic deserializer functions (deserialize_optional_string, deserialize_with_default, duration parsers, key-value parsers, etc.) from lib.rs into src/deserializers/helpers.rs. Re-exported at the crate root so all existing import paths continue to work.
1 parent 214fd8e commit a4a35ef

File tree

3 files changed

+381
-368
lines changed

3 files changed

+381
-368
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
use serde::{Deserialize, Deserializer};
2+
use serde_aux::prelude::deserialize_bool_from_anything;
3+
use serde_json::Value;
4+
5+
use std::collections::HashMap;
6+
use std::fmt;
7+
use std::time::Duration;
8+
use tracing::warn;
9+
10+
use crate::TracePropagationStyle;
11+
12+
pub fn deserialize_optional_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
13+
where
14+
D: Deserializer<'de>,
15+
{
16+
match Value::deserialize(deserializer)? {
17+
Value::String(s) => Ok(Some(s)),
18+
other => {
19+
warn!(
20+
"Failed to parse value, expected a string, got: {}, ignoring",
21+
other
22+
);
23+
Ok(None)
24+
}
25+
}
26+
}
27+
28+
pub fn deserialize_string_or_int<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
29+
where
30+
D: Deserializer<'de>,
31+
{
32+
let value = Value::deserialize(deserializer)?;
33+
match value {
34+
Value::String(s) => {
35+
if s.trim().is_empty() {
36+
Ok(None)
37+
} else {
38+
Ok(Some(s))
39+
}
40+
}
41+
Value::Number(n) => Ok(Some(n.to_string())),
42+
_ => {
43+
warn!("Failed to parse value, expected a string or an integer, ignoring");
44+
Ok(None)
45+
}
46+
}
47+
}
48+
49+
pub fn deserialize_optional_bool_from_anything<'de, D>(
50+
deserializer: D,
51+
) -> Result<Option<bool>, D::Error>
52+
where
53+
D: Deserializer<'de>,
54+
{
55+
// First try to deserialize as Option<_> to handle null/missing values
56+
let opt: Option<serde_json::Value> = Option::deserialize(deserializer)?;
57+
58+
match opt {
59+
None => Ok(None),
60+
Some(value) => match deserialize_bool_from_anything(value) {
61+
Ok(bool_result) => Ok(Some(bool_result)),
62+
Err(e) => {
63+
warn!("Failed to parse bool value: {}, ignoring", e);
64+
Ok(None)
65+
}
66+
},
67+
}
68+
}
69+
70+
/// Parse a single "key:value" string into a (key, value) tuple
71+
/// Returns None if the string is invalid (e.g., missing colon, empty key/value)
72+
fn parse_key_value_tag(tag: &str) -> Option<(String, String)> {
73+
let parts: Vec<&str> = tag.splitn(2, ':').collect();
74+
if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() {
75+
Some((parts[0].to_string(), parts[1].to_string()))
76+
} else {
77+
warn!(
78+
"Failed to parse tag '{}', expected format 'key:value', ignoring",
79+
tag
80+
);
81+
None
82+
}
83+
}
84+
85+
pub fn deserialize_key_value_pairs<'de, D>(
86+
deserializer: D,
87+
) -> Result<HashMap<String, String>, D::Error>
88+
where
89+
D: Deserializer<'de>,
90+
{
91+
struct KeyValueVisitor;
92+
93+
impl serde::de::Visitor<'_> for KeyValueVisitor {
94+
type Value = HashMap<String, String>;
95+
96+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
97+
formatter.write_str("a string in format 'key1:value1,key2:value2' or 'key1:value1'")
98+
}
99+
100+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
101+
where
102+
E: serde::de::Error,
103+
{
104+
let mut map = HashMap::new();
105+
for tag in value.split(&[',', ' ']) {
106+
if tag.is_empty() {
107+
continue;
108+
}
109+
if let Some((key, val)) = parse_key_value_tag(tag) {
110+
map.insert(key, val);
111+
}
112+
}
113+
114+
Ok(map)
115+
}
116+
117+
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
118+
where
119+
E: serde::de::Error,
120+
{
121+
warn!(
122+
"Failed to parse tags: expected string in format 'key:value', got number {}, ignoring",
123+
value
124+
);
125+
Ok(HashMap::new())
126+
}
127+
128+
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
129+
where
130+
E: serde::de::Error,
131+
{
132+
warn!(
133+
"Failed to parse tags: expected string in format 'key:value', got number {}, ignoring",
134+
value
135+
);
136+
Ok(HashMap::new())
137+
}
138+
139+
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
140+
where
141+
E: serde::de::Error,
142+
{
143+
warn!(
144+
"Failed to parse tags: expected string in format 'key:value', got number {}, ignoring",
145+
value
146+
);
147+
Ok(HashMap::new())
148+
}
149+
150+
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
151+
where
152+
E: serde::de::Error,
153+
{
154+
warn!(
155+
"Failed to parse tags: expected string in format 'key:value', got boolean {}, ignoring",
156+
value
157+
);
158+
Ok(HashMap::new())
159+
}
160+
}
161+
162+
deserializer.deserialize_any(KeyValueVisitor)
163+
}
164+
165+
pub fn deserialize_array_from_comma_separated_string<'de, D>(
166+
deserializer: D,
167+
) -> Result<Vec<String>, D::Error>
168+
where
169+
D: Deserializer<'de>,
170+
{
171+
let s: String = String::deserialize(deserializer)?;
172+
Ok(s.split(',')
173+
.map(|feature| feature.trim().to_string())
174+
.filter(|feature| !feature.is_empty())
175+
.collect())
176+
}
177+
178+
pub fn deserialize_key_value_pair_array_to_hashmap<'de, D>(
179+
deserializer: D,
180+
) -> Result<HashMap<String, String>, D::Error>
181+
where
182+
D: Deserializer<'de>,
183+
{
184+
let array: Vec<String> = match Vec::deserialize(deserializer) {
185+
Ok(v) => v,
186+
Err(e) => {
187+
warn!("Failed to deserialize tags array: {e}, ignoring");
188+
return Ok(HashMap::new());
189+
}
190+
};
191+
let mut map = HashMap::new();
192+
for s in array {
193+
if let Some((key, val)) = parse_key_value_tag(&s) {
194+
map.insert(key, val);
195+
}
196+
}
197+
Ok(map)
198+
}
199+
200+
/// Deserialize APM filter tags from space-separated "key:value" pairs, also support key-only tags
201+
pub fn deserialize_apm_filter_tags<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
202+
where
203+
D: Deserializer<'de>,
204+
{
205+
let opt: Option<String> = Option::deserialize(deserializer)?;
206+
207+
match opt {
208+
None => Ok(None),
209+
Some(s) if s.trim().is_empty() => Ok(None),
210+
Some(s) => {
211+
let tags: Vec<String> = s
212+
.split_whitespace()
213+
.filter_map(|pair| {
214+
let parts: Vec<&str> = pair.splitn(2, ':').collect();
215+
if parts.len() == 2 {
216+
let key = parts[0].trim();
217+
let value = parts[1].trim();
218+
if key.is_empty() {
219+
None
220+
} else if value.is_empty() {
221+
Some(key.to_string())
222+
} else {
223+
Some(format!("{key}:{value}"))
224+
}
225+
} else if parts.len() == 1 {
226+
let key = parts[0].trim();
227+
if key.is_empty() {
228+
None
229+
} else {
230+
Some(key.to_string())
231+
}
232+
} else {
233+
None
234+
}
235+
})
236+
.collect();
237+
238+
if tags.is_empty() {
239+
Ok(None)
240+
} else {
241+
Ok(Some(tags))
242+
}
243+
}
244+
}
245+
}
246+
247+
pub fn deserialize_option_lossless<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
248+
where
249+
D: Deserializer<'de>,
250+
T: Deserialize<'de>,
251+
{
252+
match Option::<T>::deserialize(deserializer) {
253+
Ok(value) => Ok(value),
254+
Err(e) => {
255+
warn!("Failed to deserialize optional value: {}, ignoring", e);
256+
Ok(None)
257+
}
258+
}
259+
}
260+
261+
/// Gracefully deserialize any field, falling back to `T::default()` on error.
262+
///
263+
/// This ensures that a single field with the wrong type never fails the entire
264+
/// struct extraction. Works for any `T` that implements `Deserialize + Default`:
265+
/// - `Option<T>` defaults to `None`
266+
/// - `Vec<T>` defaults to `[]`
267+
/// - `HashMap<K,V>` defaults to `{}`
268+
/// - Structs with `#[derive(Default)]` use their default
269+
pub fn deserialize_with_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
270+
where
271+
D: Deserializer<'de>,
272+
T: Deserialize<'de> + Default,
273+
{
274+
match T::deserialize(deserializer) {
275+
Ok(value) => Ok(value),
276+
Err(e) => {
277+
warn!("Failed to deserialize field: {}, using default", e);
278+
Ok(T::default())
279+
}
280+
}
281+
}
282+
283+
pub fn deserialize_optional_duration_from_microseconds<'de, D: Deserializer<'de>>(
284+
deserializer: D,
285+
) -> Result<Option<Duration>, D::Error> {
286+
match Option::<u64>::deserialize(deserializer) {
287+
Ok(opt) => Ok(opt.map(Duration::from_micros)),
288+
Err(e) => {
289+
warn!("Failed to deserialize duration (microseconds): {e}, ignoring");
290+
Ok(None)
291+
}
292+
}
293+
}
294+
295+
pub fn deserialize_optional_duration_from_seconds<'de, D: Deserializer<'de>>(
296+
deserializer: D,
297+
) -> Result<Option<Duration>, D::Error> {
298+
// Deserialize into a generic Value first to avoid propagating type errors,
299+
// then try to extract a duration from it.
300+
match Value::deserialize(deserializer) {
301+
Ok(Value::Number(n)) => {
302+
if let Some(u) = n.as_u64() {
303+
Ok(Some(Duration::from_secs(u)))
304+
} else if let Some(i) = n.as_i64() {
305+
if i < 0 {
306+
warn!("Failed to parse duration: negative durations are not allowed, ignoring");
307+
Ok(None)
308+
} else {
309+
Ok(Some(Duration::from_secs(i as u64)))
310+
}
311+
} else if let Some(f) = n.as_f64() {
312+
if f < 0.0 {
313+
warn!("Failed to parse duration: negative durations are not allowed, ignoring");
314+
Ok(None)
315+
} else {
316+
Ok(Some(Duration::from_secs_f64(f)))
317+
}
318+
} else {
319+
warn!("Failed to parse duration: unsupported number format, ignoring");
320+
Ok(None)
321+
}
322+
}
323+
Ok(Value::Null) => Ok(None),
324+
Ok(other) => {
325+
warn!("Failed to parse duration: expected number, got {other}, ignoring");
326+
Ok(None)
327+
}
328+
Err(e) => {
329+
warn!("Failed to deserialize duration: {e}, ignoring");
330+
Ok(None)
331+
}
332+
}
333+
}
334+
335+
// Like deserialize_optional_duration_from_seconds(), but return None if the value is 0
336+
pub fn deserialize_optional_duration_from_seconds_ignore_zero<'de, D: Deserializer<'de>>(
337+
deserializer: D,
338+
) -> Result<Option<Duration>, D::Error> {
339+
let duration: Option<Duration> = deserialize_optional_duration_from_seconds(deserializer)?;
340+
if duration.is_some_and(|d| d.as_secs() == 0) {
341+
return Ok(None);
342+
}
343+
Ok(duration)
344+
}
345+
346+
pub fn deserialize_trace_propagation_style<'de, D>(
347+
deserializer: D,
348+
) -> Result<Vec<TracePropagationStyle>, D::Error>
349+
where
350+
D: Deserializer<'de>,
351+
{
352+
use std::str::FromStr;
353+
let s: String = match String::deserialize(deserializer) {
354+
Ok(s) => s,
355+
Err(e) => {
356+
warn!("Failed to deserialize trace propagation style: {e}, ignoring");
357+
return Ok(Vec::new());
358+
}
359+
};
360+
361+
Ok(s.split(',')
362+
.filter_map(
363+
|style| match TracePropagationStyle::from_str(style.trim()) {
364+
Ok(parsed_style) => Some(parsed_style),
365+
Err(e) => {
366+
warn!("Failed to parse trace propagation style: {e}, ignoring");
367+
None
368+
}
369+
},
370+
)
371+
.collect())
372+
}

crates/datadog-agent-config/src/deserializers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod additional_endpoints;
22
pub mod apm_replace_rule;
33
pub mod flush_strategy;
4+
pub mod helpers;
45
pub mod log_level;
56
pub mod logs_additional_endpoints;
67
pub mod processing_rule;

0 commit comments

Comments
 (0)