1059 1059 | let n = s.parse::<i64>().map_err(|e| SerdeError::InvalidInput {
|
1060 1060 | message: e.to_string(),
|
1061 1061 | })?;
|
1062 1062 | self.advance_by(len);
|
1063 1063 | Ok(n)
|
1064 1064 | }
|
1065 1065 |
|
1066 1066 | fn read_float_value(&mut self) -> Result<f64, SerdeError> {
|
1067 1067 | self.skip_whitespace();
|
1068 1068 | let rem = self.remaining();
|
1069 - | // Handle string-encoded special float values: "NaN", "Infinity", "-Infinity"
|
1069 + | // A string may only carry a non-finite value, which JSON cannot express as a number:
|
1070 + | // `"NaN"`, `"Infinity"`, `"-Infinity"`. A quoted finite number such as `"123"` is a
|
1071 + | // type mismatch; the restJson1 protocol tests
|
1072 + | // `RestJsonBodyFloatMalformedValueRejected_case0` and
|
1073 + | // `RestJsonBodyDoubleMalformedValueRejected_case0` require it to be rejected. This
|
1074 + | // mirrors the token-based parser (`expect_number_or_null`), which parses the string
|
1075 + | // with `aws_smithy_types::primitive::Parse` and then rejects finite values, so the
|
1076 + | // spellings Rust's `f64::from_str` accepts for non-finite values (e.g. `"nan"`) are
|
1077 + | // accepted here too.
|
1070 1078 | if rem.first() == Some(&b'"') {
|
1071 1079 | let s = self.read_string(&aws_smithy_schema::prelude::STRING)?;
|
1072 - | return match s.as_str() {
|
1073 - | "NaN" => Ok(f64::NAN),
|
1074 - | "Infinity" => Ok(f64::INFINITY),
|
1075 - | "-Infinity" => Ok(f64::NEG_INFINITY),
|
1076 - | _ => s.parse::<f64>().map_err(|e| SerdeError::InvalidInput {
|
1080 + | let value = match s.as_str() {
|
1081 + | "NaN" => f64::NAN,
|
1082 + | "Infinity" => f64::INFINITY,
|
1083 + | "-Infinity" => f64::NEG_INFINITY,
|
1084 + | other => other.parse::<f64>().map_err(|e| SerdeError::InvalidInput {
|
1077 1085 | message: e.to_string(),
|
1078 - | }),
|
1086 + | })?,
|
1079 1087 | };
|
1088 + | if value.is_finite() {
|
1089 + | return Err(SerdeError::TypeMismatch {
|
1090 + | message: format!(
|
1091 + | "only `Infinity`, `-Infinity`, `NaN` can represent a float as a string but found `{s}`"
|
1092 + | ),
|
1093 + | });
|
1094 + | }
|
1095 + | return Ok(value);
|
1080 1096 | }
|
1081 1097 | let mut len = 0;
|
1082 1098 | for &b in rem {
|
1083 1099 | if b.is_ascii_digit() || b == b'-' || b == b'+' || b == b'.' || b == b'e' || b == b'E' {
|
1084 1100 | len += 1;
|
1085 1101 | } else {
|
1086 1102 | break;
|
1087 1103 | }
|
1088 1104 | }
|
1089 1105 | if len == 0 {
|
2444 2460 | #[test]
|
2445 2461 | fn documents_check_separators() {
|
2446 2462 | assert!(deser(br#"{"a":[1,2],"b":{"c":true}}"#)
|
2447 2463 | .read_document(&INT)
|
2448 2464 | .is_ok());
|
2449 2465 | assert!(deser(br#"{"a":1,}"#).read_document(&INT).is_err());
|
2450 2466 | assert!(deser(br#"[1,,2]"#).read_document(&INT).is_err());
|
2451 2467 | assert!(deser(br#"{,"a":1}"#).read_document(&INT).is_err());
|
2452 2468 | }
|
2453 2469 | }
|
2470 + |
|
2471 + | mod float_strings {
|
2472 + | //! A float or double member may carry a string only for a non-finite value
|
2473 + | //! (`"NaN"`, `"Infinity"`, `"-Infinity"`). Smithy protocol tests
|
2474 + | //! `RestJsonBodyFloatMalformedValueRejected_case0` and
|
2475 + | //! `RestJsonBodyDoubleMalformedValueRejected_case0` (`"123"`) require quoted finite
|
2476 + | //! numbers to be rejected.
|
2477 + | use super::*;
|
2478 + | use aws_smithy_schema::{shape_id, ShapeType};
|
2479 + |
|
2480 + | static F: Schema =
|
2481 + | Schema::new_member(shape_id!("test", "Input", "f"), ShapeType::Float, "f", 0);
|
2482 + | static D: Schema =
|
2483 + | Schema::new_member(shape_id!("test", "Input", "d"), ShapeType::Double, "d", 1);
|
2484 + | static INPUT: Schema =
|
2485 + | Schema::new_struct(shape_id!("test", "Input"), ShapeType::Structure, &[&F, &D]);
|
2486 + |
|
2487 + | fn read(body: &[u8]) -> Result<(Option<f32>, Option<f64>), SerdeError> {
|
2488 + | let mut deser = JsonDeserializer::new(body, Arc::new(JsonCodecSettings::default()));
|
2489 + | let (mut f, mut d) = (None, None);
|
2490 + | deser.read_struct(&INPUT, &mut |m, x| {
|
2491 + | match m.member_index() {
|
2492 + | Some(0) => f = Some(x.read_float(m)?),
|
2493 + | Some(1) => d = Some(x.read_double(m)?),
|
2494 + | _ => {}
|
2495 + | }
|
2496 + | Ok(())
|
2497 + | })?;
|
2498 + | Ok((f, d))
|
2499 + | }
|
2500 + |
|
2501 + | #[test]
|
2502 + | fn numbers_and_the_three_special_strings_are_read() {
|
2503 + | assert_eq!(
|
2504 + | read(br#"{"f": 1.5, "d": -2}"#).unwrap(),
|
2505 + | (Some(1.5), Some(-2.0))
|
2506 + | );
|
2507 + | let (f, d) = read(br#"{"f": "NaN", "d": "-Infinity"}"#).unwrap();
|
2508 + | assert!(f.unwrap().is_nan());
|
2509 + | assert_eq!(d, Some(f64::NEG_INFINITY));
|
2510 + | assert_eq!(
|
2511 + | read(br#"{"f": "Infinity"}"#).unwrap().0,
|
2512 + | Some(f32::INFINITY)
|
2513 + | );
|
2514 + | }
|
2515 + |
|
2516 + | #[test]
|
2517 + | fn quoted_finite_numbers_are_rejected() {
|
2518 + | assert!(read(br#"{"f": "123"}"#).is_err());
|
2519 + | assert!(read(br#"{"d": "123"}"#).is_err());
|
2520 + | assert!(read(br#"{"d": "1.5e3"}"#).is_err());
|
2521 + | assert!(read(br#"{"d": "-0"}"#).is_err());
|
2522 + | }
|
2523 + |
|
2524 + | #[test]
|
2525 + | fn non_numeric_strings_are_rejected() {
|
2526 + | assert!(read(br#"{"d": "abc"}"#).is_err());
|
2527 + | assert!(read(br#"{"d": ""}"#).is_err());
|
2528 + | assert!(read(br#"{"d": "1 2"}"#).is_err());
|
2529 + | }
|
2530 + |
|
2531 + | #[test]
|
2532 + | fn alternative_non_finite_spellings_match_the_legacy_parser() {
|
2533 + | // `aws_smithy_types::primitive::Parse` falls back to `f64::from_str`, which
|
2534 + | // accepts these case-insensitively; only finite results are rejected.
|
2535 + | assert!(read(br#"{"f": "nan"}"#).unwrap().0.unwrap().is_nan());
|
2536 + | assert_eq!(read(br#"{"d": "inf"}"#).unwrap().1, Some(f64::INFINITY));
|
2537 + | assert_eq!(
|
2538 + | read(br#"{"d": "-infinity"}"#).unwrap().1,
|
2539 + | Some(f64::NEG_INFINITY)
|
2540 + | );
|
2541 + | }
|
2542 + | }
|
2454 2543 | }
|