1use std::{collections::HashMap, fmt};
21
22use serde::de::{self, Deserializer, Unexpected, Visitor};
23use serde::Deserialize;
24
25pub(crate) fn parse_bool(raw: &str) -> Result<bool, String> {
31 match raw {
32 "1" | "t" | "T" | "TRUE" | "true" | "True" => Ok(true),
33 "0" | "f" | "F" | "FALSE" | "false" | "False" => Ok(false),
34 other => Err(format!("invalid boolean `{other}`")),
35 }
36}
37
38pub(crate) fn parse_i64(raw: &str) -> Result<i64, String> {
44 trim_zero_decimal(raw.trim())
45 .parse::<i64>()
46 .map_err(|_| format!("invalid integer `{raw}`"))
47}
48
49fn trim_zero_decimal(raw: &str) -> &str {
52 match raw.split_once('.') {
53 Some((integer, fraction)) if !fraction.is_empty() && fraction.bytes().all(|byte| byte == b'0') => integer,
54 _ => raw,
55 }
56}
57
58pub(crate) fn parse_f64(raw: &str) -> Result<f64, String> {
64 let parsed: f64 = raw.trim().parse().map_err(|_| format!("invalid number `{raw}`"))?;
65 if !parsed.is_finite() {
66 return Err(format!("non-finite number `{raw}`"));
67 }
68 Ok(parsed)
69}
70
71pub(crate) fn deserialize_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
78where
79 D: Deserializer<'de>,
80{
81 deserializer.deserialize_any(BoolVisitor)
82}
83
84pub(crate) fn deserialize_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
91where
92 D: Deserializer<'de>,
93{
94 deserializer.deserialize_any(I64Visitor)
95}
96
97pub(crate) fn deserialize_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
104where
105 D: Deserializer<'de>,
106{
107 deserializer.deserialize_any(F64Visitor)
108}
109
110pub(crate) fn deserialize_string<'de, D>(deserializer: D) -> Result<String, D::Error>
116where
117 D: Deserializer<'de>,
118{
119 deserializer.deserialize_any(StringVisitor)
120}
121
122pub(crate) fn deserialize_string_map<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
128where
129 D: Deserializer<'de>,
130{
131 let values = HashMap::<String, serde_json::Value>::deserialize(deserializer)?;
132 values
133 .into_iter()
134 .map(|(key, value)| {
135 cast_to_string(&value)
136 .map(|value| (key, value))
137 .map_err(de::Error::custom)
138 })
139 .collect()
140}
141
142pub fn cast_to_string(value: &::serde_json::Value) -> Result<String, String> {
152 value.deserialize_any(StringVisitor).map_err(|e| e.to_string())
153}
154
155pub(crate) fn deserialize_optional_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
161where
162 D: Deserializer<'de>,
163{
164 deserializer.deserialize_option(OptionalStringVisitor)
165}
166
167pub(crate) fn deserialize_optional_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
173where
174 D: Deserializer<'de>,
175{
176 deserializer.deserialize_option(OptionalI64Visitor)
177}
178
179struct BoolVisitor;
180
181impl Visitor<'_> for BoolVisitor {
182 type Value = bool;
183
184 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 f.write_str("a boolean, a boolean string, or a number")
186 }
187
188 fn visit_bool<E: de::Error>(self, value: bool) -> Result<bool, E> {
189 Ok(value)
190 }
191
192 fn visit_i64<E: de::Error>(self, value: i64) -> Result<bool, E> {
193 Ok(value != 0)
194 }
195
196 fn visit_u64<E: de::Error>(self, value: u64) -> Result<bool, E> {
197 Ok(value != 0)
198 }
199
200 fn visit_f64<E: de::Error>(self, value: f64) -> Result<bool, E> {
201 Ok(value != 0.0)
202 }
203
204 fn visit_str<E: de::Error>(self, value: &str) -> Result<bool, E> {
205 parse_bool(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
206 }
207
208 fn visit_unit<E: de::Error>(self) -> Result<bool, E> {
211 Ok(false)
212 }
213}
214
215struct I64Visitor;
216
217impl Visitor<'_> for I64Visitor {
218 type Value = i64;
219
220 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 f.write_str("an integer, a numeric string, or a boolean")
222 }
223
224 fn visit_bool<E: de::Error>(self, value: bool) -> Result<i64, E> {
225 Ok(i64::from(value))
226 }
227
228 fn visit_i64<E: de::Error>(self, value: i64) -> Result<i64, E> {
229 Ok(value)
230 }
231
232 fn visit_u64<E: de::Error>(self, value: u64) -> Result<i64, E> {
233 i64::try_from(value).map_err(|_| E::invalid_value(Unexpected::Unsigned(value), &self))
234 }
235
236 fn visit_f64<E: de::Error>(self, value: f64) -> Result<i64, E> {
238 if !value.is_finite() || value < i64::MIN as f64 || value > i64::MAX as f64 {
239 return Err(E::invalid_value(Unexpected::Float(value), &self));
240 }
241 Ok(value.trunc() as i64)
242 }
243
244 fn visit_str<E: de::Error>(self, value: &str) -> Result<i64, E> {
245 parse_i64(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
246 }
247
248 fn visit_unit<E: de::Error>(self) -> Result<i64, E> {
249 Ok(0)
250 }
251}
252
253struct F64Visitor;
254
255impl Visitor<'_> for F64Visitor {
256 type Value = f64;
257
258 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 f.write_str("a number, a numeric string, or a boolean")
260 }
261
262 fn visit_bool<E: de::Error>(self, value: bool) -> Result<f64, E> {
263 Ok(if value { 1.0 } else { 0.0 })
264 }
265
266 fn visit_i64<E: de::Error>(self, value: i64) -> Result<f64, E> {
267 Ok(value as f64)
268 }
269
270 fn visit_u64<E: de::Error>(self, value: u64) -> Result<f64, E> {
271 Ok(value as f64)
272 }
273
274 fn visit_f64<E: de::Error>(self, value: f64) -> Result<f64, E> {
275 Ok(value)
276 }
277
278 fn visit_str<E: de::Error>(self, value: &str) -> Result<f64, E> {
279 parse_f64(value).map_err(|_| E::invalid_value(Unexpected::Str(value), &self))
280 }
281
282 fn visit_unit<E: de::Error>(self) -> Result<f64, E> {
283 Ok(0.0)
284 }
285}
286
287struct StringVisitor;
288
289impl Visitor<'_> for StringVisitor {
290 type Value = String;
291
292 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 f.write_str("a string, a boolean, or a number")
294 }
295
296 fn visit_bool<E: de::Error>(self, value: bool) -> Result<String, E> {
297 Ok(value.to_string())
298 }
299
300 fn visit_i64<E: de::Error>(self, value: i64) -> Result<String, E> {
301 Ok(value.to_string())
302 }
303
304 fn visit_u64<E: de::Error>(self, value: u64) -> Result<String, E> {
305 Ok(value.to_string())
306 }
307
308 fn visit_f64<E: de::Error>(self, value: f64) -> Result<String, E> {
311 Ok(value.to_string())
312 }
313
314 fn visit_str<E: de::Error>(self, value: &str) -> Result<String, E> {
315 Ok(value.to_owned())
316 }
317
318 fn visit_string<E: de::Error>(self, value: String) -> Result<String, E> {
319 Ok(value)
320 }
321
322 fn visit_unit<E: de::Error>(self) -> Result<String, E> {
323 Ok(String::new())
324 }
325}
326
327struct OptionalStringVisitor;
328
329impl<'de> Visitor<'de> for OptionalStringVisitor {
330 type Value = Option<String>;
331
332 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 f.write_str("a string, a boolean, a number, or null")
334 }
335
336 fn visit_none<E: de::Error>(self) -> Result<Option<String>, E> {
337 Ok(None)
338 }
339
340 fn visit_unit<E: de::Error>(self) -> Result<Option<String>, E> {
341 Ok(None)
342 }
343
344 fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Option<String>, D::Error> {
345 deserializer.deserialize_any(StringVisitor).map(Some)
346 }
347}
348
349struct OptionalI64Visitor;
350
351impl<'de> Visitor<'de> for OptionalI64Visitor {
352 type Value = Option<i64>;
353
354 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 f.write_str("an integer, a numeric string, a boolean, or null")
356 }
357
358 fn visit_none<E: de::Error>(self) -> Result<Option<i64>, E> {
359 Ok(None)
360 }
361
362 fn visit_unit<E: de::Error>(self) -> Result<Option<i64>, E> {
363 Ok(None)
364 }
365
366 fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Option<i64>, D::Error> {
367 deserializer.deserialize_any(I64Visitor).map(Some)
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use serde::Deserialize;
374 use serde_json::{json, Value};
375
376 use super::*;
377
378 #[derive(Deserialize)]
379 struct Bool(#[serde(deserialize_with = "deserialize_bool")] bool);
380
381 #[derive(Deserialize)]
382 struct Int(#[serde(deserialize_with = "deserialize_i64")] i64);
383
384 #[derive(Deserialize)]
385 struct Float(#[serde(deserialize_with = "deserialize_f64")] f64);
386
387 #[derive(Deserialize)]
388 struct Str(#[serde(deserialize_with = "deserialize_string")] String);
389
390 #[derive(Deserialize)]
391 struct OptStr(#[serde(deserialize_with = "deserialize_optional_string")] Option<String>);
392
393 #[derive(Deserialize)]
394 struct StringMap(#[serde(deserialize_with = "deserialize_string_map")] HashMap<String, String>);
395
396 fn as_bool(value: Value) -> Result<bool, String> {
397 serde_json::from_value::<Bool>(value)
398 .map(|b| b.0)
399 .map_err(|e| e.to_string())
400 }
401
402 fn as_int(value: Value) -> Result<i64, String> {
403 serde_json::from_value::<Int>(value)
404 .map(|i| i.0)
405 .map_err(|e| e.to_string())
406 }
407
408 fn as_float(value: Value) -> Result<f64, String> {
409 serde_json::from_value::<Float>(value)
410 .map(|f| f.0)
411 .map_err(|e| e.to_string())
412 }
413
414 fn as_string(value: Value) -> Result<String, String> {
415 serde_json::from_value::<Str>(value)
416 .map(|s| s.0)
417 .map_err(|e| e.to_string())
418 }
419
420 #[test]
421 fn bool_accepts_every_spelling_go_accepts() {
422 for truthy in [
423 json!(true),
424 json!("true"),
425 json!("True"),
426 json!("TRUE"),
427 json!("t"),
428 json!("T"),
429 json!("1"),
430 ] {
431 assert_eq!(as_bool(truthy.clone()), Ok(true), "{truthy}");
432 }
433 for falsy in [
434 json!(false),
435 json!("false"),
436 json!("False"),
437 json!("FALSE"),
438 json!("f"),
439 json!("F"),
440 json!("0"),
441 ] {
442 assert_eq!(as_bool(falsy.clone()), Ok(false), "{falsy}");
443 }
444
445 assert_eq!(as_bool(json!(2)), Ok(true));
447 assert_eq!(as_bool(json!(-1)), Ok(true));
448 assert_eq!(as_bool(json!(1.0)), Ok(true));
449 assert_eq!(as_bool(json!(0.0)), Ok(false));
450 assert_eq!(as_bool(json!(null)), Ok(false));
451 }
452
453 #[test]
454 fn bool_rejects_what_go_rejects() {
455 for rejected in [json!("yes"), json!("on"), json!(""), json!([true]), json!({"a": true})] {
457 assert!(as_bool(rejected.clone()).is_err(), "{rejected}");
458 }
459 }
460
461 #[test]
462 fn integer_accepts_numeric_strings_floats_and_booleans() {
463 assert_eq!(as_int(json!(8125)), Ok(8125));
464 assert_eq!(as_int(json!("8125")), Ok(8125));
465 assert_eq!(as_int(json!(" -7 ")), Ok(-7));
466 assert_eq!(as_int(json!(true)), Ok(1));
467 assert_eq!(as_int(json!(null)), Ok(0));
468
469 assert_eq!(as_int(json!("8125.0")), Ok(8125));
471 assert_eq!(as_int(json!("8125.000")), Ok(8125));
472 assert_eq!(as_int(json!("-8125.0")), Ok(-8125));
473
474 assert_eq!(as_int(json!(10.9)), Ok(10));
476 assert_eq!(as_int(json!(-10.9)), Ok(-10));
477 }
478
479 #[test]
480 fn integer_rejects_unparseable_and_out_of_range_values() {
481 for rejected in [
482 json!("8125ms"),
483 json!(""),
484 json!("0x1f"),
485 json!("8125.5"),
486 json!("8125."),
487 json!(1e300),
488 json!(["8125"]),
489 ] {
490 assert!(as_int(rejected.clone()).is_err(), "{rejected}");
491 }
492 }
493
494 #[test]
495 fn number_accepts_numeric_strings_integers_and_booleans() {
496 assert_eq!(as_float(json!(1.5)), Ok(1.5));
497 assert_eq!(as_float(json!("1.5")), Ok(1.5));
498 assert_eq!(as_float(json!(2)), Ok(2.0));
499 assert_eq!(as_float(json!(true)), Ok(1.0));
500 assert_eq!(as_float(json!(null)), Ok(0.0));
501 assert!(as_float(json!("half")).is_err());
502 }
503
504 #[test]
505 fn string_accepts_every_scalar() {
506 assert_eq!(as_string(json!("datadog_only")), Ok("datadog_only".to_owned()));
507 assert_eq!(as_string(json!(true)), Ok("true".to_owned()));
508 assert_eq!(as_string(json!(false)), Ok("false".to_owned()));
509 assert_eq!(as_string(json!(10485760)), Ok("10485760".to_owned()));
510 assert_eq!(as_string(json!(-1)), Ok("-1".to_owned()));
511 assert_eq!(as_string(json!(10.5)), Ok("10.5".to_owned()));
512 assert_eq!(as_string(json!(null)), Ok(String::new()));
513
514 assert_eq!(as_string(json!(1.0)), Ok("1".to_owned()));
516 }
517
518 #[test]
519 fn string_rejects_compound_values() {
520 for rejected in [json!(["a"]), json!({"a": "b"})] {
521 assert!(as_string(rejected.clone()).is_err(), "{rejected}");
522 }
523 }
524
525 #[test]
526 fn string_map_coerces_scalar_values() {
527 let values = serde_json::from_value::<StringMap>(json!({
528 "bool": true,
529 "integer": 3,
530 "null": null,
531 "string": "datadog_only"
532 }))
533 .expect("scalar values deserialize")
534 .0;
535
536 assert_eq!(values["bool"], "true");
537 assert_eq!(values["integer"], "3");
538 assert_eq!(values["null"], "");
539 assert_eq!(values["string"], "datadog_only");
540 assert!(serde_json::from_value::<StringMap>(json!({ "compound": [] })).is_err());
541 }
542
543 #[test]
544 fn optional_string_distinguishes_null_from_a_coerced_scalar() {
545 let absent = serde_json::from_value::<OptStr>(json!(null))
546 .expect("null deserializes")
547 .0;
548 assert_eq!(absent, None);
549
550 let coerced = serde_json::from_value::<OptStr>(json!(3))
551 .expect("scalar deserializes")
552 .0;
553 assert_eq!(coerced, Some("3".to_owned()));
554 }
555}