From cd20296db96f6b7fba2e83bcd51188ac587c943a Mon Sep 17 00:00:00 2001 From: Jaroslav Beran Date: Fri, 6 Feb 2026 15:00:00 +0100 Subject: [PATCH] Correctly report numeric overflow and clarify error semantics Fixes a bug where ReadErrorReason::NotEnoughPrecision was never constructed. NotEnoughPrecision renamed to NumericValueOverflow for clearer semantics. Replaced magic decimal threshold with equivalent hex literal (0x80000000000000) for better readability. --- Cargo.toml | 2 +- src/bin/cp2cp.rs | 2 +- src/cpon.rs | 10 +++++----- src/json.rs | 7 ++++--- src/reader.rs | 2 +- src/textrdwr.rs | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9094943..6a8fb53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ name = "shvproto" description = "Rust implementation of the SHV protocol" license = "MIT" repository = "https://github.com/silicon-heaven/libshvproto-rs" -version = "5.0.1" +version = "6.0.0" edition = "2024" [dependencies] diff --git a/src/bin/cp2cp.rs b/src/bin/cp2cp.rs index 8b61132..38dca41 100644 --- a/src/bin/cp2cp.rs +++ b/src/bin/cp2cp.rs @@ -63,7 +63,7 @@ fn print_option(n: Option) { fn exit_with_result_and_code(result: &ChainPackRpcBlockResult, error: Option) -> ! { let exit_code = error.map_or(CODE_SUCCESS, |error| match error { ReadErrorReason::UnexpectedEndOfStream => CODE_NOT_ENOUGH_DATA, - ReadErrorReason::NotEnoughPrecision => CODE_READ_ERROR, + ReadErrorReason::NumericValueOverflow => CODE_READ_ERROR, ReadErrorReason::InvalidCharacter => { eprintln!("Parse input error: {error:?}"); CODE_READ_ERROR diff --git a/src/cpon.rs b/src/cpon.rs index aa18bf3..c76fcdf 100644 --- a/src/cpon.rs +++ b/src/cpon.rs @@ -572,7 +572,7 @@ mod test use std::collections::BTreeMap; use chrono::{Duration, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; use crate::cpon::CponReader; - use crate::reader::Reader; + use crate::reader::{ReadErrorReason, Reader}; use crate::rpcvalue::Map; #[test] fn test_read() { @@ -604,7 +604,7 @@ mod test test_cpon_round_trip("1000000.", Decimal::new(1_000_000, 0)); test_cpon_round_trip("50.03138741402532", Decimal::new(5_003_138_741_402_532, -14)); // We do not support such high precision. - assert!(RpcValue::from_cpon("36.028797018963968").is_err()); + assert!(RpcValue::from_cpon("36.028797018963968").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow))); assert_eq!(RpcValue::from_cpon(r#""foo""#).unwrap().as_str(), "foo"); assert_eq!(RpcValue::from_cpon(r#""ěščřžýáí""#).unwrap().as_str(), "ěščřžýáí"); assert_eq!(RpcValue::from_cpon("b\"foo\tbar\nbaz\"").unwrap().as_blob(), b"foo\tbar\nbaz"); @@ -706,8 +706,8 @@ mod test assert_eq!(RpcValue::from_cpon("-0x8000000000000000").unwrap().as_int(), i64::MIN); assert_eq!(RpcValue::from_cpon("-0x8000000000000001").unwrap().as_int(), i64::MIN); - assert!(RpcValue::from_cpon("1.23456789012345678901234567890123456789012345678901234567890").is_err()); - assert!(RpcValue::from_cpon("12345678901234567890123456789012345678901234567890123456.7890").is_err()); - assert!(RpcValue::from_cpon("123456789012345678901234567890123456789012345678901234567890.").is_err()); + assert!(RpcValue::from_cpon("1.23456789012345678901234567890123456789012345678901234567890").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow))); + assert!(RpcValue::from_cpon("12345678901234567890123456789012345678901234567890123456.7890").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow))); + assert!(RpcValue::from_cpon("123456789012345678901234567890123456789012345678901234567890.").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow))); } } diff --git a/src/json.rs b/src/json.rs index 29ea44c..b5f6f62 100644 --- a/src/json.rs +++ b/src/json.rs @@ -415,6 +415,7 @@ impl Reader for JsonReader<'_, R> mod test { use crate::Map; + use crate::reader::ReadErrorReason; use chrono::{Duration, FixedOffset, LocalResult}; use crate::json::{TAG_META, TAG_TYPE}; use crate::{Decimal, IMap, RpcValue}; @@ -567,8 +568,8 @@ mod test assert_eq!(RpcValue::from_json("-0x8000000000000000").unwrap().as_int(), i64::MIN); assert_eq!(RpcValue::from_json("-0x8000000000000001").unwrap().as_int(), i64::MIN); - assert!(RpcValue::from_json("1.23456789012345678901234567890123456789012345678901234567890").is_err()); - assert!(RpcValue::from_json("12345678901234567890123456789012345678901234567890123456.7890").is_err()); - assert!(RpcValue::from_json("123456789012345678901234567890123456789012345678901234567890.").is_err()); + assert!(RpcValue::from_json("1.23456789012345678901234567890123456789012345678901234567890").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow))); + assert!(RpcValue::from_json("12345678901234567890123456789012345678901234567890123456.7890").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow))); + assert!(RpcValue::from_json("123456789012345678901234567890123456789012345678901234567890.").is_err_and(|err| matches!(err.reason, ReadErrorReason::NumericValueOverflow))); } } diff --git a/src/reader.rs b/src/reader.rs index 77f99c9..756110e 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -6,7 +6,7 @@ use crate::rpcvalue::Value; pub enum ReadErrorReason { UnexpectedEndOfStream, InvalidCharacter, - NotEnoughPrecision, + NumericValueOverflow, } #[derive(Debug)] pub struct ReadError { diff --git a/src/textrdwr.rs b/src/textrdwr.rs index d5fdbf2..2984c9c 100644 --- a/src/textrdwr.rs +++ b/src/textrdwr.rs @@ -233,7 +233,7 @@ pub trait TextReader : Reader { let ReadInt { value, digit_cnt, is_overflow, .. } = self.read_int(mantissa, true)?; decimal_overflow = decimal_overflow || is_overflow; mantissa = value; - if mantissa >= 36_028_797_018_963_968 { + if mantissa >= 0x80_0000_0000_0000 { decimal_overflow = true; } dec_cnt = i64::from(digit_cnt); @@ -260,7 +260,7 @@ pub trait TextReader : Reader { let mantissa = if is_negative { -mantissa } else { mantissa }; if is_decimal { if decimal_overflow { - return Err(self.make_error("Not enough precision to read the Decimal", ReadErrorReason::InvalidCharacter)) + return Err(self.make_error("Not enough precision to read the Decimal", ReadErrorReason::NumericValueOverflow)) } #[expect(clippy::cast_possible_truncation, reason = "We hope that the new exponent is not big enough to truncate")] return Ok(Value::from(Decimal::new(mantissa, (exponent - dec_cnt) as i8)))