Skip to main content

plugx_input/
serde.rs

1use crate::{
2    Input,
3    error::{EXPECTED_INPUT_TYPES, InputDeserializeError, i128_to_isize, invalid_type},
4    position::InputPath,
5};
6use serde::{
7    Deserializer, Serializer,
8    de::{self, Deserialize, DeserializeSeed, MapAccess, SeqAccess, Visitor},
9    ser::{Serialize, SerializeMap, SerializeSeq},
10};
11use std::{collections::HashMap, fmt};
12
13impl Input {
14    /// Serializes `self`. This cannot fail for any supported [`Serializer`].
15    pub fn serialize<S: Serializer>(&self, serializer: S) -> S::Ok {
16        serialize_infallible(self, serializer)
17    }
18}
19
20impl Serialize for Input {
21    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
22        Ok(serialize_infallible(self, serializer))
23    }
24}
25
26fn serialize_infallible<S: Serializer>(input: &Input, serializer: S) -> S::Ok {
27    match input {
28        Input::Bool(value) => serializer.serialize_bool(*value).unwrap_infallible(),
29        Input::Int(value) => serializer.serialize_i64(*value as i64).unwrap_infallible(),
30        Input::Float(value) => serializer.serialize_f64(*value).unwrap_infallible(),
31        Input::Str(value) => serializer.serialize_str(value).unwrap_infallible(),
32        Input::List(values) => {
33            let mut seq = serializer
34                .serialize_seq(Some(values.len()))
35                .unwrap_infallible();
36            for value in values {
37                seq.serialize_element(value).unwrap_infallible();
38            }
39            seq.end().unwrap_infallible()
40        }
41        Input::Map(values) => {
42            let mut map = serializer
43                .serialize_map(Some(values.len()))
44                .unwrap_infallible();
45            for (key, value) in values {
46                map.serialize_key(key).unwrap_infallible();
47                map.serialize_value(value).unwrap_infallible();
48            }
49            map.end().unwrap_infallible()
50        }
51    }
52}
53
54trait UnwrapInfallible<T> {
55    fn unwrap_infallible(self) -> T;
56}
57
58impl<T, E: fmt::Debug> UnwrapInfallible<T> for Result<T, E> {
59    fn unwrap_infallible(self) -> T {
60        self.unwrap_or_else(|error| panic!("Input serialization cannot fail: {error:?}"))
61    }
62}
63
64impl<'de> Deserialize<'de> for Input {
65    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
66        deserializer.deserialize_any(InputSeed {
67            path: InputPath::root(),
68        })
69    }
70}
71
72struct InputSeed {
73    path: InputPath,
74}
75
76impl Clone for InputSeed {
77    fn clone(&self) -> Self {
78        Self {
79            path: self.path.clone(),
80        }
81    }
82}
83
84impl<'de> DeserializeSeed<'de> for InputSeed {
85    type Value = Input;
86
87    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
88        deserializer.deserialize_any(self)
89    }
90}
91
92impl InputSeed {
93    fn invalid_type<E: de::Error>(&self, found: impl Into<String>) -> E {
94        E::custom(invalid_type(self.path.clone(), found).to_string())
95    }
96
97    fn integer_out_of_range<E: de::Error>(&self, value: i128) -> E {
98        E::custom(
99            InputDeserializeError::IntegerOutOfRange {
100                path: self.path.clone(),
101                value,
102            }
103            .to_string(),
104        )
105    }
106
107    fn int_from_i128<E: de::Error>(&self, value: i128) -> Result<Input, E> {
108        match i128_to_isize(value) {
109            Ok(value) => Ok(Input::Int(value)),
110            Err(()) => Err(self.integer_out_of_range(value)),
111        }
112    }
113}
114
115impl<'de> Visitor<'de> for InputSeed {
116    type Value = Input;
117
118    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(formatter, "a {EXPECTED_INPUT_TYPES}")
120    }
121
122    fn visit_bool<E: de::Error>(self, value: bool) -> Result<Self::Value, E> {
123        Ok(Input::Bool(value))
124    }
125
126    fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
127        self.int_from_i128(value as i128)
128    }
129
130    fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
131        self.int_from_i128(value as i128)
132    }
133
134    fn visit_i128<E: de::Error>(self, value: i128) -> Result<Self::Value, E> {
135        self.int_from_i128(value)
136    }
137
138    fn visit_u128<E: de::Error>(self, value: u128) -> Result<Self::Value, E> {
139        self.int_from_i128(value as i128)
140    }
141
142    fn visit_f32<E: de::Error>(self, value: f32) -> Result<Self::Value, E> {
143        Ok(Input::Float(value as f64))
144    }
145
146    fn visit_f64<E: de::Error>(self, value: f64) -> Result<Self::Value, E> {
147        Ok(Input::Float(value))
148    }
149
150    fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
151        Ok(Input::Str(value.to_owned()))
152    }
153
154    fn visit_string<E: de::Error>(self, value: String) -> Result<Self::Value, E> {
155        Ok(Input::Str(value))
156    }
157
158    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
159        let mut values = Vec::new();
160        let mut index = 0;
161        while let Some(value) = seq.next_element_seed(InputSeed {
162            path: self.path.clone().with_index(index),
163        })? {
164            values.push(value);
165            index += 1;
166        }
167        Ok(Input::List(values))
168    }
169
170    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
171        let mut values = HashMap::new();
172        while let Some(key) = map.next_key::<String>()? {
173            let value = map.next_value_seed(InputSeed {
174                path: self.path.clone().with_key(&key),
175            })?;
176            values.insert(key, value);
177        }
178        Ok(Input::Map(values))
179    }
180
181    fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
182        Err(self.invalid_type("null"))
183    }
184
185    fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
186        Err(self.invalid_type("null"))
187    }
188
189    fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
190        deserializer.deserialize_any(self)
191    }
192
193    fn visit_newtype_struct<D: Deserializer<'de>>(
194        self,
195        deserializer: D,
196    ) -> Result<Self::Value, D::Error> {
197        deserializer.deserialize_any(self)
198    }
199
200    fn visit_enum<A: de::EnumAccess<'de>>(self, _data: A) -> Result<Self::Value, A::Error> {
201        Err(self.invalid_type("enum"))
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use crate::error::InputSerializeError;
209    use serde_json::{Value, json};
210
211    #[test]
212    fn serialize_is_infallible() {
213        let input = Input::Map(HashMap::from([
214            ("list".to_string(), Input::from([1, 2, 3])),
215            ("flag".to_string(), Input::from(true)),
216        ]));
217        let _: Result<Value, InputSerializeError> =
218            Ok(input.serialize(serde_json::value::Serializer));
219    }
220
221    #[test]
222    fn rejects_null_with_clean_error() {
223        let error = serde_json::from_str::<Input>("null").unwrap_err();
224        assert!(
225            error
226                .to_string()
227                .starts_with("expected boolean, integer, float, string, list, or map, found null")
228        );
229    }
230
231    #[test]
232    fn rejects_invalid_type_at_path() {
233        let error = serde_json::from_str::<Input>(r#"{"foo": null}"#).unwrap_err();
234        assert!(error.to_string().starts_with(
235            "[foo]: expected boolean, integer, float, string, list, or map, found null"
236        ));
237    }
238
239    #[test]
240    fn rejects_out_of_range_integer_at_path() {
241        let value = if cfg!(target_pointer_width = "64") {
242            json!(i64::MAX as i128 + 1)
243        } else {
244            json!(i64::MAX)
245        };
246        let json = format!(r#"{{"count": {value}}}"#);
247        let error = serde_json::from_str::<Input>(&json).unwrap_err();
248        assert!(error.to_string().starts_with(&format!(
249            "[count]: integer {value} is out of range for isize"
250        )));
251    }
252
253    #[test]
254    fn roundtrip() {
255        let input = Input::from(HashMap::from([
256            ("name".to_string(), Input::from("plugx")),
257            ("enabled".to_string(), Input::from(true)),
258            ("count".to_string(), Input::from(3)),
259            ("ratio".to_string(), Input::from(0.5)),
260        ]));
261        let json = serde_json::to_string(&input).unwrap();
262        let decoded: Input = serde_json::from_str(&json).unwrap();
263        assert_eq!(input, decoded);
264    }
265
266    #[test]
267    fn deep_json_roundtrip() {
268        let json = r#"{
269            "app": {
270                "server": {
271                    "host": "localhost",
272                    "port": 8080,
273                    "routes": [
274                        {
275                            "path": "/api",
276                            "handler": {
277                                "type": "proxy",
278                                "target": {
279                                    "url": "http://example.com",
280                                    "headers": ["accept", "authorization"]
281                                }
282                            }
283                        }
284                    ]
285                },
286                "logging": {
287                    "level": "debug",
288                    "filters": {
289                        "modules": {
290                            "plugx": {
291                                "input": {
292                                    "serde": true
293                                }
294                            }
295                        }
296                    }
297                }
298            }
299        }"#;
300
301        let decoded: Input = serde_json::from_str(json).unwrap();
302        let encoded = serde_json::to_string(&decoded).unwrap();
303        let roundtripped: Input = serde_json::from_str(&encoded).unwrap();
304        assert_eq!(decoded, roundtripped);
305
306        assert_eq!(
307            decoded
308                .as_map()
309                .unwrap()
310                .get("app")
311                .unwrap()
312                .as_map()
313                .unwrap()
314                .get("logging")
315                .unwrap()
316                .as_map()
317                .unwrap()
318                .get("filters")
319                .unwrap()
320                .as_map()
321                .unwrap()
322                .get("modules")
323                .unwrap()
324                .as_map()
325                .unwrap()
326                .get("plugx")
327                .unwrap()
328                .as_map()
329                .unwrap()
330                .get("input")
331                .unwrap()
332                .as_map()
333                .unwrap()
334                .get("serde")
335                .unwrap(),
336            &Input::Bool(true)
337        );
338    }
339
340    #[test]
341    fn rejects_invalid_type_at_deep_path() {
342        let error = serde_json::from_str::<Input>(
343            r#"{"app": {"logging": {"filters": {"modules": {"plugx": null}}}}}"#,
344        )
345        .unwrap_err();
346        assert!(
347            error.to_string().starts_with(
348                "[app][logging][filters][modules][plugx]: expected boolean, integer, float, string, list, or map, found null"
349            )
350        );
351    }
352}