datadog_agent_remote_config/
json.rs

1//! Decoding support for products whose payloads are JSON.
2
3use std::fmt;
4
5use serde::de::DeserializeOwned;
6
7use crate::ApplyError;
8
9/// Deserializes a JSON payload, for a [`decode`](crate::ProductDecoder::decode) implementation to call.
10///
11/// Payloads are opaque bytes and need not be JSON, so decoding is left to each product. This helper serves the
12/// products whose payloads are. It takes no configuration ID: the client attributes a `decode` error to the
13/// configuration being decoded.
14///
15/// # Errors
16///
17/// Returns [`JsonError`] if `payload` is not valid JSON or does not match the shape of `T`.
18///
19/// # Examples
20///
21/// ```
22/// use datadog_agent_remote_config::{decode_json, ConfigId, JsonError, ProductDecoder};
23/// use serde::Deserialize;
24///
25/// #[derive(Deserialize)]
26/// struct Limits {
27///     max_spans: u32,
28/// }
29///
30/// #[derive(Default)]
31/// struct LimitsDecoder {
32///     limits: Option<Limits>,
33/// }
34///
35/// impl ProductDecoder for LimitsDecoder {
36///     const PRODUCT: &'static str = "EXAMPLE_LIMITS";
37///
38///     type Snapshot = Option<Limits>;
39///     type Error = JsonError;
40///
41///     fn decode(&mut self, _id: &ConfigId, payload: &[u8]) -> Result<(), Self::Error> {
42///         self.limits = Some(decode_json(payload)?);
43///         Ok(())
44///     }
45///
46///     fn build(self) -> Result<Self::Snapshot, Self::Error> {
47///         Ok(self.limits)
48///     }
49/// }
50///
51/// let mut decoder = LimitsDecoder::default();
52/// decoder.decode(&ConfigId::new("limits.v1"), br#"{"max_spans": 100}"#).unwrap();
53/// assert!(decoder.decode(&ConfigId::new("limits.v2"), b"{").is_err());
54/// assert_eq!(decoder.build().unwrap().unwrap().max_spans, 100);
55/// ```
56pub fn decode_json<T>(payload: &[u8]) -> Result<T, JsonError>
57where
58    T: DeserializeOwned,
59{
60    serde_json::from_slice(payload).map_err(JsonError)
61}
62
63/// A payload that [`decode_json`] could not deserialize.
64///
65/// The message gives the position of the failure and nothing of the payload's contents, so it is safe to report to the
66/// Agent. The underlying [`serde_json::Error`], which may quote payload values, is available as the error's
67/// [`source`](std::error::Error::source) for local diagnostics.
68#[derive(Debug)]
69pub struct JsonError(serde_json::Error);
70
71impl fmt::Display for JsonError {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(
74            f,
75            "Payload is malformed JSON at line {}, column {}.",
76            self.0.line(),
77            self.0.column()
78        )
79    }
80}
81
82impl std::error::Error for JsonError {
83    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
84        Some(&self.0)
85    }
86}
87
88impl ApplyError for JsonError {
89    fn apply_error(&self) -> String {
90        self.to_string()
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use std::error::Error as _;
97
98    use serde::Deserialize;
99
100    use crate::{decode_json, ApplyError};
101
102    #[derive(Debug, Deserialize, PartialEq)]
103    struct Limits {
104        max_spans: u32,
105    }
106
107    #[test]
108    fn decodes_a_json_payload() {
109        let limits: Limits = decode_json(br#"{"max_spans": 100}"#).unwrap();
110
111        assert_eq!(limits, Limits { max_spans: 100 });
112    }
113
114    #[test]
115    fn reports_malformed_json_without_its_contents() {
116        for (payload, expected) in [
117            (br#"{"max_spans": 1"#.as_slice(), "line 1, column 15"),
118            (br#"{"max_spans": "hunter2"}"#.as_slice(), "line 1, column 23"),
119        ] {
120            let error = decode_json::<Limits>(payload).unwrap_err();
121
122            assert_eq!(error.to_string(), format!("Payload is malformed JSON at {expected}."));
123            assert_eq!(error.apply_error(), error.to_string());
124            assert!(!error.to_string().contains("hunter2"));
125            let source = error.source().expect("the serde_json error is the source");
126            assert!(source.is::<serde_json::Error>());
127        }
128    }
129}