Skip to main content

datadog_agent_config/
cast_de.rs

1//! Deserialize schema values the way the Datadog Agent reads them.
2//!
3//! The Agent never reads a setting as the type its YAML happens to hold: `GetBool`, `GetInt`,
4//! `GetFloat64`, and `GetString` each coerce whatever is stored through `spf13/cast`. Permissiveness
5//! is therefore a property of the leaf's declared type, not of the key, and `dogstatsd_port: "8125"`
6//! or `use_v3_api.series.enabled: true` are configurations the Agent accepts.
7//!
8//! This module ports `cast.To{Bool,Int64,Float64,String}E` so scalar leaves and string-map values
9//! accept every spelling the Agent accepts while keeping the schema's type. [`crate::env_decode`]
10//! uses the same parsers for environment strings.
11//!
12//! Three deliberate divergences from `cast`:
13//!
14//! - A value `cast` cannot convert is a hard error here. `cast.To*` swallows its error and yields the
15//!   zero value, so the Agent silently reads a malformed setting as `false`/`0`/`""`;
16//!   [`crate::env_decode`] already rejects such a value rather than losing it. The exception is an
17//!   integer string with a leading `0` and an `8` or `9` digit (see [`parse_i64`]), which reads as `0`
18//!   as it does in the Agent.
19//! - Surrounding whitespace is trimmed from a numeric string, which Go's parsers reject.
20//! - A float string must be finite. Go accepts `inf` and `nan`, which a JSON number cannot hold.
21
22use std::{collections::HashMap, fmt, marker::PhantomData};
23
24use serde::de::{self, DeserializeOwned, Deserializer, MapAccess, Unexpected, Visitor};
25
26/// `cast.ToBoolE` for a string: Go's `strconv.ParseBool` grammar, exactly.
27///
28/// # Errors
29///
30/// Returns a message naming the value when it is not one of the accepted spellings.
31pub(crate) fn parse_bool(raw: &str) -> Result<bool, String> {
32    match raw {
33        "1" | "t" | "T" | "TRUE" | "true" | "True" => Ok(true),
34        "0" | "f" | "F" | "FALSE" | "false" | "False" => Ok(false),
35        other => Err(format!("invalid boolean `{other}`")),
36    }
37}
38
39/// `cast.ToInt64E` for a string: Go's `strconv.ParseInt(s, 0, 64)` after `trimZeroDecimal`.
40///
41/// Accepts an optional sign; `0x`, `0o` and `0b` prefixes in either case; a bare leading `0` as octal;
42/// `_` digit separators where Go allows them; and the full `i64` range. `"0x10.0"` is `16` because the
43/// zero fraction is dropped before the base is read.
44///
45/// A signed or unsigned run of decimal digits with a leading `0` and an `8` or `9`, such as `"08"`,
46/// `"-09"`, `"0128"` or `"08.0"`, is octal with a digit out of range, so Go rejects it. The Agent
47/// still starts with such a value and reads it as `0` (`GetInt` logs and returns the zero value), so it
48/// reads as `0` here, with a warning.
49///
50/// # Errors
51///
52/// Returns a message naming the value when it is not an integer in that grammar.
53pub(crate) fn parse_i64(raw: &str) -> Result<i64, String> {
54    let literal = trim_zero_decimal(raw.trim());
55    if let Some(value) = parse_go_int(literal) {
56        return Ok(value);
57    }
58    if is_invalid_octal(literal) {
59        tracing::warn!(
60            "Integer `{raw}` has a leading 0, so it is octal, but it holds an 8 or 9. Reading it as 0, as the \
61             Datadog Agent does."
62        );
63        return Ok(0);
64    }
65    Err(format!("invalid integer `{raw}`"))
66}
67
68/// Whether `s` is an optionally signed run of decimal digits with a leading `0` and an `8` or `9`.
69fn is_invalid_octal(s: &str) -> bool {
70    let digits = s.strip_prefix(['+', '-']).unwrap_or(s);
71    digits.len() > 1
72        && digits.starts_with('0')
73        && digits.bytes().all(|byte| byte.is_ascii_digit())
74        && digits.bytes().any(|byte| byte >= b'8')
75}
76
77/// A decimal integer, optionally followed by an all-zero fraction.
78///
79/// This is the grammar of Saluki-only settings, which have no Agent counterpart: no base prefixes,
80/// no underscores, and a leading `0` is decimal (`"010"` is `10`).
81///
82/// # Errors
83///
84/// Returns a message naming the value when it is not in that grammar.
85pub(crate) fn parse_decimal_i64(raw: &str) -> Result<i64, String> {
86    trim_zero_fraction(raw.trim())
87        .parse::<i64>()
88        .map_err(|_| format!("invalid integer `{raw}`"))
89}
90
91/// Drops an all-zero fraction, so `"8125.0"` is an integer while `"8125.5"` is not.
92fn trim_zero_fraction(raw: &str) -> &str {
93    match raw.split_once('.') {
94        Some((integer, fraction)) if !fraction.is_empty() && fraction.bytes().all(|byte| byte == b'0') => integer,
95        _ => raw,
96    }
97}
98
99/// `cast`'s `trimZeroDecimal`, byte for byte.
100///
101/// Scanning from the end, a run of `0`s followed by a `.` drops both. Any other byte stops the scan,
102/// and a `.` met before any `0` is skipped. It is not limited to numbers: `"1.0.0"` becomes `"1.0"`
103/// and `".0"` becomes `""`.
104fn trim_zero_decimal(s: &str) -> &str {
105    let mut found_zero = false;
106    for (index, byte) in s.bytes().enumerate().rev() {
107        match byte {
108            b'.' if found_zero => return &s[..index],
109            b'.' => {}
110            b'0' => found_zero = true,
111            _ => return s,
112        }
113    }
114    s
115}
116
117/// Go's lowercase fold for ASCII letters, as used throughout `strconv`.
118fn lower(byte: u8) -> u8 {
119    byte | 0x20
120}
121
122/// Go's `strconv.ParseInt(s, 0, 64)`.
123fn parse_go_int(s: &str) -> Option<i64> {
124    let (negative, unsigned) = match s.as_bytes().first()? {
125        b'+' => (false, &s[1..]),
126        b'-' => (true, &s[1..]),
127        _ => (false, s),
128    };
129    let magnitude = parse_go_uint(unsigned.as_bytes())?;
130    if negative {
131        i64::try_from(-i128::from(magnitude)).ok()
132    } else {
133        i64::try_from(magnitude).ok()
134    }
135}
136
137/// Go's `strconv.ParseUint(s, 0, 64)`: the base comes from the prefix.
138fn parse_go_uint(s: &[u8]) -> Option<u64> {
139    let (base, digits) = match s {
140        [b'0', prefix, rest @ ..] if !rest.is_empty() && lower(*prefix) == b'b' => (2, rest),
141        [b'0', prefix, rest @ ..] if !rest.is_empty() && lower(*prefix) == b'o' => (8, rest),
142        [b'0', prefix, rest @ ..] if !rest.is_empty() && lower(*prefix) == b'x' => (16, rest),
143        [b'0', rest @ ..] => (8, rest),
144        [] => return None,
145        _ => (10, s),
146    };
147
148    let mut value: u64 = 0;
149    let mut saw_underscore = false;
150    for &byte in digits {
151        let digit = match byte {
152            b'_' => {
153                saw_underscore = true;
154                continue;
155            }
156            b'0'..=b'9' => byte - b'0',
157            _ if lower(byte).is_ascii_lowercase() => lower(byte) - b'a' + 10,
158            _ => return None,
159        };
160        if digit >= base {
161            return None;
162        }
163        value = value.checked_mul(u64::from(base))?.checked_add(u64::from(digit))?;
164    }
165    if saw_underscore && !underscore_ok(s) {
166        return None;
167    }
168    Some(value)
169}
170
171/// Go's `underscoreOK`: whether `_` appears only between digits, or between a base prefix and a digit.
172fn underscore_ok(s: &[u8]) -> bool {
173    #[derive(PartialEq)]
174    enum Last {
175        Start,
176        Digit,
177        Underscore,
178        Other,
179    }
180
181    let s = match s.first() {
182        Some(b'+' | b'-') => &s[1..],
183        _ => s,
184    };
185
186    let mut last = Last::Start;
187    let mut hex = false;
188    let mut rest = s;
189    if let [b'0', prefix, tail @ ..] = s {
190        if matches!(lower(*prefix), b'b' | b'o' | b'x') {
191            // The prefix counts as a digit, so `0x_1` is fine.
192            last = Last::Digit;
193            hex = lower(*prefix) == b'x';
194            rest = tail;
195        }
196    }
197
198    for &byte in rest {
199        if byte.is_ascii_digit() || (hex && matches!(lower(byte), b'a'..=b'f')) {
200            last = Last::Digit;
201        } else if byte == b'_' {
202            if last != Last::Digit {
203                return false;
204            }
205            last = Last::Underscore;
206        } else if last == Last::Underscore {
207            return false;
208        } else {
209            last = Last::Other;
210        }
211    }
212    last != Last::Underscore
213}
214
215/// `cast.ToFloat64E` for a string: Go's `strconv.ParseFloat(s, 64)`, finite values only.
216///
217/// Accepts an optional sign, decimal literals with an optional exponent, `_` digit separators where
218/// Go allows them, and hexadecimal literals, which require a `p` exponent (`"0x1p4"` is `16`).
219///
220/// # Errors
221///
222/// Returns a message naming the value when it is not a finite number in that grammar. `inf`, `nan`
223/// and a literal that overflows are rejected.
224pub(crate) fn parse_f64(raw: &str) -> Result<f64, String> {
225    finite_number(raw, parse_go_float(raw.trim()))
226}
227
228/// A finite number in Rust's `f64` grammar: decimal only, with no `_` separators.
229///
230/// This is the grammar of Saluki-only settings, which have no Agent counterpart.
231///
232/// # Errors
233///
234/// Returns a message naming the value when it is not a finite number in that grammar.
235pub(crate) fn parse_decimal_f64(raw: &str) -> Result<f64, String> {
236    finite_number(raw, raw.trim().parse().ok())
237}
238
239/// Names `raw` in the error for a missing or non-finite `parsed`.
240fn finite_number(raw: &str, parsed: Option<f64>) -> Result<f64, String> {
241    let parsed = parsed.ok_or_else(|| format!("invalid number `{raw}`"))?;
242    if !parsed.is_finite() {
243        return Err(format!("non-finite number `{raw}`"));
244    }
245    Ok(parsed)
246}
247
248/// The syntax of a Go float literal, as `strconv.readFloat` reads it. Mantissa digits past what a `u64`
249/// holds only set `truncated`.
250struct FloatLiteral {
251    negative: bool,
252    hex: bool,
253    mantissa: u64,
254    /// The power of the literal's base, 2 for hex and 10 otherwise, by which `mantissa` is scaled.
255    exponent: i64,
256    truncated: bool,
257}
258
259/// Go's `strconv.ParseFloat(s, 64)`, returning an infinity for an overflowing literal and `None` for
260/// a syntax error. `inf` and `nan` are syntax errors here.
261fn parse_go_float(s: &str) -> Option<f64> {
262    let literal = FloatLiteral::read(s.as_bytes())?;
263    if literal.hex {
264        return Some(literal.hex_to_f64());
265    }
266    // Without its underscores, the literal is in Rust's grammar, which rounds correctly at any length.
267    s.replace('_', "").parse().ok()
268}
269
270impl FloatLiteral {
271    /// Go's `strconv.readFloat`, requiring that it consume all of `s`.
272    fn read(s: &[u8]) -> Option<Self> {
273        let mut i = 0;
274        let mut negative = false;
275        match s.first() {
276            Some(b'+') => i += 1,
277            Some(b'-') => {
278                negative = true;
279                i += 1;
280            }
281            _ => {}
282        }
283
284        let hex = s.len() > i + 2 && s[i] == b'0' && lower(s[i + 1]) == b'x';
285        let (max_mantissa_digits, exponent_char) = if hex {
286            i += 2;
287            (16, b'p')
288        } else {
289            (19, b'e')
290        };
291        let base: u64 = if hex { 16 } else { 10 };
292
293        let mut underscores = false;
294        let mut saw_dot = false;
295        let mut saw_digits = false;
296        let mut digits = 0;
297        let mut mantissa_digits = 0;
298        let mut mantissa: u64 = 0;
299        let mut truncated = false;
300        let mut dot_position: i64 = 0;
301        while i < s.len() {
302            let byte = s[i];
303            match byte {
304                b'_' => underscores = true,
305                b'.' => {
306                    if saw_dot {
307                        break;
308                    }
309                    saw_dot = true;
310                    dot_position = digits;
311                }
312                b'0'..=b'9' => {
313                    saw_digits = true;
314                    if byte == b'0' && digits == 0 {
315                        dot_position -= 1;
316                    } else {
317                        digits += 1;
318                        if mantissa_digits < max_mantissa_digits {
319                            mantissa = mantissa * base + u64::from(byte - b'0');
320                            mantissa_digits += 1;
321                        } else if byte != b'0' {
322                            truncated = true;
323                        }
324                    }
325                }
326                _ if hex && matches!(lower(byte), b'a'..=b'f') => {
327                    saw_digits = true;
328                    digits += 1;
329                    if mantissa_digits < max_mantissa_digits {
330                        mantissa = mantissa * 16 + u64::from(lower(byte) - b'a' + 10);
331                        mantissa_digits += 1;
332                    } else {
333                        truncated = true;
334                    }
335                }
336                _ => break,
337            }
338            i += 1;
339        }
340        if !saw_digits {
341            return None;
342        }
343        if !saw_dot {
344            dot_position = digits;
345        }
346        let mut mantissa_digits = i64::from(mantissa_digits);
347        if hex {
348            dot_position *= 4;
349            mantissa_digits *= 4;
350        }
351
352        if i < s.len() && lower(s[i]) == exponent_char {
353            i += 1;
354            let mut exponent_sign = 1;
355            match s.get(i) {
356                Some(b'+') => i += 1,
357                Some(b'-') => {
358                    exponent_sign = -1;
359                    i += 1;
360                }
361                _ => {}
362            }
363            if !s.get(i)?.is_ascii_digit() {
364                return None;
365            }
366            let mut exponent: i64 = 0;
367            while let Some(&byte) = s.get(i) {
368                if byte == b'_' {
369                    underscores = true;
370                } else if byte.is_ascii_digit() {
371                    // Go's cap, which keeps a huge exponent from overflowing.
372                    if exponent < 10_000 {
373                        exponent = exponent * 10 + i64::from(byte - b'0');
374                    }
375                } else {
376                    break;
377                }
378                i += 1;
379            }
380            dot_position += exponent * exponent_sign;
381        } else if hex {
382            return None;
383        }
384
385        if i != s.len() || (underscores && !underscore_ok(s)) {
386            return None;
387        }
388        Some(Self {
389            negative,
390            hex,
391            mantissa,
392            exponent: if mantissa == 0 {
393                0
394            } else {
395                dot_position - mantissa_digits
396            },
397            truncated,
398        })
399    }
400
401    /// Go's `atofHex` for `float64`: rounds `mantissa * 2^exponent` to nearest even, with a sticky bit for
402    /// bits beyond the 64 that `mantissa` holds. Overflow is infinity.
403    fn hex_to_f64(&self) -> f64 {
404        const MANTISSA_BITS: u32 = 52;
405        const EXPONENT_BIAS: i64 = -1023;
406        const MAX_EXPONENT: i64 = 1023;
407        const MIN_EXPONENT: i64 = EXPONENT_BIAS + 1;
408
409        let mut mantissa = self.mantissa;
410        // The mantissa is now implicitly divided by 2^MANTISSA_BITS.
411        let mut exponent = self.exponent + i64::from(MANTISSA_BITS);
412
413        // Normalize to a leading 1 bit followed by MANTISSA_BITS bits plus two bits for rounding, the
414        // lowest of which records whether anything non-zero lies beyond it.
415        while mantissa != 0 && mantissa >> (MANTISSA_BITS + 2) == 0 {
416            mantissa <<= 1;
417            exponent -= 1;
418        }
419        if self.truncated {
420            mantissa |= 1;
421        }
422        while mantissa >> (MANTISSA_BITS + 3) != 0 {
423            mantissa = mantissa >> 1 | (mantissa & 1);
424            exponent += 1;
425        }
426        // Denormalize an exponent that is too small.
427        while mantissa > 1 && exponent < MIN_EXPONENT - 2 {
428            mantissa = mantissa >> 1 | (mantissa & 1);
429            exponent += 1;
430        }
431
432        let mut round = mantissa & 3;
433        mantissa >>= 2;
434        round |= mantissa & 1;
435        exponent += 2;
436        if round == 3 {
437            mantissa += 1;
438            if mantissa == 1 << (MANTISSA_BITS + 1) {
439                mantissa >>= 1;
440                exponent += 1;
441            }
442        }
443        if mantissa >> MANTISSA_BITS == 0 {
444            exponent = EXPONENT_BIAS;
445        }
446        if exponent > MAX_EXPONENT {
447            return if self.negative {
448                f64::NEG_INFINITY
449            } else {
450                f64::INFINITY
451            };
452        }
453
454        let mut bits = mantissa & ((1 << MANTISSA_BITS) - 1);
455        bits |= (((exponent - EXPONENT_BIAS) & 0x7ff) as u64) << MANTISSA_BITS;
456        if self.negative {
457            bits |= 1 << 63;
458        }
459        f64::from_bits(bits)
460    }
461}
462
463/// `cast.ToStringSliceE` for a string.
464///
465/// A string that is wholly a JSON array of strings (a `null` element is `""`) or the JSON `null` (an
466/// empty list) is decoded as JSON. Anything else, including JSON that is not a string array, is split
467/// on whitespace.
468///
469/// One difference remains: Go repairs a lone UTF-16 surrogate escape in a JSON string, while here
470/// that string is not JSON and is split on whitespace.
471pub(crate) fn parse_string_list(raw: &str) -> Vec<String> {
472    match serde_json::from_str::<Option<Vec<Option<String>>>>(raw) {
473        Ok(elements) => elements
474            .unwrap_or_default()
475            .into_iter()
476            .map(Option::unwrap_or_default)
477            .collect(),
478        Err(_) => raw.split_whitespace().map(str::to_owned).collect(),
479    }
480}
481
482/// Deserializes a `boolean` leaf (`cast.ToBoolE`).
483///
484/// # Errors
485///
486/// Returns an error for a value the Agent cannot cast to a boolean: an unrecognized string or a
487/// compound value.
488pub(crate) fn deserialize_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
489where
490    D: Deserializer<'de>,
491{
492    deserializer.deserialize_any(BoolVisitor)
493}
494
495/// Deserializes an `integer` leaf (`cast.ToInt64E`).
496///
497/// # Errors
498///
499/// Returns an error for a value the Agent cannot cast to an integer: a non-numeric string, an
500/// out-of-range number, or a compound value.
501pub(crate) fn deserialize_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
502where
503    D: Deserializer<'de>,
504{
505    deserializer.deserialize_any(I64Visitor)
506}
507
508/// Deserializes a `number` leaf (`cast.ToFloat64E`).
509///
510/// # Errors
511///
512/// Returns an error for a value the Agent cannot cast to a number: a non-numeric string or a
513/// compound value.
514pub(crate) fn deserialize_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
515where
516    D: Deserializer<'de>,
517{
518    deserializer.deserialize_any(F64Visitor)
519}
520
521/// Deserializes a `string` leaf (`cast.ToStringE`).
522///
523/// # Errors
524///
525/// Returns an error for a compound value, the one shape the Agent cannot render as a string.
526pub(crate) fn deserialize_string<'de, D>(deserializer: D) -> Result<String, D::Error>
527where
528    D: Deserializer<'de>,
529{
530    deserializer.deserialize_any(StringVisitor)
531}
532
533/// Deserializes a map, or a string holding a JSON-encoded map (`cast.ToStringMap*E`).
534///
535/// The Agent stores a map-typed environment variable as its raw string and decodes the JSON when the
536/// key is read, so its configuration stream carries the string.
537///
538/// A null is an empty map: the Agent's configuration stream can carry `null` for an empty or
539/// cleared map, and its map accessors read it as empty.
540/// # Errors
541///
542/// Returns an error when the value is neither a map, a null, nor a string that decodes to a map, or
543/// when a map value does not deserialize as `T`.
544pub(crate) fn deserialize_map_or_json_string<'de, D, T>(deserializer: D) -> Result<T, D::Error>
545where
546    D: Deserializer<'de>,
547    T: DeserializeOwned,
548{
549    struct MapOrJsonString<T>(PhantomData<T>);
550
551    impl<'de, T: DeserializeOwned> Visitor<'de> for MapOrJsonString<T> {
552        type Value = T;
553
554        fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555            f.write_str("a map or a JSON-encoded map string")
556        }
557
558        fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
559            serde_json::from_str(value).map_err(|e| E::custom(format_args!("invalid JSON-encoded map: {e}")))
560        }
561
562        fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
563            T::deserialize(de::value::MapAccessDeserializer::new(map))
564        }
565
566        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
567            serde_json::from_value(serde_json::Value::Object(serde_json::Map::new())).map_err(E::custom)
568        }
569
570        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
571            serde_json::from_value(serde_json::Value::Object(serde_json::Map::new())).map_err(E::custom)
572        }
573    }
574
575    deserializer.deserialize_any(MapOrJsonString(PhantomData))
576}
577
578/// Deserializes a string map, coercing each value as the Agent does.
579///
580/// # Errors
581///
582/// Returns an error when the map is malformed (see [`deserialize_map_or_json_string`]) or a value is
583/// not scalar.
584pub(crate) fn deserialize_string_map<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
585where
586    D: Deserializer<'de>,
587{
588    let values = deserialize_map_or_json_string::<_, HashMap<String, serde_json::Value>>(deserializer)?;
589    values
590        .into_iter()
591        .map(|(key, value)| {
592            cast_to_string(&value)
593                .map(|value| (key, value))
594                .map_err(de::Error::custom)
595        })
596        .collect()
597}
598
599/// Deserializes a number map, coercing each value as the Agent does.
600///
601/// # Errors
602///
603/// Returns an error when a value cannot be cast to a number.
604pub(crate) fn deserialize_number_map<'de, D>(deserializer: D) -> Result<HashMap<String, f64>, D::Error>
605where
606    D: Deserializer<'de>,
607{
608    let values = deserialize_map_or_json_string::<_, HashMap<String, serde_json::Value>>(deserializer)?;
609    values
610        .into_iter()
611        .map(|(key, value)| cast_to_f64(&value).map(|value| (key, value)).map_err(de::Error::custom))
612        .collect()
613}
614
615/// Renders a JSON value as a `number` leaf (`cast.ToFloat64E`).
616///
617/// A witness method that receives a leaf as raw JSON, rather than as a generated field, renders
618/// it through this so that `1` and `"1.5"` read as the Agent reads them (`1.0` and `1.5`) instead
619/// of rejecting their JSON spelling.
620///
621/// # Errors
622///
623/// Returns an error for a value the Agent cannot cast to a number: a non-numeric string or a
624/// compound value.
625pub fn cast_to_f64(value: &::serde_json::Value) -> Result<f64, String> {
626    value.deserialize_any(F64Visitor).map_err(|e| e.to_string())
627}
628
629/// Renders a JSON value as a `string` leaf (`cast.ToStringE`).
630///
631/// A witness method that receives a leaf as raw JSON, rather than as a generated field, renders it
632/// through this so that `1.0` and a null read as the Agent reads them (`"1"` and `""`) instead of as
633/// their JSON spelling.
634///
635/// # Errors
636///
637/// Returns an error for a compound value, the one shape the Agent cannot render as a string.
638pub fn cast_to_string(value: &::serde_json::Value) -> Result<String, String> {
639    value.deserialize_any(StringVisitor).map_err(|e| e.to_string())
640}
641
642/// Deserializes an optional `string` leaf, where an absent or null value stays `None`.
643///
644/// # Errors
645///
646/// Same as [`deserialize_string`] for a present value.
647pub(crate) fn deserialize_optional_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
648where
649    D: Deserializer<'de>,
650{
651    deserializer.deserialize_option(OptionalStringVisitor)
652}
653
654/// Deserializes an optional `integer` leaf, where an absent or null value stays `None`.
655///
656/// # Errors
657///
658/// Same as [`deserialize_i64`] for a present value.
659pub(crate) fn deserialize_optional_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
660where
661    D: Deserializer<'de>,
662{
663    deserializer.deserialize_option(OptionalI64Visitor)
664}
665
666struct BoolVisitor;
667
668impl Visitor<'_> for BoolVisitor {
669    type Value = bool;
670
671    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
672        f.write_str("a boolean, a boolean string, or a number")
673    }
674
675    fn visit_bool<E: de::Error>(self, value: bool) -> Result<bool, E> {
676        Ok(value)
677    }
678
679    fn visit_i64<E: de::Error>(self, value: i64) -> Result<bool, E> {
680        Ok(value != 0)
681    }
682
683    fn visit_u64<E: de::Error>(self, value: u64) -> Result<bool, E> {
684        Ok(value != 0)
685    }
686
687    fn visit_f64<E: de::Error>(self, value: f64) -> Result<bool, E> {
688        Ok(value != 0.0)
689    }
690
691    fn visit_str<E: de::Error>(self, value: &str) -> Result<bool, E> {
692        parse_bool(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
693    }
694
695    // `cast` maps a nil to the zero value, which for a configuration leaf means an explicit
696    // `key: null` reads as if the key were unset.
697    fn visit_unit<E: de::Error>(self) -> Result<bool, E> {
698        Ok(false)
699    }
700}
701
702struct I64Visitor;
703
704impl Visitor<'_> for I64Visitor {
705    type Value = i64;
706
707    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708        f.write_str("an integer, a numeric string, or a boolean")
709    }
710
711    fn visit_bool<E: de::Error>(self, value: bool) -> Result<i64, E> {
712        Ok(i64::from(value))
713    }
714
715    fn visit_i64<E: de::Error>(self, value: i64) -> Result<i64, E> {
716        Ok(value)
717    }
718
719    fn visit_u64<E: de::Error>(self, value: u64) -> Result<i64, E> {
720        i64::try_from(value).map_err(|_| E::invalid_value(Unexpected::Unsigned(value), &self))
721    }
722
723    // Go's `int(float64)` truncation, which is what the Agent reads for a leaf written `10.5`.
724    fn visit_f64<E: de::Error>(self, value: f64) -> Result<i64, E> {
725        if !value.is_finite() || value < i64::MIN as f64 || value > i64::MAX as f64 {
726            return Err(E::invalid_value(Unexpected::Float(value), &self));
727        }
728        Ok(value.trunc() as i64)
729    }
730
731    fn visit_str<E: de::Error>(self, value: &str) -> Result<i64, E> {
732        parse_i64(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
733    }
734
735    fn visit_unit<E: de::Error>(self) -> Result<i64, E> {
736        Ok(0)
737    }
738}
739
740struct F64Visitor;
741
742impl Visitor<'_> for F64Visitor {
743    type Value = f64;
744
745    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746        f.write_str("a number, a numeric string, or a boolean")
747    }
748
749    fn visit_bool<E: de::Error>(self, value: bool) -> Result<f64, E> {
750        Ok(if value { 1.0 } else { 0.0 })
751    }
752
753    fn visit_i64<E: de::Error>(self, value: i64) -> Result<f64, E> {
754        Ok(value as f64)
755    }
756
757    fn visit_u64<E: de::Error>(self, value: u64) -> Result<f64, E> {
758        Ok(value as f64)
759    }
760
761    fn visit_f64<E: de::Error>(self, value: f64) -> Result<f64, E> {
762        Ok(value)
763    }
764
765    fn visit_str<E: de::Error>(self, value: &str) -> Result<f64, E> {
766        parse_f64(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
767    }
768
769    fn visit_unit<E: de::Error>(self) -> Result<f64, E> {
770        Ok(0.0)
771    }
772}
773
774struct StringVisitor;
775
776impl Visitor<'_> for StringVisitor {
777    type Value = String;
778
779    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780        f.write_str("a string, a boolean, or a number")
781    }
782
783    fn visit_bool<E: de::Error>(self, value: bool) -> Result<String, E> {
784        Ok(value.to_string())
785    }
786
787    fn visit_i64<E: de::Error>(self, value: i64) -> Result<String, E> {
788        Ok(value.to_string())
789    }
790
791    fn visit_u64<E: de::Error>(self, value: u64) -> Result<String, E> {
792        Ok(value.to_string())
793    }
794
795    // Rust's `Display` for `f64` matches Go's `FormatFloat(v, 'f', -1, 64)`: the shortest form that
796    // round-trips, never in exponent notation.
797    fn visit_f64<E: de::Error>(self, value: f64) -> Result<String, E> {
798        Ok(value.to_string())
799    }
800
801    fn visit_str<E: de::Error>(self, value: &str) -> Result<String, E> {
802        Ok(value.to_owned())
803    }
804
805    fn visit_string<E: de::Error>(self, value: String) -> Result<String, E> {
806        Ok(value)
807    }
808
809    fn visit_unit<E: de::Error>(self) -> Result<String, E> {
810        Ok(String::new())
811    }
812}
813
814struct OptionalStringVisitor;
815
816impl<'de> Visitor<'de> for OptionalStringVisitor {
817    type Value = Option<String>;
818
819    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
820        f.write_str("a string, a boolean, a number, or null")
821    }
822
823    fn visit_none<E: de::Error>(self) -> Result<Option<String>, E> {
824        Ok(None)
825    }
826
827    fn visit_unit<E: de::Error>(self) -> Result<Option<String>, E> {
828        Ok(None)
829    }
830
831    fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Option<String>, D::Error> {
832        deserializer.deserialize_any(StringVisitor).map(Some)
833    }
834}
835
836struct OptionalI64Visitor;
837
838impl<'de> Visitor<'de> for OptionalI64Visitor {
839    type Value = Option<i64>;
840
841    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
842        f.write_str("an integer, a numeric string, a boolean, or null")
843    }
844
845    fn visit_none<E: de::Error>(self) -> Result<Option<i64>, E> {
846        Ok(None)
847    }
848
849    fn visit_unit<E: de::Error>(self) -> Result<Option<i64>, E> {
850        Ok(None)
851    }
852
853    fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Option<i64>, D::Error> {
854        deserializer.deserialize_any(I64Visitor).map(Some)
855    }
856}
857
858/// Strings with the list that Go's `cast.ToStringSliceE` returns for each.
859#[cfg(test)]
860pub(crate) const STRING_LIST_CASES: &[(&str, &[&str])] = &[
861    (r#"["cr-a","cr-b"]"#, &["cr-a", "cr-b"]),
862    (r#"["client_id", "product_id"]"#, &["client_id", "product_id"]),
863    (r#" ["a b","c"] "#, &["a b", "c"]),
864    (r#"["a",null]"#, &["a", ""]),
865    ("[null]", &[""]),
866    ("null", &[]),
867    ("[]", &[]),
868    ("", &[]),
869    ("   ", &[]),
870    ("a b", &["a", "b"]),
871    ("a,b", &["a,b"]),
872    ("cr-a cr-b,cr-c", &["cr-a", "cr-b,cr-c"]),
873    ("[a,b]", &["[a,b]"]),
874    (r#"["a",1]"#, &[r#"["a",1]"#]),
875    (r#"{"a":1}"#, &[r#"{"a":1}"#]),
876    (r#""abc""#, &[r#""abc""#]),
877    (r#"["a"] x"#, &[r#"["a"]"#, "x"]),
878    ("a\tb\nc", &["a", "b", "c"]),
879];
880
881#[cfg(test)]
882mod tests {
883    use serde::Deserialize;
884    use serde_json::{json, Value};
885
886    use super::*;
887
888    #[derive(Deserialize)]
889    struct Bool(#[serde(deserialize_with = "deserialize_bool")] bool);
890
891    #[derive(Deserialize)]
892    struct Int(#[serde(deserialize_with = "deserialize_i64")] i64);
893
894    #[derive(Deserialize)]
895    struct Float(#[serde(deserialize_with = "deserialize_f64")] f64);
896
897    #[derive(Deserialize)]
898    struct Str(#[serde(deserialize_with = "deserialize_string")] String);
899
900    #[derive(Deserialize)]
901    struct OptStr(#[serde(deserialize_with = "deserialize_optional_string")] Option<String>);
902
903    #[derive(Deserialize)]
904    struct StringMap(#[serde(deserialize_with = "deserialize_string_map")] HashMap<String, String>);
905
906    #[derive(Deserialize)]
907    struct NumberMap(#[serde(deserialize_with = "deserialize_number_map")] HashMap<String, f64>);
908
909    #[derive(Deserialize)]
910    struct FreeFormMap(
911        #[serde(deserialize_with = "deserialize_map_or_json_string")] ::serde_json::Map<String, ::serde_json::Value>,
912    );
913
914    fn as_bool(value: Value) -> Result<bool, String> {
915        serde_json::from_value::<Bool>(value)
916            .map(|b| b.0)
917            .map_err(|e| e.to_string())
918    }
919
920    fn as_int(value: Value) -> Result<i64, String> {
921        serde_json::from_value::<Int>(value)
922            .map(|i| i.0)
923            .map_err(|e| e.to_string())
924    }
925
926    fn as_float(value: Value) -> Result<f64, String> {
927        serde_json::from_value::<Float>(value)
928            .map(|f| f.0)
929            .map_err(|e| e.to_string())
930    }
931
932    fn as_string(value: Value) -> Result<String, String> {
933        serde_json::from_value::<Str>(value)
934            .map(|s| s.0)
935            .map_err(|e| e.to_string())
936    }
937
938    #[test]
939    fn bool_accepts_every_spelling_go_accepts() {
940        for truthy in [
941            json!(true),
942            json!("true"),
943            json!("True"),
944            json!("TRUE"),
945            json!("t"),
946            json!("T"),
947            json!("1"),
948        ] {
949            assert_eq!(as_bool(truthy.clone()), Ok(true), "{truthy}");
950        }
951        for falsy in [
952            json!(false),
953            json!("false"),
954            json!("False"),
955            json!("FALSE"),
956            json!("f"),
957            json!("F"),
958            json!("0"),
959        ] {
960            assert_eq!(as_bool(falsy.clone()), Ok(false), "{falsy}");
961        }
962
963        // Any non-zero number is truthy, and a null reads as the zero value.
964        assert_eq!(as_bool(json!(2)), Ok(true));
965        assert_eq!(as_bool(json!(-1)), Ok(true));
966        assert_eq!(as_bool(json!(1.0)), Ok(true));
967        assert_eq!(as_bool(json!(0.0)), Ok(false));
968        assert_eq!(as_bool(json!(null)), Ok(false));
969    }
970
971    #[test]
972    fn bool_rejects_what_go_rejects() {
973        // `strconv.ParseBool` accepts none of these.
974        for rejected in [json!("yes"), json!("on"), json!(""), json!([true]), json!({"a": true})] {
975            assert!(as_bool(rejected.clone()).is_err(), "{rejected}");
976        }
977    }
978
979    #[test]
980    fn integer_accepts_numeric_strings_floats_and_booleans() {
981        assert_eq!(as_int(json!(8125)), Ok(8125));
982        assert_eq!(as_int(json!("8125")), Ok(8125));
983        assert_eq!(as_int(json!(" -7 ")), Ok(-7));
984        assert_eq!(as_int(json!(true)), Ok(1));
985        assert_eq!(as_int(json!(null)), Ok(0));
986
987        // `cast` drops an all-zero fraction from a numeric string.
988        assert_eq!(as_int(json!("8125.0")), Ok(8125));
989        assert_eq!(as_int(json!("8125.000")), Ok(8125));
990        assert_eq!(as_int(json!("-8125.0")), Ok(-8125));
991
992        // Go truncates toward zero rather than rounding.
993        assert_eq!(as_int(json!(10.9)), Ok(10));
994        assert_eq!(as_int(json!(-10.9)), Ok(-10));
995    }
996
997    #[test]
998    fn integer_rejects_unparseable_and_out_of_range_values() {
999        for rejected in [
1000            json!("8125ms"),
1001            json!(""),
1002            json!("8125.5"),
1003            json!("8125."),
1004            json!(1e300),
1005            json!(["8125"]),
1006        ] {
1007            assert!(as_int(rejected.clone()).is_err(), "{rejected}");
1008        }
1009    }
1010
1011    // Each pair is the result of Go's `cast.ToInt64E` (DataDog/cast v1.8.0) for that string.
1012    #[test]
1013    fn integer_string_accepts_exactly_the_go_spellings() {
1014        let accepted: &[(&str, i64)] = &[
1015            ("0x10", 16),
1016            ("0X1F", 31),
1017            ("010", 8),
1018            ("0010", 8),
1019            ("0b11", 3),
1020            ("0B1", 1),
1021            ("0o17", 15),
1022            ("0O7", 7),
1023            ("1_000", 1000),
1024            ("0x_10", 16),
1025            ("0_7", 7),
1026            ("+5", 5),
1027            ("+0x1", 1),
1028            ("-0x10", -16),
1029            ("00", 0),
1030            ("-0", 0),
1031            ("0.0", 0),
1032            ("0x10.0", 16),
1033            ("10.00", 10),
1034            ("-9223372036854775808", i64::MIN),
1035            ("-0x8000000000000000", i64::MIN),
1036            ("9223372036854775807", i64::MAX),
1037        ];
1038        for (raw, expected) in accepted {
1039            assert_eq!(parse_i64(raw), Ok(*expected), "{raw}");
1040            assert_eq!(as_int(json!(raw)), Ok(*expected), "{raw} through the leaf deserializer");
1041        }
1042    }
1043
1044    // Go rejects each of these, but the Agent's integer getter reads it as 0 and the Agent starts.
1045    #[test]
1046    fn integer_string_with_an_invalid_octal_digit_reads_as_zero() {
1047        for raw in [
1048            "08", "09", "-09", "+08", "0128", "00009", "08.0", "08.00", "-08.0", " 08 ",
1049        ] {
1050            assert_eq!(parse_i64(raw), Ok(0), "{raw}");
1051            assert_eq!(as_int(json!(raw)), Ok(0), "{raw} through the leaf deserializer");
1052        }
1053    }
1054
1055    // Go also rejects each of these. The Agent reads them as 0, but only the invalid octal digit family
1056    // above follows it here.
1057    #[test]
1058    fn integer_string_rejects_other_strings_go_rejects() {
1059        for raw in [
1060            "",
1061            "-",
1062            "08.",
1063            "08.5",
1064            "0_8",
1065            "08e1",
1066            "0x8g",
1067            "1__0",
1068            "_1",
1069            "1_",
1070            "0_",
1071            "0x1_",
1072            "0x",
1073            "0b",
1074            "0b2",
1075            "0xg",
1076            "10.50",
1077            "1e3",
1078            "5.",
1079            ".0",
1080            "1.0.0",
1081            "9223372036854775808",
1082            "0x8000000000000000",
1083            "-9223372036854775809",
1084            "+-5",
1085        ] {
1086            assert_eq!(parse_i64(raw), Err(format!("invalid integer `{raw}`")), "{raw}");
1087        }
1088    }
1089
1090    #[test]
1091    fn integer_string_trims_surrounding_whitespace_unlike_go() {
1092        assert_eq!(parse_i64(" 10 "), Ok(10));
1093        assert_eq!(parse_i64("\t0x10\n"), Ok(16));
1094    }
1095
1096    #[test]
1097    fn decimal_integer_keeps_the_saluki_only_grammar() {
1098        let accepted: &[(&str, i64)] = &[
1099            ("010", 10),
1100            ("08", 8),
1101            ("+5", 5),
1102            ("-7", -7),
1103            ("8125.0", 8125),
1104            (" 10 ", 10),
1105        ];
1106        for (raw, expected) in accepted {
1107            assert_eq!(parse_decimal_i64(raw), Ok(*expected), "{raw}");
1108        }
1109        for raw in ["0x10", "0b11", "1_000", "8125.5", "8125.", ""] {
1110            assert_eq!(parse_decimal_i64(raw), Err(format!("invalid integer `{raw}`")), "{raw}");
1111        }
1112    }
1113
1114    #[test]
1115    fn decimal_float_keeps_the_saluki_only_grammar() {
1116        for (raw, expected) in [("1.5", 1.5), ("1e3", 1000.0), (".5", 0.5), (" 2 ", 2.0)] {
1117            assert_eq!(parse_decimal_f64(raw), Ok(expected), "{raw}");
1118        }
1119        for raw in ["0x1p4", "1_000.5", ""] {
1120            assert_eq!(parse_decimal_f64(raw), Err(format!("invalid number `{raw}`")), "{raw}");
1121        }
1122        assert_eq!(parse_decimal_f64("inf"), Err("non-finite number `inf`".to_owned()));
1123    }
1124
1125    // Each pair is the result of Go's `cast.ToFloat64E` for that string.
1126    #[test]
1127    fn float_string_accepts_go_literals() {
1128        let accepted: &[(&str, f64)] = &[
1129            ("1.5", 1.5),
1130            ("+1.5", 1.5),
1131            ("-1.5", -1.5),
1132            (".5", 0.5),
1133            ("5.", 5.0),
1134            ("010", 10.0),
1135            ("1e3", 1000.0),
1136            ("1_000.5", 1000.5),
1137            ("1e5_0", 1e50),
1138            ("1e-400", 0.0),
1139            ("0x1p4", 16.0),
1140            ("0X1P4", 16.0),
1141            ("-0x1p4", -16.0),
1142            ("0x1.8p1", 3.0),
1143            ("0x.8p1", 1.0),
1144            ("0x1_0p0", 16.0),
1145            ("0x0p0", 0.0),
1146        ];
1147        for (raw, expected) in accepted {
1148            assert_eq!(parse_f64(raw), Ok(*expected), "{raw}");
1149            assert_eq!(
1150                as_float(json!(raw)),
1151                Ok(*expected),
1152                "{raw} through the leaf deserializer"
1153            );
1154        }
1155    }
1156
1157    #[test]
1158    fn float_string_rejects_what_go_or_json_rejects() {
1159        for raw in [
1160            "", ".", "e5", "0x10", "0x1p", "0x1p+", "1_e5", "1__0.5", "_1.5", "1.5_", "1e", "half", "inf", "nan",
1161            "+Inf", "1e400", "0x1p1024",
1162        ] {
1163            assert!(parse_f64(raw).is_err(), "{raw}");
1164        }
1165    }
1166
1167    // Go rounds hex floats to nearest even, so digits past the 64 bits of a `u64` still count.
1168    #[test]
1169    fn hex_float_rounds_like_go() {
1170        let cases: &[(&str, u64)] = &[
1171            ("0x1p-1074", 0x1),
1172            ("0x1p-1075", 0x0),
1173            ("-0x0p0", 0x8000_0000_0000_0000),
1174            ("0x1.fffffffffffffp1023", 0x7fef_ffff_ffff_ffff),
1175            ("0x123456789abcdef01p0", 0x43f2_3456_789a_bcdf),
1176            ("0x1.00000000000008p0", 0x3ff0_0000_0000_0000),
1177            ("0x1.00000000000018p0", 0x3ff0_0000_0000_0002),
1178            ("0x1.0000000000000800000001p0", 0x3ff0_0000_0000_0001),
1179            ("0x1.0000000000000800000p0", 0x3ff0_0000_0000_0000),
1180        ];
1181        for (raw, bits) in cases {
1182            assert_eq!(parse_f64(raw).map(f64::to_bits), Ok(*bits), "{raw}");
1183        }
1184    }
1185
1186    #[test]
1187    fn number_accepts_numeric_strings_integers_and_booleans() {
1188        assert_eq!(as_float(json!(1.5)), Ok(1.5));
1189        assert_eq!(as_float(json!("1.5")), Ok(1.5));
1190        assert_eq!(as_float(json!(2)), Ok(2.0));
1191        assert_eq!(as_float(json!(true)), Ok(1.0));
1192        assert_eq!(as_float(json!(null)), Ok(0.0));
1193        assert!(as_float(json!("half")).is_err());
1194    }
1195
1196    #[test]
1197    fn string_accepts_every_scalar() {
1198        assert_eq!(as_string(json!("datadog_only")), Ok("datadog_only".to_owned()));
1199        assert_eq!(as_string(json!(true)), Ok("true".to_owned()));
1200        assert_eq!(as_string(json!(false)), Ok("false".to_owned()));
1201        assert_eq!(as_string(json!(10485760)), Ok("10485760".to_owned()));
1202        assert_eq!(as_string(json!(-1)), Ok("-1".to_owned()));
1203        assert_eq!(as_string(json!(10.5)), Ok("10.5".to_owned()));
1204        assert_eq!(as_string(json!(null)), Ok(String::new()));
1205
1206        // A float with no fractional part renders without one, as Go's shortest form does.
1207        assert_eq!(as_string(json!(1.0)), Ok("1".to_owned()));
1208    }
1209
1210    #[test]
1211    fn string_rejects_compound_values() {
1212        for rejected in [json!(["a"]), json!({"a": "b"})] {
1213            assert!(as_string(rejected.clone()).is_err(), "{rejected}");
1214        }
1215    }
1216
1217    #[test]
1218    fn string_map_coerces_scalar_values() {
1219        let values = serde_json::from_value::<StringMap>(json!({
1220            "bool": true,
1221            "integer": 3,
1222            "null": null,
1223            "string": "datadog_only"
1224        }))
1225        .expect("scalar values deserialize")
1226        .0;
1227
1228        assert_eq!(values["bool"], "true");
1229        assert_eq!(values["integer"], "3");
1230        assert_eq!(values["null"], "");
1231        assert_eq!(values["string"], "datadog_only");
1232        assert!(serde_json::from_value::<StringMap>(json!({ "compound": [] })).is_err());
1233    }
1234
1235    #[test]
1236    fn string_map_accepts_a_json_encoded_string() {
1237        let values = serde_json::from_value::<StringMap>(json!(r#"{"bool": true, "string": "datadog_only"}"#))
1238            .expect("JSON-encoded map deserializes")
1239            .0;
1240
1241        assert_eq!(values["bool"], "true");
1242        assert_eq!(values["string"], "datadog_only");
1243        for rejected in [json!("not json"), json!(r#"["a"]"#), json!(r#"{"compound": []}"#)] {
1244            assert!(
1245                serde_json::from_value::<StringMap>(rejected.clone()).is_err(),
1246                "{rejected}"
1247            );
1248        }
1249    }
1250
1251    #[test]
1252    fn string_map_reads_null_as_empty() {
1253        for empty in [json!(null), json!({})] {
1254            let values = serde_json::from_value::<StringMap>(empty.clone())
1255                .expect("empty map deserializes")
1256                .0;
1257            assert!(values.is_empty(), "{empty}");
1258        }
1259    }
1260
1261    #[test]
1262    fn number_map_coerces_numeric_values() {
1263        let values = serde_json::from_value::<NumberMap>(json!({
1264            "integer": 3,
1265            "float": 0.8,
1266            "string": "0.25"
1267        }))
1268        .expect("numeric values deserialize")
1269        .0;
1270
1271        assert_eq!(values["integer"], 3.0);
1272        assert_eq!(values["float"], 0.8);
1273        assert_eq!(values["string"], 0.25);
1274        assert!(serde_json::from_value::<NumberMap>(json!({ "compound": [] })).is_err());
1275        assert!(serde_json::from_value::<NumberMap>(json!({ "text": "datadog_only" })).is_err());
1276    }
1277
1278    #[test]
1279    fn number_map_accepts_a_json_encoded_string() {
1280        let values = serde_json::from_value::<NumberMap>(json!(r#"{"one": 0.5, "two": 2}"#))
1281            .expect("JSON-encoded map deserializes")
1282            .0;
1283
1284        assert_eq!(values["one"], 0.5);
1285        assert_eq!(values["two"], 2.0);
1286        for rejected in [json!("not json"), json!(r#"["a"]"#), json!(r#"{"compound": []}"#)] {
1287            assert!(
1288                serde_json::from_value::<NumberMap>(rejected.clone()).is_err(),
1289                "{rejected}"
1290            );
1291        }
1292    }
1293
1294    #[test]
1295    fn free_form_map_accepts_a_json_encoded_string() {
1296        let values = serde_json::from_value::<FreeFormMap>(json!(r#"{"svc|op": 0.75}"#))
1297            .expect("JSON-encoded map deserializes")
1298            .0;
1299
1300        assert_eq!(values["svc|op"], json!(0.75));
1301        // The parsed form arrives from YAML-sourced settings and must keep working.
1302        let parsed = serde_json::from_value::<FreeFormMap>(json!({ "svc|op": 0.75 }))
1303            .expect("parsed map deserializes")
1304            .0;
1305        assert_eq!(parsed["svc|op"], json!(0.75));
1306        for rejected in [json!("not json"), json!(r#"["a"]"#), json!(5)] {
1307            assert!(
1308                serde_json::from_value::<FreeFormMap>(rejected.clone()).is_err(),
1309                "{rejected}"
1310            );
1311        }
1312    }
1313
1314    #[test]
1315    fn optional_string_distinguishes_null_from_a_coerced_scalar() {
1316        let absent = serde_json::from_value::<OptStr>(json!(null))
1317            .expect("null deserializes")
1318            .0;
1319        assert_eq!(absent, None);
1320
1321        let coerced = serde_json::from_value::<OptStr>(json!(3))
1322            .expect("scalar deserializes")
1323            .0;
1324        assert_eq!(coerced, Some("3".to_owned()));
1325    }
1326
1327    #[test]
1328    fn string_list_follows_the_go_slice_cast() {
1329        for (raw, expected) in STRING_LIST_CASES {
1330            assert_eq!(parse_string_list(raw), *expected, "{raw:?}");
1331        }
1332    }
1333}