From f47306a4c5752df32c16bf2495902fb57b97b176 Mon Sep 17 00:00:00 2001 From: Tpt Date: Mon, 12 Jun 2023 21:47:11 +0200 Subject: [PATCH] Duration: ensures that the smallest supported duration can be parsed and serialized --- lib/oxsdatatypes/src/duration.rs | 25 ++++++--------- lib/oxsdatatypes/src/parser.rs | 53 +++++++++++++++++--------------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/lib/oxsdatatypes/src/duration.rs b/lib/oxsdatatypes/src/duration.rs index 11ed0c1d..96faec7b 100644 --- a/lib/oxsdatatypes/src/duration.rs +++ b/lib/oxsdatatypes/src/duration.rs @@ -143,13 +143,11 @@ impl fmt::Display for Duration { #[allow(clippy::many_single_char_names)] #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut ym = self.year_month.months; - let mut ss = self.day_time.seconds; + let ym = self.year_month.months; + let ss = self.day_time.seconds; if ym < 0 || ss < 0.into() { write!(f, "-")?; - ym = -ym; - ss = -ss; } write!(f, "P")?; @@ -163,12 +161,12 @@ impl fmt::Display for Duration { if y != 0 { if m == 0 { - write!(f, "{y}Y")?; + write!(f, "{}Y", y.abs())?; } else { - write!(f, "{y}Y{m}M")?; + write!(f, "{}Y{}M", y.abs(), m.abs())?; } } else if m != 0 || ss == 0.into() { - write!(f, "{m}M")?; + write!(f, "{}M", m.abs())?; } } @@ -184,19 +182,19 @@ impl fmt::Display for Duration { .ok_or(fmt::Error)?; if d != 0 { - write!(f, "{d}D")?; + write!(f, "{}D", d.abs())?; } if h != 0 || m != 0 || s != 0.into() { write!(f, "T")?; if h != 0 { - write!(f, "{h}H")?; + write!(f, "{}H", h.abs())?; } if m != 0 { - write!(f, "{m}M")?; + write!(f, "{}M", m.abs())?; } if s != 0.into() { - write!(f, "{s}S")?; + write!(f, "{}S", s.abs())?; } } } @@ -621,10 +619,7 @@ mod tests { #[test] fn from_str() -> Result<(), XsdParseError> { - let min = Duration::new( - i64::MIN + 1, - Decimal::MIN.checked_add(Decimal::STEP).unwrap(), - ); + let min = Duration::new(i64::MIN, Decimal::MIN); let max = Duration::new(i64::MAX, Decimal::MAX); assert_eq!(YearMonthDuration::from_str("P1Y")?.to_string(), "P1Y"); diff --git a/lib/oxsdatatypes/src/parser.rs b/lib/oxsdatatypes/src/parser.rs index 06a1ad1f..942c71e4 100644 --- a/lib/oxsdatatypes/src/parser.rs +++ b/lib/oxsdatatypes/src/parser.rs @@ -109,7 +109,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { const AFTER_MINUTE: u32 = 6; const AFTER_SECOND: u32 = 7; - let (negative, input) = if let Some(left) = input.strip_prefix('-') { + let (is_negative, input) = if let Some(left) = input.strip_prefix('-') { (true, left) } else { (false, input) @@ -133,7 +133,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { year_month .unwrap_or_default() .checked_add( - i64::from_str(number_str)? + apply_i64_neg(i64::from_str(number_str)?, is_negative)? .checked_mul(12) .ok_or(OVERFLOW_ERROR)?, ) @@ -145,7 +145,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { year_month = Some( year_month .unwrap_or_default() - .checked_add(i64::from_str(number_str)?) + .checked_add(apply_i64_neg(i64::from_str(number_str)?, is_negative)?) .ok_or(OVERFLOW_ERROR)?, ); state = AFTER_MONTH; @@ -160,7 +160,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { day_time .unwrap_or_default() .checked_add( - Decimal::from_str(number_str)? + apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)? .checked_mul(86400) .ok_or(OVERFLOW_ERROR)?, ) @@ -178,7 +178,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { day_time .unwrap_or_default() .checked_add( - Decimal::from_str(number_str)? + apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)? .checked_mul(3600) .ok_or(OVERFLOW_ERROR)?, ) @@ -196,7 +196,7 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { day_time .unwrap_or_default() .checked_add( - Decimal::from_str(number_str)? + apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)? .checked_mul(60) .ok_or(OVERFLOW_ERROR)?, ) @@ -208,7 +208,10 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { day_time = Some( day_time .unwrap_or_default() - .checked_add(Decimal::from_str(number_str)?) + .checked_add(apply_decimal_neg( + Decimal::from_str(number_str)?, + is_negative, + )?) .ok_or(OVERFLOW_ERROR)?, ); state = AFTER_SECOND; @@ -226,29 +229,29 @@ fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> { Ok(( DurationParts { - year_month: if let Some(v) = year_month { - Some(if negative { - v.checked_neg().ok_or(OVERFLOW_ERROR)? - } else { - v - }) - } else { - None - }, - day_time: if let Some(v) = day_time { - Some(if negative { - v.checked_neg().ok_or(OVERFLOW_ERROR)? - } else { - v - }) - } else { - None - }, + year_month, + day_time, }, input, )) } +fn apply_i64_neg(value: i64, is_negative: bool) -> Result { + if is_negative { + value.checked_neg().ok_or(OVERFLOW_ERROR) + } else { + Ok(value) + } +} + +fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result { + if is_negative { + value.checked_neg().ok_or(OVERFLOW_ERROR) + } else { + Ok(value) + } +} + pub fn parse_duration(input: &str) -> Result { let parts = ensure_complete(input, duration_parts)?; if parts.year_month.is_none() && parts.day_time.is_none() {