From 7ebf413c30a25b21b006f27e1e23636593c61e0d Mon Sep 17 00:00:00 2001 From: mag123c Date: Mon, 13 Jul 2026 21:22:15 +0900 Subject: [PATCH] Add approx integer parsing error-path test coverage Mirrors the correct-parser coverage for the approx-number-parsing feature: leading zeroes, base prefixes (0x/0b/0o), a bare/invalid sign, integers with an exponent marker but no exponent digits, and non-terminator trailing bytes. These InvalidNumber paths in the approx parse_number were previously untested. --- src/numberparse/approx.rs | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/numberparse/approx.rs b/src/numberparse/approx.rs index d2670971..f9706e24 100644 --- a/src/numberparse/approx.rs +++ b/src/numberparse/approx.rs @@ -562,10 +562,18 @@ impl Deserializer<'_> { #[cfg(test)] mod test { #![allow(clippy::default_trait_access)] + use crate::error::Error; + use crate::value::owned::Value; use crate::value::owned::to_value; use float_cmp::approx_eq; use value_trait::prelude::*; + fn to_value_from_str(buf: &str) -> Result { + let mut val = String::from(buf); + let val = unsafe { val.as_bytes_mut() }; + to_value(val) + } + #[test] fn bad_exp() { let mut too_big = String::from("1e309"); @@ -680,4 +688,65 @@ mod test { let r = to_value(&mut i).expect("failed to decode"); assert!(approx_eq!(f64, r.as_f64().expect("float"), -0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000596916642387374)); } + + #[test] + fn int_trailing_invalid() { + // The byte terminating an integer must be structural-or-whitespace. An + // embedded NUL, a trailing letter or a digit separator are none of those + // and must be rejected. + assert!(to_value_from_str("123\x00").is_err()); + assert!(to_value_from_str("[123\x00]").is_err()); + assert!(to_value_from_str("123a").is_err()); + assert!(to_value_from_str("1a").is_err()); + assert!(to_value_from_str("[123a]").is_err()); + assert!(to_value_from_str("[1a]").is_err()); + // digit separators (e.g. Rust's `1_000`) are not valid JSON + assert!(to_value_from_str("1_000").is_err()); + } + + #[test] + fn int_leading_zero() { + // JSON forbids leading zeroes: a `0` may only be followed by a + // structural/whitespace/exponent/decimal byte, never another digit. + assert!(to_value_from_str("01").is_err()); + assert!(to_value_from_str("00").is_err()); + assert!(to_value_from_str("012").is_err()); + assert!(to_value_from_str("0123").is_err()); + assert!(to_value_from_str("-01").is_err()); + assert!(to_value_from_str("-00").is_err()); + assert!(to_value_from_str("[01]").is_err()); + assert!(to_value_from_str("[-01]").is_err()); + } + + #[test] + fn int_zero_prefix() { + // A `0` followed by a base prefix (hex/binary/octal) is not valid JSON. + assert!(to_value_from_str("0x1").is_err()); + assert!(to_value_from_str("0X1").is_err()); + assert!(to_value_from_str("0b1").is_err()); + assert!(to_value_from_str("0o1").is_err()); + assert!(to_value_from_str("[0x1]").is_err()); + } + + #[test] + fn int_bare_sign() { + // A `-` must be followed by a digit. + assert!(to_value_from_str("-").is_err()); + assert!(to_value_from_str("-x").is_err()); + assert!(to_value_from_str("-e5").is_err()); + assert!(to_value_from_str("-.5").is_err()); + } + + #[test] + fn int_bad_exponent() { + // An integer carrying an exponent marker still needs at least one + // exponent digit; a bare sign after `e`/`E` is not enough. + assert!(to_value_from_str("1e").is_err()); + assert!(to_value_from_str("2E").is_err()); + assert!(to_value_from_str("1e+").is_err()); + assert!(to_value_from_str("1e-").is_err()); + assert!(to_value_from_str("9e+").is_err()); + assert!(to_value_from_str("[1e]").is_err()); + assert!(to_value_from_str("[9e-]").is_err()); + } }