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 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}