Improves XSD errors and code organization

pull/580/head
Tpt 1 year ago committed by Thomas Tanon
parent b22e74379a
commit 077c1fc1a8
  1. 4
      lib/oxsdatatypes/README.md
  2. 987
      lib/oxsdatatypes/src/date_time.rs
  3. 293
      lib/oxsdatatypes/src/decimal.rs
  4. 480
      lib/oxsdatatypes/src/duration.rs
  5. 64
      lib/oxsdatatypes/src/integer.rs
  6. 13
      lib/oxsdatatypes/src/lib.rs
  7. 626
      lib/oxsdatatypes/src/parser.rs
  8. 24
      lib/src/sparql/eval.rs

@ -38,10 +38,10 @@ The `DateTime::now()` function needs special OS support.
Currently: Currently:
- If the `custom-now` feature is enabled, a function computing `now` must be set: - If the `custom-now` feature is enabled, a function computing `now` must be set:
```rust ```rust
use oxsdatatypes::{DateTimeError, Duration}; use oxsdatatypes::Duration;
#[no_mangle] #[no_mangle]
fn custom_ox_now() -> Result<Duration, DateTimeError> { fn custom_ox_now() -> Duration {
unimplemented!("now implementation") unimplemented!("now implementation")
} }
``` ```

File diff suppressed because it is too large Load Diff

@ -1,4 +1,4 @@
use crate::{Boolean, Double, Float, Integer}; use crate::{Boolean, Double, Float, Integer, TooLargeForIntegerError};
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::fmt::Write; use std::fmt::Write;
@ -21,15 +21,20 @@ pub struct Decimal {
impl Decimal { impl Decimal {
/// Constructs the decimal i / 10^n /// Constructs the decimal i / 10^n
#[inline] #[inline]
pub fn new(i: i128, n: u32) -> Result<Self, DecimalOverflowError> { pub const fn new(i: i128, n: u32) -> Result<Self, TooLargeForDecimalError> {
let shift = DECIMAL_PART_DIGITS let Some(shift) = DECIMAL_PART_DIGITS.checked_sub(n) else {
.checked_sub(n) return Err(TooLargeForDecimalError);
.ok_or(DecimalOverflowError)?; };
Ok(Self { let Some(value) = i.checked_mul(10_i128.pow(shift)) else {
value: i return Err(TooLargeForDecimalError);
.checked_mul(10_i128.pow(shift)) };
.ok_or(DecimalOverflowError)?, Ok(Self { value })
}) }
pub(crate) const fn new_from_i128_unchecked(value: i128) -> Self {
Self {
value: value * DECIMAL_PART_POW,
}
} }
#[inline] #[inline]
@ -47,6 +52,8 @@ impl Decimal {
} }
/// [op:numeric-add](https://www.w3.org/TR/xpath-functions-31/#func-numeric-add) /// [op:numeric-add](https://www.w3.org/TR/xpath-functions-31/#func-numeric-add)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
@ -56,6 +63,8 @@ impl Decimal {
} }
/// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract) /// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
@ -65,6 +74,8 @@ impl Decimal {
} }
/// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply) /// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_mul(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_mul(self, rhs: impl Into<Self>) -> Option<Self> {
@ -98,6 +109,8 @@ impl Decimal {
} }
/// [op:numeric-divide](https://www.w3.org/TR/xpath-functions-31/#func-numeric-divide) /// [op:numeric-divide](https://www.w3.org/TR/xpath-functions-31/#func-numeric-divide)
///
/// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_div(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_div(self, rhs: impl Into<Self>) -> Option<Self> {
@ -132,6 +145,8 @@ impl Decimal {
} }
/// [op:numeric-mod](https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod) /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod)
///
/// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_rem(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_rem(self, rhs: impl Into<Self>) -> Option<Self> {
@ -140,6 +155,9 @@ impl Decimal {
}) })
} }
/// Euclidean remainder
///
/// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_rem_euclid(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_rem_euclid(self, rhs: impl Into<Self>) -> Option<Self> {
@ -149,6 +167,8 @@ impl Decimal {
} }
/// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus) /// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_neg(self) -> Option<Self> { pub fn checked_neg(self) -> Option<Self> {
@ -158,52 +178,63 @@ impl Decimal {
} }
/// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs) /// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn abs(self) -> Self { pub fn checked_abs(self) -> Option<Self> {
Self { Some(Self {
value: self.value.abs(), value: self.value.checked_abs()?,
} })
} }
/// [fn:round](https://www.w3.org/TR/xpath-functions-31/#func-round) /// [fn:round](https://www.w3.org/TR/xpath-functions-31/#func-round)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn round(self) -> Self { pub fn checked_round(self) -> Option<Self> {
let value = self.value / DECIMAL_PART_POW_MINUS_ONE; let value = self.value / DECIMAL_PART_POW_MINUS_ONE;
Self { Some(Self {
value: if value >= 0 { value: if value >= 0 {
(value / 10 + i128::from(value % 10 >= 5)) * DECIMAL_PART_POW value / 10 + i128::from(value % 10 >= 5)
} else { } else {
(value / 10 - i128::from(-value % 10 > 5)) * DECIMAL_PART_POW value / 10 - i128::from(-value % 10 > 5)
},
} }
.checked_mul(DECIMAL_PART_POW)?,
})
} }
/// [fn:ceiling](https://www.w3.org/TR/xpath-functions-31/#func-ceiling) /// [fn:ceiling](https://www.w3.org/TR/xpath-functions-31/#func-ceiling)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn ceil(self) -> Self { pub fn checked_ceil(self) -> Option<Self> {
Self { Some(Self {
value: if self.value >= 0 && self.value % DECIMAL_PART_POW != 0 { value: if self.value > 0 && self.value % DECIMAL_PART_POW != 0 {
(self.value / DECIMAL_PART_POW + 1) * DECIMAL_PART_POW self.value / DECIMAL_PART_POW + 1
} else { } else {
(self.value / DECIMAL_PART_POW) * DECIMAL_PART_POW self.value / DECIMAL_PART_POW
},
} }
.checked_mul(DECIMAL_PART_POW)?,
})
} }
/// [fn:floor](https://www.w3.org/TR/xpath-functions-31/#func-floor) /// [fn:floor](https://www.w3.org/TR/xpath-functions-31/#func-floor)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn floor(self) -> Self { pub fn checked_floor(self) -> Option<Self> {
Self { Some(Self {
value: if self.value >= 0 || self.value % DECIMAL_PART_POW == 0 { value: if self.value >= 0 || self.value % DECIMAL_PART_POW == 0 {
(self.value / DECIMAL_PART_POW) * DECIMAL_PART_POW self.value / DECIMAL_PART_POW
} else { } else {
(self.value / DECIMAL_PART_POW - 1) * DECIMAL_PART_POW self.value / DECIMAL_PART_POW - 1
},
} }
.checked_mul(DECIMAL_PART_POW)?,
})
} }
#[inline] #[inline]
@ -328,28 +359,28 @@ impl From<Integer> for Decimal {
} }
impl TryFrom<i128> for Decimal { impl TryFrom<i128> for Decimal {
type Error = DecimalOverflowError; type Error = TooLargeForDecimalError;
#[inline] #[inline]
fn try_from(value: i128) -> Result<Self, DecimalOverflowError> { fn try_from(value: i128) -> Result<Self, TooLargeForDecimalError> {
Ok(Self { Ok(Self {
value: value value: value
.checked_mul(DECIMAL_PART_POW) .checked_mul(DECIMAL_PART_POW)
.ok_or(DecimalOverflowError)?, .ok_or(TooLargeForDecimalError)?,
}) })
} }
} }
impl TryFrom<u128> for Decimal { impl TryFrom<u128> for Decimal {
type Error = DecimalOverflowError; type Error = TooLargeForDecimalError;
#[inline] #[inline]
fn try_from(value: u128) -> Result<Self, DecimalOverflowError> { fn try_from(value: u128) -> Result<Self, TooLargeForDecimalError> {
Ok(Self { Ok(Self {
value: i128::try_from(value) value: i128::try_from(value)
.map_err(|_| DecimalOverflowError)? .map_err(|_| TooLargeForDecimalError)?
.checked_mul(DECIMAL_PART_POW) .checked_mul(DECIMAL_PART_POW)
.ok_or(DecimalOverflowError)?, .ok_or(TooLargeForDecimalError)?,
}) })
} }
} }
@ -362,27 +393,27 @@ impl From<Boolean> for Decimal {
} }
impl TryFrom<Float> for Decimal { impl TryFrom<Float> for Decimal {
type Error = DecimalOverflowError; type Error = TooLargeForDecimalError;
#[inline] #[inline]
fn try_from(value: Float) -> Result<Self, DecimalOverflowError> { fn try_from(value: Float) -> Result<Self, TooLargeForDecimalError> {
Double::from(value).try_into() Double::from(value).try_into()
} }
} }
impl TryFrom<Double> for Decimal { impl TryFrom<Double> for Decimal {
type Error = DecimalOverflowError; type Error = TooLargeForDecimalError;
#[inline] #[inline]
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
fn try_from(value: Double) -> Result<Self, DecimalOverflowError> { fn try_from(value: Double) -> Result<Self, TooLargeForDecimalError> {
let shifted = f64::from(value) * (DECIMAL_PART_POW as f64); let shifted = f64::from(value) * (DECIMAL_PART_POW as f64);
if shifted.is_finite() && (i128::MIN as f64) <= shifted && shifted <= (i128::MAX as f64) { if (i128::MIN as f64) <= shifted && shifted <= (i128::MAX as f64) {
Ok(Self { Ok(Self {
value: shifted as i128, value: shifted as i128,
}) })
} else { } else {
Err(DecimalOverflowError) Err(TooLargeForDecimalError)
} }
} }
} }
@ -415,17 +446,17 @@ impl From<Decimal> for Double {
} }
impl TryFrom<Decimal> for Integer { impl TryFrom<Decimal> for Integer {
type Error = DecimalOverflowError; type Error = TooLargeForIntegerError;
#[inline] #[inline]
fn try_from(value: Decimal) -> Result<Self, DecimalOverflowError> { fn try_from(value: Decimal) -> Result<Self, TooLargeForIntegerError> {
Ok(i64::try_from( Ok(i64::try_from(
value value
.value .value
.checked_div(DECIMAL_PART_POW) .checked_div(DECIMAL_PART_POW)
.ok_or(DecimalOverflowError)?, .ok_or(TooLargeForIntegerError)?,
) )
.map_err(|_| DecimalOverflowError)? .map_err(|_| TooLargeForIntegerError)?
.into()) .into())
} }
} }
@ -620,25 +651,27 @@ impl fmt::Display for ParseDecimalError {
impl Error for ParseDecimalError {} impl Error for ParseDecimalError {}
impl From<DecimalOverflowError> for ParseDecimalError { impl From<TooLargeForDecimalError> for ParseDecimalError {
fn from(_: DecimalOverflowError) -> Self { fn from(_: TooLargeForDecimalError) -> Self {
Self { Self {
kind: DecimalParseErrorKind::Overflow, kind: DecimalParseErrorKind::Overflow,
} }
} }
} }
/// An overflow in [`Decimal`] computations. /// The input is too large to fit into a [`Decimal`].
///
/// Matches XPath [`FOCA0001` error](https://www.w3.org/TR/xpath-functions-31/#ERRFOCA0001).
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct DecimalOverflowError; pub struct TooLargeForDecimalError;
impl fmt::Display for DecimalOverflowError { impl fmt::Display for TooLargeForDecimalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Value overflow") write!(f, "Value too large for xsd:decimal internal representation")
} }
} }
impl Error for DecimalOverflowError {} impl Error for TooLargeForDecimalError {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -797,45 +830,153 @@ mod tests {
Some(Decimal::from_str("0.9")?) Some(Decimal::from_str("0.9")?)
); );
assert_eq!(Decimal::from(1).checked_rem(0), None); assert_eq!(Decimal::from(1).checked_rem(0), None);
assert_eq!(
Decimal::MAX.checked_rem(1),
Some(Decimal::from_str("0.687303715884105727")?)
);
assert_eq!(
Decimal::MIN.checked_rem(1),
Some(Decimal::from_str("-0.687303715884105728")?)
);
assert_eq!(
Decimal::MAX.checked_rem(Decimal::STEP),
Some(Decimal::default())
);
assert_eq!(
Decimal::MIN.checked_rem(Decimal::STEP),
Some(Decimal::default())
);
assert_eq!(
Decimal::MAX.checked_rem(Decimal::MAX),
Some(Decimal::default())
);
assert_eq!(
Decimal::MIN.checked_rem(Decimal::MIN),
Some(Decimal::default())
);
Ok(()) Ok(())
} }
#[test] #[test]
fn round() -> Result<(), ParseDecimalError> { fn round() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from(10).round(), Decimal::from(10)); assert_eq!(Decimal::from(10).checked_round(), Some(Decimal::from(10)));
assert_eq!(Decimal::from(-10).round(), Decimal::from(-10)); assert_eq!(Decimal::from(-10).checked_round(), Some(Decimal::from(-10)));
assert_eq!(Decimal::from_str("2.5")?.round(), Decimal::from(3)); assert_eq!(
assert_eq!(Decimal::from_str("2.4999")?.round(), Decimal::from(2)); Decimal::from(i64::MIN).checked_round(),
assert_eq!(Decimal::from_str("-2.5")?.round(), Decimal::from(-2)); Some(Decimal::from(i64::MIN))
assert_eq!(Decimal::from(i64::MIN).round(), Decimal::from(i64::MIN)); );
assert_eq!(Decimal::from(i64::MAX).round(), Decimal::from(i64::MAX)); assert_eq!(
Decimal::from(i64::MAX).checked_round(),
Some(Decimal::from(i64::MAX))
);
assert_eq!(
Decimal::from_str("2.5")?.checked_round(),
Some(Decimal::from(3))
);
assert_eq!(
Decimal::from_str("2.4999")?.checked_round(),
Some(Decimal::from(2))
);
assert_eq!(
Decimal::from_str("-2.5")?.checked_round(),
Some(Decimal::from(-2))
);
assert_eq!(Decimal::MAX.checked_round(), None);
assert_eq!(
(Decimal::MAX.checked_sub(Decimal::from_str("0.5")?))
.unwrap()
.checked_round(),
Some(Decimal::from_str("170141183460469231731")?)
);
assert_eq!(Decimal::MIN.checked_round(), None);
assert_eq!(
(Decimal::MIN.checked_add(Decimal::from_str("0.5")?))
.unwrap()
.checked_round(),
Some(Decimal::from_str("-170141183460469231731")?)
);
Ok(()) Ok(())
} }
#[test] #[test]
fn ceil() -> Result<(), ParseDecimalError> { fn ceil() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from(10).ceil(), Decimal::from(10)); assert_eq!(Decimal::from(10).checked_ceil(), Some(Decimal::from(10)));
assert_eq!(Decimal::from(-10).ceil(), Decimal::from(-10)); assert_eq!(Decimal::from(-10).checked_ceil(), Some(Decimal::from(-10)));
assert_eq!(Decimal::from_str("10.5")?.ceil(), Decimal::from(11)); assert_eq!(
assert_eq!(Decimal::from_str("-10.5")?.ceil(), Decimal::from(-10)); Decimal::from_str("10.5")?.checked_ceil(),
assert_eq!(Decimal::from(i64::MIN).ceil(), Decimal::from(i64::MIN)); Some(Decimal::from(11))
assert_eq!(Decimal::from(i64::MAX).ceil(), Decimal::from(i64::MAX)); );
assert_eq!(
Decimal::from_str("-10.5")?.checked_ceil(),
Some(Decimal::from(-10))
);
assert_eq!(
Decimal::from(i64::MIN).checked_ceil(),
Some(Decimal::from(i64::MIN))
);
assert_eq!(
Decimal::from(i64::MAX).checked_ceil(),
Some(Decimal::from(i64::MAX))
);
assert_eq!(Decimal::MAX.checked_ceil(), None);
assert_eq!(
Decimal::MAX
.checked_sub(Decimal::from(1))
.unwrap()
.checked_ceil(),
Some(Decimal::from_str("170141183460469231731")?)
);
assert_eq!(
Decimal::MIN.checked_ceil(),
Some(Decimal::from_str("-170141183460469231731")?)
);
Ok(()) Ok(())
} }
#[test] #[test]
fn floor() -> Result<(), ParseDecimalError> { fn floor() -> Result<(), ParseDecimalError> {
assert_eq!(Decimal::from(10).ceil(), Decimal::from(10)); assert_eq!(Decimal::from(10).checked_floor(), Some(Decimal::from(10)));
assert_eq!(Decimal::from(-10).ceil(), Decimal::from(-10)); assert_eq!(Decimal::from(-10).checked_floor(), Some(Decimal::from(-10)));
assert_eq!(Decimal::from_str("10.5")?.floor(), Decimal::from(10)); assert_eq!(
assert_eq!(Decimal::from_str("-10.5")?.floor(), Decimal::from(-11)); Decimal::from_str("10.5")?.checked_floor(),
assert_eq!(Decimal::from(i64::MIN).floor(), Decimal::from(i64::MIN)); Some(Decimal::from(10))
assert_eq!(Decimal::from(i64::MAX).floor(), Decimal::from(i64::MAX)); );
assert_eq!(
Decimal::from_str("-10.5")?.checked_floor(),
Some(Decimal::from(-11))
);
assert_eq!(
Decimal::from(i64::MIN).checked_floor(),
Some(Decimal::from(i64::MIN))
);
assert_eq!(
Decimal::from(i64::MAX).checked_floor(),
Some(Decimal::from(i64::MAX))
);
assert_eq!(
Decimal::MAX.checked_floor(),
Some(Decimal::from_str("170141183460469231731")?)
);
assert_eq!(Decimal::MIN.checked_floor(), None);
assert_eq!(
(Decimal::MIN.checked_add(Decimal::from_str("1")?))
.unwrap()
.checked_floor(),
Some(Decimal::from_str("-170141183460469231731")?)
);
Ok(()) Ok(())
} }
#[test] #[test]
fn to_be_bytes() -> Result<(), ParseDecimalError> { fn to_be_bytes() -> Result<(), ParseDecimalError> {
assert_eq!(
Decimal::from_be_bytes(Decimal::MIN.to_be_bytes()),
Decimal::MIN
);
assert_eq!(
Decimal::from_be_bytes(Decimal::MAX.to_be_bytes()),
Decimal::MAX
);
assert_eq!( assert_eq!(
Decimal::from_be_bytes(Decimal::from(i64::MIN).to_be_bytes()), Decimal::from_be_bytes(Decimal::from(i64::MIN).to_be_bytes()),
Decimal::from(i64::MIN) Decimal::from(i64::MIN)
@ -889,7 +1030,8 @@ mod tests {
.unwrap() .unwrap()
.checked_sub(Decimal::from(1_672_507_293_696_i64)) .checked_sub(Decimal::from(1_672_507_293_696_i64))
.unwrap() .unwrap()
.abs() .checked_abs()
.unwrap()
< Decimal::from(1) < Decimal::from(1)
); );
Ok(()) Ok(())
@ -914,7 +1056,8 @@ mod tests {
.unwrap() .unwrap()
.checked_sub(Decimal::from(1_672_507_302_466_i64)) .checked_sub(Decimal::from(1_672_507_302_466_i64))
.unwrap() .unwrap()
.abs() .checked_abs()
.unwrap()
< Decimal::from(1) < Decimal::from(1)
); );
assert!(Decimal::try_from(Double::from(f64::NAN)).is_err()); assert!(Decimal::try_from(Double::from(f64::NAN)).is_err());

@ -1,7 +1,6 @@
use super::decimal::DecimalOverflowError; use crate::{DateTime, Decimal};
use super::parser::*;
use super::*;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::error::Error;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration as StdDuration; use std::time::Duration as StdDuration;
@ -78,13 +77,13 @@ impl Duration {
#[inline] #[inline]
#[must_use] #[must_use]
pub(super) const fn all_months(self) -> i64 { pub(crate) const fn all_months(self) -> i64 {
self.year_month.all_months() self.year_month.all_months()
} }
#[inline] #[inline]
#[must_use] #[must_use]
pub(super) const fn all_seconds(self) -> Decimal { pub(crate) const fn all_seconds(self) -> Decimal {
self.day_time.as_seconds() self.day_time.as_seconds()
} }
@ -98,6 +97,8 @@ impl Duration {
} }
/// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations) and [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations) /// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations) and [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations)
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
@ -109,6 +110,8 @@ impl Duration {
} }
/// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations) and [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations) /// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations) and [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations)
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
@ -119,6 +122,9 @@ impl Duration {
}) })
} }
/// Unary negation.
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_neg(self) -> Option<Self> { pub fn checked_neg(self) -> Option<Self> {
@ -134,22 +140,39 @@ impl Duration {
pub fn is_identical_with(self, other: Self) -> bool { pub fn is_identical_with(self, other: Self) -> bool {
self == other self == other
} }
pub const MIN: Self = Self {
year_month: YearMonthDuration::MIN,
day_time: DayTimeDuration::MIN,
};
pub const MAX: Self = Self {
year_month: YearMonthDuration::MAX,
day_time: DayTimeDuration::MAX,
};
} }
impl TryFrom<StdDuration> for Duration { impl TryFrom<StdDuration> for Duration {
type Error = DecimalOverflowError; type Error = DurationOverflowError;
#[inline] #[inline]
fn try_from(value: StdDuration) -> Result<Self, DecimalOverflowError> { fn try_from(value: StdDuration) -> Result<Self, DurationOverflowError> {
Ok(DayTimeDuration::try_from(value)?.into()) Ok(DayTimeDuration::try_from(value)?.into())
} }
} }
impl FromStr for Duration { impl FromStr for Duration {
type Err = XsdParseError; type Err = ParseDurationError;
fn from_str(input: &str) -> Result<Self, XsdParseError> { fn from_str(input: &str) -> Result<Self, ParseDurationError> {
parse_duration(input) let parts = ensure_complete(input, duration_parts)?;
if parts.year_month.is_none() && parts.day_time.is_none() {
return Err(ParseDurationError::msg("Empty duration"));
}
Ok(Self::new(
parts.year_month.unwrap_or(0),
parts.day_time.unwrap_or_default(),
))
} }
} }
@ -208,7 +231,7 @@ impl fmt::Display for Duration {
write!(f, "{}M", m.abs())?; write!(f, "{}M", m.abs())?;
} }
if s != 0.into() { if s != 0.into() {
write!(f, "{}S", s.abs())?; write!(f, "{}S", s.checked_abs().ok_or(fmt::Error)?)?;
} }
} }
} }
@ -282,7 +305,7 @@ impl YearMonthDuration {
} }
#[inline] #[inline]
pub(super) const fn all_months(self) -> i64 { pub(crate) const fn all_months(self) -> i64 {
self.months self.months
} }
@ -292,6 +315,8 @@ impl YearMonthDuration {
} }
/// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations) /// [op:add-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-yearMonthDurations)
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
let rhs = rhs.into(); let rhs = rhs.into();
@ -301,6 +326,8 @@ impl YearMonthDuration {
} }
/// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations) /// [op:subtract-yearMonthDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-yearMonthDurations)
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
let rhs = rhs.into(); let rhs = rhs.into();
@ -309,6 +336,9 @@ impl YearMonthDuration {
}) })
} }
/// Unary negation.
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
pub fn checked_neg(self) -> Option<Self> { pub fn checked_neg(self) -> Option<Self> {
Some(Self { Some(Self {
@ -321,6 +351,10 @@ impl YearMonthDuration {
pub fn is_identical_with(self, other: Self) -> bool { pub fn is_identical_with(self, other: Self) -> bool {
self == other self == other
} }
pub const MIN: Self = Self { months: i64::MIN };
pub const MAX: Self = Self { months: i64::MAX };
} }
impl From<YearMonthDuration> for Duration { impl From<YearMonthDuration> for Duration {
@ -334,23 +368,31 @@ impl From<YearMonthDuration> for Duration {
} }
impl TryFrom<Duration> for YearMonthDuration { impl TryFrom<Duration> for YearMonthDuration {
type Error = DecimalOverflowError; type Error = DurationOverflowError;
#[inline] #[inline]
fn try_from(value: Duration) -> Result<Self, DecimalOverflowError> { fn try_from(value: Duration) -> Result<Self, DurationOverflowError> {
if value.day_time == DayTimeDuration::default() { if value.day_time == DayTimeDuration::default() {
Ok(value.year_month) Ok(value.year_month)
} else { } else {
Err(DecimalOverflowError {}) Err(DurationOverflowError)
} }
} }
} }
impl FromStr for YearMonthDuration { impl FromStr for YearMonthDuration {
type Err = XsdParseError; type Err = ParseDurationError;
fn from_str(input: &str) -> Result<Self, XsdParseError> { fn from_str(input: &str) -> Result<Self, ParseDurationError> {
parse_year_month_duration(input) let parts = ensure_complete(input, duration_parts)?;
if parts.day_time.is_some() {
return Err(ParseDurationError::msg(
"There must not be any day or time component in a yearMonthDuration",
));
}
Ok(Self::new(parts.year_month.ok_or(
ParseDurationError::msg("No year and month values found"),
)?))
} }
} }
@ -455,6 +497,8 @@ impl DayTimeDuration {
} }
/// [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations) /// [op:add-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-add-dayTimeDurations)
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
let rhs = rhs.into(); let rhs = rhs.into();
@ -464,6 +508,8 @@ impl DayTimeDuration {
} }
/// [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations) /// [op:subtract-dayTimeDurations](https://www.w3.org/TR/xpath-functions-31/#func-subtract-dayTimeDurations)
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
let rhs = rhs.into(); let rhs = rhs.into();
@ -472,6 +518,9 @@ impl DayTimeDuration {
}) })
} }
/// Unary negation.
///
/// Returns `None` in case of overflow ([`FODT0002`](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002)).
#[inline] #[inline]
pub fn checked_neg(self) -> Option<Self> { pub fn checked_neg(self) -> Option<Self> {
Some(Self { Some(Self {
@ -484,6 +533,14 @@ impl DayTimeDuration {
pub fn is_identical_with(self, other: Self) -> bool { pub fn is_identical_with(self, other: Self) -> bool {
self == other self == other
} }
pub const MIN: Self = Self {
seconds: Decimal::MIN,
};
pub const MAX: Self = Self {
seconds: Decimal::MAX,
};
} }
impl From<DayTimeDuration> for Duration { impl From<DayTimeDuration> for Duration {
@ -497,65 +554,75 @@ impl From<DayTimeDuration> for Duration {
} }
impl TryFrom<Duration> for DayTimeDuration { impl TryFrom<Duration> for DayTimeDuration {
type Error = DecimalOverflowError; type Error = DurationOverflowError;
#[inline] #[inline]
fn try_from(value: Duration) -> Result<Self, DecimalOverflowError> { fn try_from(value: Duration) -> Result<Self, DurationOverflowError> {
if value.year_month == YearMonthDuration::default() { if value.year_month == YearMonthDuration::default() {
Ok(value.day_time) Ok(value.day_time)
} else { } else {
Err(DecimalOverflowError {}) Err(DurationOverflowError)
} }
} }
} }
impl TryFrom<StdDuration> for DayTimeDuration { impl TryFrom<StdDuration> for DayTimeDuration {
type Error = DecimalOverflowError; type Error = DurationOverflowError;
#[inline] #[inline]
fn try_from(value: StdDuration) -> Result<Self, DecimalOverflowError> { fn try_from(value: StdDuration) -> Result<Self, DurationOverflowError> {
Ok(Self { Ok(Self {
seconds: Decimal::new( seconds: Decimal::new(
i128::try_from(value.as_nanos()).map_err(|_| DecimalOverflowError)?, i128::try_from(value.as_nanos()).map_err(|_| DurationOverflowError)?,
9, 9,
)?, )
.map_err(|_| DurationOverflowError)?,
}) })
} }
} }
impl TryFrom<DayTimeDuration> for StdDuration { impl TryFrom<DayTimeDuration> for StdDuration {
type Error = DecimalOverflowError; type Error = DurationOverflowError;
#[inline] #[inline]
fn try_from(value: DayTimeDuration) -> Result<Self, DecimalOverflowError> { fn try_from(value: DayTimeDuration) -> Result<Self, DurationOverflowError> {
if value.seconds.is_negative() { if value.seconds.is_negative() {
return Err(DecimalOverflowError); return Err(DurationOverflowError);
} }
let secs = value.seconds.floor(); let secs = value.seconds.checked_floor().ok_or(DurationOverflowError)?;
let nanos = value let nanos = value
.seconds .seconds
.checked_sub(secs) .checked_sub(secs)
.ok_or(DecimalOverflowError)? .ok_or(DurationOverflowError)?
.checked_mul(1_000_000_000) .checked_mul(1_000_000_000)
.ok_or(DecimalOverflowError)? .ok_or(DurationOverflowError)?
.floor(); .checked_floor()
.ok_or(DurationOverflowError)?;
Ok(StdDuration::new( Ok(StdDuration::new(
secs.as_i128() secs.as_i128()
.try_into() .try_into()
.map_err(|_| DecimalOverflowError)?, .map_err(|_| DurationOverflowError)?,
nanos nanos
.as_i128() .as_i128()
.try_into() .try_into()
.map_err(|_| DecimalOverflowError)?, .map_err(|_| DurationOverflowError)?,
)) ))
} }
} }
impl FromStr for DayTimeDuration { impl FromStr for DayTimeDuration {
type Err = XsdParseError; type Err = ParseDurationError;
fn from_str(input: &str) -> Result<Self, XsdParseError> { fn from_str(input: &str) -> Result<Self, ParseDurationError> {
parse_day_time_duration(input) let parts = ensure_complete(input, duration_parts)?;
if parts.year_month.is_some() {
return Err(ParseDurationError::msg(
"There must not be any year or month component in a dayTimeDuration",
));
}
Ok(Self::new(parts.day_time.ok_or(ParseDurationError::msg(
"No day or time values found",
))?))
} }
} }
@ -622,12 +689,273 @@ impl PartialOrd<DayTimeDuration> for YearMonthDuration {
} }
} }
// [6] duYearFrag ::= unsignedNoDecimalPtNumeral 'Y'
// [7] duMonthFrag ::= unsignedNoDecimalPtNumeral 'M'
// [8] duDayFrag ::= unsignedNoDecimalPtNumeral 'D'
// [9] duHourFrag ::= unsignedNoDecimalPtNumeral 'H'
// [10] duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M'
// [11] duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S'
// [12] duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag
// [13] duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag)
// [14] duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag
// [15] durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag)
struct DurationParts {
year_month: Option<i64>,
day_time: Option<Decimal>,
}
fn duration_parts(input: &str) -> Result<(DurationParts, &str), ParseDurationError> {
// States
const START: u32 = 0;
const AFTER_YEAR: u32 = 1;
const AFTER_MONTH: u32 = 2;
const AFTER_DAY: u32 = 3;
const AFTER_T: u32 = 4;
const AFTER_HOUR: u32 = 5;
const AFTER_MINUTE: u32 = 6;
const AFTER_SECOND: u32 = 7;
let (is_negative, input) = if let Some(left) = input.strip_prefix('-') {
(true, left)
} else {
(false, input)
};
let mut input = expect_char(input, 'P', "Durations must start with 'P'")?;
let mut state = START;
let mut year_month: Option<i64> = None;
let mut day_time: Option<Decimal> = None;
while !input.is_empty() {
if let Some(left) = input.strip_prefix('T') {
if state >= AFTER_T {
return Err(ParseDurationError::msg("Duplicated time separator 'T'"));
}
state = AFTER_T;
input = left;
} else {
let (number_str, left) = decimal_prefix(input);
match left.chars().next() {
Some('Y') if state < AFTER_YEAR => {
year_month = Some(
year_month
.unwrap_or_default()
.checked_add(
apply_i64_neg(
i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
is_negative,
)?
.checked_mul(12)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_YEAR;
}
Some('M') if state < AFTER_MONTH => {
year_month = Some(
year_month
.unwrap_or_default()
.checked_add(apply_i64_neg(
i64::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
is_negative,
)?)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_MONTH;
}
Some('D') if state < AFTER_DAY => {
if number_str.contains('.') {
return Err(ParseDurationError::msg(
"Decimal numbers are not allowed for days",
));
}
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(
apply_decimal_neg(
Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
is_negative,
)?
.checked_mul(86400)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_DAY;
}
Some('H') if state == AFTER_T => {
if number_str.contains('.') {
return Err(ParseDurationError::msg(
"Decimal numbers are not allowed for hours",
));
}
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(
apply_decimal_neg(
Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
is_negative,
)?
.checked_mul(3600)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_HOUR;
}
Some('M') if (AFTER_T..AFTER_MINUTE).contains(&state) => {
if number_str.contains('.') {
return Err(ParseDurationError::msg(
"Decimal numbers are not allowed for minutes",
));
}
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(
apply_decimal_neg(
Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
is_negative,
)?
.checked_mul(60)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_MINUTE;
}
Some('S') if (AFTER_T..AFTER_SECOND).contains(&state) => {
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(apply_decimal_neg(
Decimal::from_str(number_str).map_err(|_| OVERFLOW_ERROR)?,
is_negative,
)?)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_SECOND;
}
Some(_) => return Err(ParseDurationError::msg("Unexpected type character")),
None => {
return Err(ParseDurationError::msg(
"Numbers in durations must be followed by a type character",
))
}
}
input = &left[1..];
}
}
Ok((
DurationParts {
year_month,
day_time,
},
input,
))
}
fn apply_i64_neg(value: i64, is_negative: bool) -> Result<i64, ParseDurationError> {
if is_negative {
value.checked_neg().ok_or(OVERFLOW_ERROR)
} else {
Ok(value)
}
}
fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result<Decimal, ParseDurationError> {
if is_negative {
value.checked_neg().ok_or(OVERFLOW_ERROR)
} else {
Ok(value)
}
}
fn ensure_complete<T>(
input: &str,
parse: impl FnOnce(&str) -> Result<(T, &str), ParseDurationError>,
) -> Result<T, ParseDurationError> {
let (result, left) = parse(input)?;
if !left.is_empty() {
return Err(ParseDurationError::msg("Unrecognized value suffix"));
}
Ok(result)
}
fn expect_char<'a>(
input: &'a str,
constant: char,
error_message: &'static str,
) -> Result<&'a str, ParseDurationError> {
if let Some(left) = input.strip_prefix(constant) {
Ok(left)
} else {
Err(ParseDurationError::msg(error_message))
}
}
fn decimal_prefix(input: &str) -> (&str, &str) {
let mut end = input.len();
let mut dot_seen = false;
for (i, c) in input.char_indices() {
if c.is_ascii_digit() {
// Ok
} else if c == '.' && !dot_seen {
dot_seen = true;
} else {
end = i;
break;
}
}
input.split_at(end)
}
/// A parsing error
#[derive(Debug, Clone)]
pub struct ParseDurationError {
msg: &'static str,
}
const OVERFLOW_ERROR: ParseDurationError = ParseDurationError {
msg: "Overflow error",
};
impl fmt::Display for ParseDurationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
impl ParseDurationError {
const fn msg(msg: &'static str) -> Self {
Self { msg }
}
}
impl Error for ParseDurationError {}
/// An overflow during [`Duration`]-related operations.
///
/// Matches XPath [`FODT0002` error](https://www.w3.org/TR/xpath-functions-31/#ERRFODT0002).
#[derive(Debug, Clone, Copy)]
pub struct DurationOverflowError;
impl fmt::Display for DurationOverflowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "overflow during xsd:duration computation")
}
}
impl Error for DurationOverflowError {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn from_str() -> Result<(), XsdParseError> { fn from_str() -> Result<(), ParseDurationError> {
let min = Duration::new(i64::MIN, Decimal::MIN); let min = Duration::new(i64::MIN, Decimal::MIN);
let max = Duration::new(i64::MAX, Decimal::MAX); let max = Duration::new(i64::MAX, Decimal::MAX);
@ -667,25 +995,52 @@ mod tests {
} }
#[test] #[test]
fn from_std() { fn from_std() -> Result<(), DurationOverflowError> {
assert_eq!( assert_eq!(
Duration::try_from(StdDuration::new(10, 10)) Duration::try_from(StdDuration::new(10, 10))?.to_string(),
.unwrap()
.to_string(),
"PT10.00000001S" "PT10.00000001S"
); );
Ok(())
} }
#[test] #[test]
fn to_std() -> Result<(), XsdParseError> { fn to_std() -> Result<(), Box<dyn Error>> {
let duration = StdDuration::try_from(DayTimeDuration::from_str("PT10.00000001S")?).unwrap(); let duration = StdDuration::try_from(DayTimeDuration::from_str("PT10.00000001S")?)?;
assert_eq!(duration.as_secs(), 10); assert_eq!(duration.as_secs(), 10);
assert_eq!(duration.subsec_nanos(), 10); assert_eq!(duration.subsec_nanos(), 10);
Ok(()) Ok(())
} }
#[test] #[test]
fn equals() -> Result<(), XsdParseError> { fn to_be_bytes() {
assert_eq!(
Duration::from_be_bytes(Duration::MIN.to_be_bytes()),
Duration::MIN
);
assert_eq!(
Duration::from_be_bytes(Duration::MAX.to_be_bytes()),
Duration::MAX
);
assert_eq!(
YearMonthDuration::from_be_bytes(YearMonthDuration::MIN.to_be_bytes()),
YearMonthDuration::MIN
);
assert_eq!(
YearMonthDuration::from_be_bytes(YearMonthDuration::MAX.to_be_bytes()),
YearMonthDuration::MAX
);
assert_eq!(
DayTimeDuration::from_be_bytes(DayTimeDuration::MIN.to_be_bytes()),
DayTimeDuration::MIN
);
assert_eq!(
DayTimeDuration::from_be_bytes(DayTimeDuration::MAX.to_be_bytes()),
DayTimeDuration::MAX
);
}
#[test]
fn equals() -> Result<(), ParseDurationError> {
assert_eq!( assert_eq!(
YearMonthDuration::from_str("P1Y")?, YearMonthDuration::from_str("P1Y")?,
YearMonthDuration::from_str("P12M")? YearMonthDuration::from_str("P12M")?
@ -730,7 +1085,24 @@ mod tests {
} }
#[test] #[test]
fn years() -> Result<(), XsdParseError> { #[allow(clippy::neg_cmp_op_on_partial_ord)]
fn cmp() -> Result<(), ParseDurationError> {
assert!(Duration::from_str("P1Y1D")? < Duration::from_str("P13MT25H")?);
assert!(YearMonthDuration::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?);
assert!(Duration::from_str("P1Y")? < YearMonthDuration::from_str("P13M")?);
assert!(YearMonthDuration::from_str("P1Y")? < Duration::from_str("P13M")?);
assert!(DayTimeDuration::from_str("P1D")? < DayTimeDuration::from_str("PT25H")?);
assert!(DayTimeDuration::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?);
assert!(DayTimeDuration::from_str("PT1M")? < DayTimeDuration::from_str("PT61S")?);
assert!(Duration::from_str("PT1H")? < DayTimeDuration::from_str("PT61M")?);
assert!(DayTimeDuration::from_str("PT1H")? < Duration::from_str("PT61M")?);
assert!(YearMonthDuration::from_str("P1M")? < DayTimeDuration::from_str("P40D")?);
assert!(DayTimeDuration::from_str("P25D")? < YearMonthDuration::from_str("P1M")?);
Ok(())
}
#[test]
fn years() -> Result<(), ParseDurationError> {
assert_eq!(Duration::from_str("P20Y15M")?.years(), 21); assert_eq!(Duration::from_str("P20Y15M")?.years(), 21);
assert_eq!(Duration::from_str("-P15M")?.years(), -1); assert_eq!(Duration::from_str("-P15M")?.years(), -1);
assert_eq!(Duration::from_str("-P2DT15H")?.years(), 0); assert_eq!(Duration::from_str("-P2DT15H")?.years(), 0);
@ -738,7 +1110,7 @@ mod tests {
} }
#[test] #[test]
fn months() -> Result<(), XsdParseError> { fn months() -> Result<(), ParseDurationError> {
assert_eq!(Duration::from_str("P20Y15M")?.months(), 3); assert_eq!(Duration::from_str("P20Y15M")?.months(), 3);
assert_eq!(Duration::from_str("-P20Y18M")?.months(), -6); assert_eq!(Duration::from_str("-P20Y18M")?.months(), -6);
assert_eq!(Duration::from_str("-P2DT15H0M0S")?.months(), 0); assert_eq!(Duration::from_str("-P2DT15H0M0S")?.months(), 0);
@ -746,7 +1118,7 @@ mod tests {
} }
#[test] #[test]
fn days() -> Result<(), XsdParseError> { fn days() -> Result<(), ParseDurationError> {
assert_eq!(Duration::from_str("P3DT10H")?.days(), 3); assert_eq!(Duration::from_str("P3DT10H")?.days(), 3);
assert_eq!(Duration::from_str("P3DT55H")?.days(), 5); assert_eq!(Duration::from_str("P3DT55H")?.days(), 5);
assert_eq!(Duration::from_str("P3Y5M")?.days(), 0); assert_eq!(Duration::from_str("P3Y5M")?.days(), 0);
@ -754,7 +1126,7 @@ mod tests {
} }
#[test] #[test]
fn hours() -> Result<(), XsdParseError> { fn hours() -> Result<(), ParseDurationError> {
assert_eq!(Duration::from_str("P3DT10H")?.hours(), 10); assert_eq!(Duration::from_str("P3DT10H")?.hours(), 10);
assert_eq!(Duration::from_str("P3DT12H32M12S")?.hours(), 12); assert_eq!(Duration::from_str("P3DT12H32M12S")?.hours(), 12);
assert_eq!(Duration::from_str("PT123H")?.hours(), 3); assert_eq!(Duration::from_str("PT123H")?.hours(), 3);
@ -763,14 +1135,14 @@ mod tests {
} }
#[test] #[test]
fn minutes() -> Result<(), XsdParseError> { fn minutes() -> Result<(), ParseDurationError> {
assert_eq!(Duration::from_str("P3DT10H")?.minutes(), 0); assert_eq!(Duration::from_str("P3DT10H")?.minutes(), 0);
assert_eq!(Duration::from_str("-P5DT12H30M")?.minutes(), -30); assert_eq!(Duration::from_str("-P5DT12H30M")?.minutes(), -30);
Ok(()) Ok(())
} }
#[test] #[test]
fn seconds() -> Result<(), XsdParseError> { fn seconds() -> Result<(), Box<dyn Error>> {
assert_eq!( assert_eq!(
Duration::from_str("P3DT10H12.5S")?.seconds(), Duration::from_str("P3DT10H12.5S")?.seconds(),
Decimal::from_str("12.5")? Decimal::from_str("12.5")?
@ -783,7 +1155,7 @@ mod tests {
} }
#[test] #[test]
fn add() -> Result<(), XsdParseError> { fn add() -> Result<(), ParseDurationError> {
assert_eq!( assert_eq!(
Duration::from_str("P2Y11M")?.checked_add(Duration::from_str("P3Y3M")?), Duration::from_str("P2Y11M")?.checked_add(Duration::from_str("P3Y3M")?),
Some(Duration::from_str("P6Y2M")?) Some(Duration::from_str("P6Y2M")?)
@ -796,7 +1168,7 @@ mod tests {
} }
#[test] #[test]
fn sub() -> Result<(), XsdParseError> { fn sub() -> Result<(), ParseDurationError> {
assert_eq!( assert_eq!(
Duration::from_str("P2Y11M")?.checked_sub(Duration::from_str("P3Y3M")?), Duration::from_str("P2Y11M")?.checked_sub(Duration::from_str("P3Y3M")?),
Some(Duration::from_str("-P4M")?) Some(Duration::from_str("-P4M")?)
@ -809,7 +1181,7 @@ mod tests {
} }
#[test] #[test]
fn minimally_conformant() -> Result<(), XsdParseError> { fn minimally_conformant() -> Result<(), ParseDurationError> {
// All minimally conforming processors must support fractional-second duration values // All minimally conforming processors must support fractional-second duration values
// to milliseconds (i.e. those expressible with three fraction digits). // to milliseconds (i.e. those expressible with three fraction digits).
assert_eq!(Duration::from_str("PT0.001S")?.to_string(), "PT0.001S"); assert_eq!(Duration::from_str("PT0.001S")?.to_string(), "PT0.001S");

@ -1,4 +1,5 @@
use crate::{Boolean, Decimal, DecimalOverflowError, Double, Float}; use crate::{Boolean, Decimal, Double, Float};
use std::error::Error;
use std::fmt; use std::fmt;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::str::FromStr; use std::str::FromStr;
@ -28,6 +29,8 @@ impl Integer {
} }
/// [op:numeric-add](https://www.w3.org/TR/xpath-functions-31/#func-numeric-add) /// [op:numeric-add](https://www.w3.org/TR/xpath-functions-31/#func-numeric-add)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_add(self, rhs: impl Into<Self>) -> Option<Self> {
@ -37,6 +40,8 @@ impl Integer {
} }
/// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract) /// [op:numeric-subtract](https://www.w3.org/TR/xpath-functions-31/#func-numeric-subtract)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_sub(self, rhs: impl Into<Self>) -> Option<Self> {
@ -46,6 +51,8 @@ impl Integer {
} }
/// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply) /// [op:numeric-multiply](https://www.w3.org/TR/xpath-functions-31/#func-numeric-multiply)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_mul(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_mul(self, rhs: impl Into<Self>) -> Option<Self> {
@ -55,6 +62,8 @@ impl Integer {
} }
/// [op:numeric-integer-divide](https://www.w3.org/TR/xpath-functions-31/#func-numeric-integer-divide) /// [op:numeric-integer-divide](https://www.w3.org/TR/xpath-functions-31/#func-numeric-integer-divide)
///
/// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_div(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_div(self, rhs: impl Into<Self>) -> Option<Self> {
@ -64,6 +73,8 @@ impl Integer {
} }
/// [op:numeric-mod](https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod) /// [op:numeric-mod](https://www.w3.org/TR/xpath-functions-31/#func-numeric-mod)
///
/// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_rem(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_rem(self, rhs: impl Into<Self>) -> Option<Self> {
@ -72,6 +83,9 @@ impl Integer {
}) })
} }
/// Euclidean remainder
///
/// Returns `None` in case of division by 0 ([FOAR0001](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0001)) or overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_rem_euclid(self, rhs: impl Into<Self>) -> Option<Self> { pub fn checked_rem_euclid(self, rhs: impl Into<Self>) -> Option<Self> {
@ -81,6 +95,8 @@ impl Integer {
} }
/// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus) /// [op:numeric-unary-minus](https://www.w3.org/TR/xpath-functions-31/#func-numeric-unary-minus)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub fn checked_neg(self) -> Option<Self> { pub fn checked_neg(self) -> Option<Self> {
@ -90,12 +106,14 @@ impl Integer {
} }
/// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs) /// [fn:abs](https://www.w3.org/TR/xpath-functions-31/#func-abs)
///
/// Returns `None` in case of overflow ([FOAR0002](https://www.w3.org/TR/xpath-functions-31/#ERRFOAR0002)).
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn abs(self) -> Self { pub fn checked_abs(self) -> Option<Self> {
Self { Some(Self {
value: self.value.abs(), value: self.value.checked_abs()?,
} })
} }
#[inline] #[inline]
@ -223,23 +241,41 @@ impl fmt::Display for Integer {
} }
impl TryFrom<Float> for Integer { impl TryFrom<Float> for Integer {
type Error = DecimalOverflowError; type Error = TooLargeForIntegerError;
#[inline] #[inline]
fn try_from(value: Float) -> Result<Self, DecimalOverflowError> { fn try_from(value: Float) -> Result<Self, TooLargeForIntegerError> {
Decimal::try_from(value)?.try_into() Decimal::try_from(value)
.map_err(|_| TooLargeForIntegerError)?
.try_into()
} }
} }
impl TryFrom<Double> for Integer { impl TryFrom<Double> for Integer {
type Error = DecimalOverflowError; type Error = TooLargeForIntegerError;
#[inline] #[inline]
fn try_from(value: Double) -> Result<Self, DecimalOverflowError> { fn try_from(value: Double) -> Result<Self, TooLargeForIntegerError> {
Decimal::try_from(value)?.try_into() Decimal::try_from(value)
.map_err(|_| TooLargeForIntegerError)?
.try_into()
}
}
/// The input is too large to fit into an [`Integer`].
///
/// Matches XPath [`FOCA0003` error](https://www.w3.org/TR/xpath-functions-31/#ERRFOCA0003).
#[derive(Debug, Clone, Copy)]
pub struct TooLargeForIntegerError;
impl fmt::Display for TooLargeForIntegerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Value too large for xsd:integer internal representation")
} }
} }
impl Error for TooLargeForIntegerError {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -278,7 +314,8 @@ mod tests {
.unwrap() .unwrap()
.checked_sub(Integer::from_str("1672507300000")?) .checked_sub(Integer::from_str("1672507300000")?)
.unwrap() .unwrap()
.abs() .checked_abs()
.unwrap()
< Integer::from(1_000_000) < Integer::from(1_000_000)
); );
Ok(()) Ok(())
@ -303,7 +340,8 @@ mod tests {
.unwrap() .unwrap()
.checked_sub(Integer::from_str("1672507300000").unwrap()) .checked_sub(Integer::from_str("1672507300000").unwrap())
.unwrap() .unwrap()
.abs() .checked_abs()
.unwrap()
< Integer::from(10) < Integer::from(10)
); );
assert!(Integer::try_from(Double::from(f64::NAN)).is_err()); assert!(Integer::try_from(Double::from(f64::NAN)).is_err());

@ -11,15 +11,16 @@ mod double;
mod duration; mod duration;
mod float; mod float;
mod integer; mod integer;
mod parser;
pub use self::boolean::Boolean; pub use self::boolean::Boolean;
pub use self::date_time::{ pub use self::date_time::{
Date, DateTime, DateTimeError, GDay, GMonth, GMonthDay, GYear, GYearMonth, Time, TimezoneOffset, Date, DateTime, DateTimeOverflowError, GDay, GMonth, GMonthDay, GYear, GYearMonth,
InvalidTimezoneError, ParseDateTimeError, Time, TimezoneOffset,
}; };
pub use self::decimal::{Decimal, DecimalOverflowError, ParseDecimalError}; pub use self::decimal::{Decimal, ParseDecimalError, TooLargeForDecimalError};
pub use self::double::Double; pub use self::double::Double;
pub use self::duration::{DayTimeDuration, Duration, YearMonthDuration}; pub use self::duration::{
DayTimeDuration, Duration, DurationOverflowError, ParseDurationError, YearMonthDuration,
};
pub use self::float::Float; pub use self::float::Float;
pub use self::integer::Integer; pub use self::integer::{Integer, TooLargeForIntegerError};
pub use self::parser::XsdParseError;

@ -1,626 +0,0 @@
use super::date_time::{DateTimeError, GDay, GMonth, GMonthDay, GYear, GYearMonth, TimezoneOffset};
use super::decimal::ParseDecimalError;
use super::duration::{DayTimeDuration, YearMonthDuration};
use super::*;
use std::error::Error;
use std::fmt;
use std::num::ParseIntError;
use std::str::FromStr;
/// A parsing error
#[derive(Debug, Clone)]
pub struct XsdParseError {
kind: XsdParseErrorKind,
}
#[derive(Debug, Clone)]
enum XsdParseErrorKind {
ParseInt(ParseIntError),
ParseDecimal(ParseDecimalError),
DateTime(DateTimeError),
Message(&'static str),
}
const OVERFLOW_ERROR: XsdParseError = XsdParseError {
kind: XsdParseErrorKind::Message("Overflow error"),
};
impl fmt::Display for XsdParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
XsdParseErrorKind::ParseInt(error) => {
write!(f, "Error while parsing integer: {error}")
}
XsdParseErrorKind::ParseDecimal(error) => {
write!(f, "Error while parsing decimal: {error}")
}
XsdParseErrorKind::DateTime(error) => error.fmt(f),
XsdParseErrorKind::Message(msg) => write!(f, "{msg}"),
}
}
}
impl XsdParseError {
const fn msg(message: &'static str) -> Self {
Self {
kind: XsdParseErrorKind::Message(message),
}
}
}
impl Error for XsdParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.kind {
XsdParseErrorKind::ParseInt(error) => Some(error),
XsdParseErrorKind::ParseDecimal(error) => Some(error),
XsdParseErrorKind::DateTime(error) => Some(error),
XsdParseErrorKind::Message(_) => None,
}
}
}
impl From<ParseIntError> for XsdParseError {
fn from(error: ParseIntError) -> Self {
Self {
kind: XsdParseErrorKind::ParseInt(error),
}
}
}
impl From<ParseDecimalError> for XsdParseError {
fn from(error: ParseDecimalError) -> Self {
Self {
kind: XsdParseErrorKind::ParseDecimal(error),
}
}
}
impl From<DateTimeError> for XsdParseError {
fn from(error: DateTimeError) -> Self {
Self {
kind: XsdParseErrorKind::DateTime(error),
}
}
}
// [6] duYearFrag ::= unsignedNoDecimalPtNumeral 'Y'
// [7] duMonthFrag ::= unsignedNoDecimalPtNumeral 'M'
// [8] duDayFrag ::= unsignedNoDecimalPtNumeral 'D'
// [9] duHourFrag ::= unsignedNoDecimalPtNumeral 'H'
// [10] duMinuteFrag ::= unsignedNoDecimalPtNumeral 'M'
// [11] duSecondFrag ::= (unsignedNoDecimalPtNumeral | unsignedDecimalPtNumeral) 'S'
// [12] duYearMonthFrag ::= (duYearFrag duMonthFrag?) | duMonthFrag
// [13] duTimeFrag ::= 'T' ((duHourFrag duMinuteFrag? duSecondFrag?) | (duMinuteFrag duSecondFrag?) | duSecondFrag)
// [14] duDayTimeFrag ::= (duDayFrag duTimeFrag?) | duTimeFrag
// [15] durationLexicalRep ::= '-'? 'P' ((duYearMonthFrag duDayTimeFrag?) | duDayTimeFrag)
struct DurationParts {
year_month: Option<i64>,
day_time: Option<Decimal>,
}
fn duration_parts(input: &str) -> Result<(DurationParts, &str), XsdParseError> {
// States
const START: u32 = 0;
const AFTER_YEAR: u32 = 1;
const AFTER_MONTH: u32 = 2;
const AFTER_DAY: u32 = 3;
const AFTER_T: u32 = 4;
const AFTER_HOUR: u32 = 5;
const AFTER_MINUTE: u32 = 6;
const AFTER_SECOND: u32 = 7;
let (is_negative, input) = if let Some(left) = input.strip_prefix('-') {
(true, left)
} else {
(false, input)
};
let mut input = expect_char(input, 'P', "Durations must start with 'P'")?;
let mut state = START;
let mut year_month: Option<i64> = None;
let mut day_time: Option<Decimal> = None;
while !input.is_empty() {
if let Some(left) = input.strip_prefix('T') {
if state >= AFTER_T {
return Err(XsdParseError::msg("Duplicated time separator 'T'"));
}
state = AFTER_T;
input = left;
} else {
let (number_str, left) = decimal_prefix(input);
match left.chars().next() {
Some('Y') if state < AFTER_YEAR => {
year_month = Some(
year_month
.unwrap_or_default()
.checked_add(
apply_i64_neg(i64::from_str(number_str)?, is_negative)?
.checked_mul(12)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_YEAR;
}
Some('M') if state < AFTER_MONTH => {
year_month = Some(
year_month
.unwrap_or_default()
.checked_add(apply_i64_neg(i64::from_str(number_str)?, is_negative)?)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_MONTH;
}
Some('D') if state < AFTER_DAY => {
if number_str.contains('.') {
return Err(XsdParseError::msg(
"Decimal numbers are not allowed for days",
));
}
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
.checked_mul(86400)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_DAY;
}
Some('H') if state == AFTER_T => {
if number_str.contains('.') {
return Err(XsdParseError::msg(
"Decimal numbers are not allowed for hours",
));
}
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
.checked_mul(3600)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_HOUR;
}
Some('M') if (AFTER_T..AFTER_MINUTE).contains(&state) => {
if number_str.contains('.') {
return Err(XsdParseError::msg(
"Decimal numbers are not allowed for minutes",
));
}
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(
apply_decimal_neg(Decimal::from_str(number_str)?, is_negative)?
.checked_mul(60)
.ok_or(OVERFLOW_ERROR)?,
)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_MINUTE;
}
Some('S') if (AFTER_T..AFTER_SECOND).contains(&state) => {
day_time = Some(
day_time
.unwrap_or_default()
.checked_add(apply_decimal_neg(
Decimal::from_str(number_str)?,
is_negative,
)?)
.ok_or(OVERFLOW_ERROR)?,
);
state = AFTER_SECOND;
}
Some(_) => return Err(XsdParseError::msg("Unexpected type character")),
None => {
return Err(XsdParseError::msg(
"Numbers in durations must be followed by a type character",
))
}
}
input = &left[1..];
}
}
Ok((
DurationParts {
year_month,
day_time,
},
input,
))
}
fn apply_i64_neg(value: i64, is_negative: bool) -> Result<i64, XsdParseError> {
if is_negative {
value.checked_neg().ok_or(OVERFLOW_ERROR)
} else {
Ok(value)
}
}
fn apply_decimal_neg(value: Decimal, is_negative: bool) -> Result<Decimal, XsdParseError> {
if is_negative {
value.checked_neg().ok_or(OVERFLOW_ERROR)
} else {
Ok(value)
}
}
pub fn parse_duration(input: &str) -> Result<Duration, XsdParseError> {
let parts = ensure_complete(input, duration_parts)?;
if parts.year_month.is_none() && parts.day_time.is_none() {
return Err(XsdParseError::msg("Empty duration"));
}
Ok(Duration::new(
parts.year_month.unwrap_or(0),
parts.day_time.unwrap_or_default(),
))
}
pub fn parse_year_month_duration(input: &str) -> Result<YearMonthDuration, XsdParseError> {
let parts = ensure_complete(input, duration_parts)?;
if parts.day_time.is_some() {
return Err(XsdParseError::msg(
"There must not be any day or time component in a yearMonthDuration",
));
}
Ok(YearMonthDuration::new(parts.year_month.ok_or(
XsdParseError::msg("No year and month values found"),
)?))
}
pub fn parse_day_time_duration(input: &str) -> Result<DayTimeDuration, XsdParseError> {
let parts = ensure_complete(input, duration_parts)?;
if parts.year_month.is_some() {
return Err(XsdParseError::msg(
"There must not be any year or month component in a dayTimeDuration",
));
}
Ok(DayTimeDuration::new(parts.day_time.ok_or(
XsdParseError::msg("No day or time values found"),
)?))
}
// [16] dateTimeLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag 'T' ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
fn date_time_lexical_rep(input: &str) -> Result<(DateTime, &str), XsdParseError> {
let (year, input) = year_frag(input)?;
let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
let (month, input) = month_frag(input)?;
let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
let (day, input) = day_frag(input)?;
let input = expect_char(input, 'T', "The date and time must be separated by 'T'")?;
let (hour, input) = hour_frag(input)?;
let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
let (minute, input) = minute_frag(input)?;
let input = expect_char(
input,
':',
"The minutes and seconds must be separated by ':'",
)?;
let (second, input) = second_frag(input)?;
// We validate 24:00:00
if hour == 24 && minute != 0 && second != Decimal::from(0) {
return Err(XsdParseError::msg(
"Times are not allowed to be after 24:00:00",
));
}
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((
DateTime::new(year, month, day, hour, minute, second, timezone_offset)?,
input,
))
}
pub fn parse_date_time(input: &str) -> Result<DateTime, XsdParseError> {
ensure_complete(input, date_time_lexical_rep)
}
// [17] timeLexicalRep ::= ((hourFrag ':' minuteFrag ':' secondFrag) | endOfDayFrag) timezoneFrag?
fn time_lexical_rep(input: &str) -> Result<(Time, &str), XsdParseError> {
let (hour, input) = hour_frag(input)?;
let input = expect_char(input, ':', "The hours and minutes must be separated by ':'")?;
let (minute, input) = minute_frag(input)?;
let input = expect_char(
input,
':',
"The minutes and seconds must be separated by ':'",
)?;
let (second, input) = second_frag(input)?;
// We validate 24:00:00
if hour == 24 && minute != 0 && second != Decimal::from(0) {
return Err(XsdParseError::msg(
"Times are not allowed to be after 24:00:00",
));
}
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((Time::new(hour, minute, second, timezone_offset)?, input))
}
pub fn parse_time(input: &str) -> Result<Time, XsdParseError> {
ensure_complete(input, time_lexical_rep)
}
// [18] dateLexicalRep ::= yearFrag '-' monthFrag '-' dayFrag timezoneFrag? Constraint: Day-of-month Representations
fn date_lexical_rep(input: &str) -> Result<(Date, &str), XsdParseError> {
let (year, input) = year_frag(input)?;
let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
let (month, input) = month_frag(input)?;
let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
let (day, input) = day_frag(input)?;
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((Date::new(year, month, day, timezone_offset)?, input))
}
pub fn parse_date(input: &str) -> Result<Date, XsdParseError> {
ensure_complete(input, date_lexical_rep)
}
// [19] gYearMonthLexicalRep ::= yearFrag '-' monthFrag timezoneFrag?
fn g_year_month_lexical_rep(input: &str) -> Result<(GYearMonth, &str), XsdParseError> {
let (year, input) = year_frag(input)?;
let input = expect_char(input, '-', "The year and month must be separated by '-'")?;
let (month, input) = month_frag(input)?;
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((GYearMonth::new(year, month, timezone_offset)?, input))
}
pub fn parse_g_year_month(input: &str) -> Result<GYearMonth, XsdParseError> {
ensure_complete(input, g_year_month_lexical_rep)
}
// [20] gYearLexicalRep ::= yearFrag timezoneFrag?
fn g_year_lexical_rep(input: &str) -> Result<(GYear, &str), XsdParseError> {
let (year, input) = year_frag(input)?;
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((GYear::new(year, timezone_offset)?, input))
}
pub fn parse_g_year(input: &str) -> Result<GYear, XsdParseError> {
ensure_complete(input, g_year_lexical_rep)
}
// [21] gMonthDayLexicalRep ::= '--' monthFrag '-' dayFrag timezoneFrag? Constraint: Day-of-month Representations
fn g_month_day_lexical_rep(input: &str) -> Result<(GMonthDay, &str), XsdParseError> {
let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
let input = expect_char(input, '-', "gMonthDay values must start with '--'")?;
let (month, input) = month_frag(input)?;
let input = expect_char(input, '-', "The month and day must be separated by '-'")?;
let (day, input) = day_frag(input)?;
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((GMonthDay::new(month, day, timezone_offset)?, input))
}
pub fn parse_g_month_day(input: &str) -> Result<GMonthDay, XsdParseError> {
ensure_complete(input, g_month_day_lexical_rep)
}
// [22] gDayLexicalRep ::= '---' dayFrag timezoneFrag?
fn g_day_lexical_rep(input: &str) -> Result<(GDay, &str), XsdParseError> {
let input = expect_char(input, '-', "gDay values must start with '---'")?;
let input = expect_char(input, '-', "gDay values must start with '---'")?;
let input = expect_char(input, '-', "gDay values must start with '---'")?;
let (day, input) = day_frag(input)?;
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((GDay::new(day, timezone_offset)?, input))
}
pub fn parse_g_day(input: &str) -> Result<GDay, XsdParseError> {
ensure_complete(input, g_day_lexical_rep)
}
// [23] gMonthLexicalRep ::= '--' monthFrag timezoneFrag?
fn g_month_lexical_rep(input: &str) -> Result<(GMonth, &str), XsdParseError> {
let input = expect_char(input, '-', "gMonth values must start with '--'")?;
let input = expect_char(input, '-', "gMonth values must start with '--'")?;
let (month, input) = month_frag(input)?;
let (timezone_offset, input) = optional_end(input, timezone_frag)?;
Ok((GMonth::new(month, timezone_offset)?, input))
}
pub fn parse_g_month(input: &str) -> Result<GMonth, XsdParseError> {
ensure_complete(input, g_month_lexical_rep)
}
// [56] yearFrag ::= '-'? (([1-9] digit digit digit+)) | ('0' digit digit digit))
fn year_frag(input: &str) -> Result<(i64, &str), XsdParseError> {
let (sign, input) = if let Some(left) = input.strip_prefix('-') {
(-1, left)
} else {
(1, input)
};
let (number_str, input) = integer_prefix(input);
if number_str.len() < 4 {
return Err(XsdParseError::msg("The year should be encoded on 4 digits"));
}
if number_str.len() > 4 && number_str.starts_with('0') {
return Err(XsdParseError::msg(
"The years value must not start with 0 if it can be encoded in at least 4 digits",
));
}
let number = i64::from_str(number_str)?;
Ok((sign * number, input))
}
// [57] monthFrag ::= ('0' [1-9]) | ('1' [0-2])
fn month_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
let (number_str, input) = integer_prefix(input);
if number_str.len() != 2 {
return Err(XsdParseError::msg("Month must be encoded with two digits"));
}
let number = u8::from_str(number_str)?;
if !(1..=12).contains(&number) {
return Err(XsdParseError::msg("Month must be between 01 and 12"));
}
Ok((number, input))
}
// [58] dayFrag ::= ('0' [1-9]) | ([12] digit) | ('3' [01])
fn day_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
let (number_str, input) = integer_prefix(input);
if number_str.len() != 2 {
return Err(XsdParseError::msg("Day must be encoded with two digits"));
}
let number = u8::from_str(number_str)?;
if !(1..=31).contains(&number) {
return Err(XsdParseError::msg("Day must be between 01 and 31"));
}
Ok((number, input))
}
// [59] hourFrag ::= ([01] digit) | ('2' [0-3])
// We also allow 24 for ease of parsing
fn hour_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
let (number_str, input) = integer_prefix(input);
if number_str.len() != 2 {
return Err(XsdParseError::msg("Hours must be encoded with two digits"));
}
let number = u8::from_str(number_str)?;
if !(0..=24).contains(&number) {
return Err(XsdParseError::msg("Hours must be between 00 and 24"));
}
Ok((number, input))
}
// [60] minuteFrag ::= [0-5] digit
fn minute_frag(input: &str) -> Result<(u8, &str), XsdParseError> {
let (number_str, input) = integer_prefix(input);
if number_str.len() != 2 {
return Err(XsdParseError::msg(
"Minutes must be encoded with two digits",
));
}
let number = u8::from_str(number_str)?;
if !(0..=59).contains(&number) {
return Err(XsdParseError::msg("Minutes must be between 00 and 59"));
}
Ok((number, input))
}
// [61] secondFrag ::= ([0-5] digit) ('.' digit+)?
fn second_frag(input: &str) -> Result<(Decimal, &str), XsdParseError> {
let (number_str, input) = decimal_prefix(input);
let (before_dot_str, _) = number_str.split_once('.').unwrap_or((number_str, ""));
if before_dot_str.len() != 2 {
return Err(XsdParseError::msg(
"Seconds must be encoded with two digits",
));
}
let number = Decimal::from_str(number_str)?;
if number < Decimal::from(0) || number >= Decimal::from(60) {
return Err(XsdParseError::msg("Seconds must be between 00 and 60"));
}
if number_str.ends_with('.') {
return Err(XsdParseError::msg(
"Seconds are not allowed to end with a dot",
));
}
Ok((number, input))
}
// [63] timezoneFrag ::= 'Z' | ('+' | '-') (('0' digit | '1' [0-3]) ':' minuteFrag | '14:00')
fn timezone_frag(input: &str) -> Result<(TimezoneOffset, &str), XsdParseError> {
if let Some(left) = input.strip_prefix('Z') {
return Ok((TimezoneOffset::UTC, left));
}
let (sign, input) = if let Some(left) = input.strip_prefix('-') {
(-1, left)
} else if let Some(left) = input.strip_prefix('+') {
(1, left)
} else {
(1, input)
};
let (hour_str, input) = integer_prefix(input);
if hour_str.len() != 2 {
return Err(XsdParseError::msg(
"The timezone hours must be encoded with two digits",
));
}
let hours = i16::from_str(hour_str)?;
let input = expect_char(
input,
':',
"The timezone hours and minutes must be separated by ':'",
)?;
let (minutes, input) = minute_frag(input)?;
if hours > 13 && !(hours == 14 && minutes == 0) {
return Err(XsdParseError::msg(
"The timezone hours must be between 00 and 13",
));
}
Ok((
TimezoneOffset::new(sign * (hours * 60 + i16::from(minutes)))?,
input,
))
}
fn ensure_complete<T>(
input: &str,
parse: impl FnOnce(&str) -> Result<(T, &str), XsdParseError>,
) -> Result<T, XsdParseError> {
let (result, left) = parse(input)?;
if !left.is_empty() {
return Err(XsdParseError::msg("Unrecognized value suffix"));
}
Ok(result)
}
fn expect_char<'a>(
input: &'a str,
constant: char,
error_message: &'static str,
) -> Result<&'a str, XsdParseError> {
if let Some(left) = input.strip_prefix(constant) {
Ok(left)
} else {
Err(XsdParseError::msg(error_message))
}
}
fn integer_prefix(input: &str) -> (&str, &str) {
let mut end = input.len();
for (i, c) in input.char_indices() {
if !c.is_ascii_digit() {
end = i;
break;
}
}
input.split_at(end)
}
fn decimal_prefix(input: &str) -> (&str, &str) {
let mut end = input.len();
let mut dot_seen = false;
for (i, c) in input.char_indices() {
if c.is_ascii_digit() {
// Ok
} else if c == '.' && !dot_seen {
dot_seen = true;
} else {
end = i;
break;
}
}
input.split_at(end)
}
fn optional_end<T>(
input: &str,
parse: impl FnOnce(&str) -> Result<(T, &str), XsdParseError>,
) -> Result<(Option<T>, &str), XsdParseError> {
Ok(if input.is_empty() {
(None, input)
} else {
let (result, input) = parse(input)?;
(Some(result), input)
})
}

@ -142,7 +142,7 @@ impl SimpleEvaluator {
Self { Self {
dataset, dataset,
base_iri, base_iri,
now: DateTime::now().unwrap(), now: DateTime::now(),
service_handler, service_handler,
custom_functions, custom_functions,
run_stats, run_stats,
@ -1605,8 +1605,8 @@ impl SimpleEvaluator {
stat_children, stat_children,
); );
Rc::new(move |tuple| match e(tuple)? { Rc::new(move |tuple| match e(tuple)? {
EncodedTerm::IntegerLiteral(value) => Some(value.abs().into()), EncodedTerm::IntegerLiteral(value) => Some(value.checked_abs()?.into()),
EncodedTerm::DecimalLiteral(value) => Some(value.abs().into()), EncodedTerm::DecimalLiteral(value) => Some(value.checked_abs()?.into()),
EncodedTerm::FloatLiteral(value) => Some(value.abs().into()), EncodedTerm::FloatLiteral(value) => Some(value.abs().into()),
EncodedTerm::DoubleLiteral(value) => Some(value.abs().into()), EncodedTerm::DoubleLiteral(value) => Some(value.abs().into()),
_ => None, _ => None,
@ -1620,7 +1620,9 @@ impl SimpleEvaluator {
); );
Rc::new(move |tuple| match e(tuple)? { Rc::new(move |tuple| match e(tuple)? {
EncodedTerm::IntegerLiteral(value) => Some(value.into()), EncodedTerm::IntegerLiteral(value) => Some(value.into()),
EncodedTerm::DecimalLiteral(value) => Some(value.ceil().into()), EncodedTerm::DecimalLiteral(value) => {
Some(value.checked_ceil()?.into())
}
EncodedTerm::FloatLiteral(value) => Some(value.ceil().into()), EncodedTerm::FloatLiteral(value) => Some(value.ceil().into()),
EncodedTerm::DoubleLiteral(value) => Some(value.ceil().into()), EncodedTerm::DoubleLiteral(value) => Some(value.ceil().into()),
_ => None, _ => None,
@ -1634,7 +1636,9 @@ impl SimpleEvaluator {
); );
Rc::new(move |tuple| match e(tuple)? { Rc::new(move |tuple| match e(tuple)? {
EncodedTerm::IntegerLiteral(value) => Some(value.into()), EncodedTerm::IntegerLiteral(value) => Some(value.into()),
EncodedTerm::DecimalLiteral(value) => Some(value.floor().into()), EncodedTerm::DecimalLiteral(value) => {
Some(value.checked_floor()?.into())
}
EncodedTerm::FloatLiteral(value) => Some(value.floor().into()), EncodedTerm::FloatLiteral(value) => Some(value.floor().into()),
EncodedTerm::DoubleLiteral(value) => Some(value.floor().into()), EncodedTerm::DoubleLiteral(value) => Some(value.floor().into()),
_ => None, _ => None,
@ -1648,7 +1652,9 @@ impl SimpleEvaluator {
); );
Rc::new(move |tuple| match e(tuple)? { Rc::new(move |tuple| match e(tuple)? {
EncodedTerm::IntegerLiteral(value) => Some(value.into()), EncodedTerm::IntegerLiteral(value) => Some(value.into()),
EncodedTerm::DecimalLiteral(value) => Some(value.round().into()), EncodedTerm::DecimalLiteral(value) => {
Some(value.checked_round()?.into())
}
EncodedTerm::FloatLiteral(value) => Some(value.round().into()), EncodedTerm::FloatLiteral(value) => Some(value.round().into()),
EncodedTerm::DoubleLiteral(value) => Some(value.round().into()), EncodedTerm::DoubleLiteral(value) => Some(value.round().into()),
_ => None, _ => None,
@ -5851,18 +5857,18 @@ fn format_list<T: ToString>(values: impl IntoIterator<Item = T>) -> String {
} }
pub struct Timer { pub struct Timer {
start: Option<DateTime>, start: DateTime,
} }
impl Timer { impl Timer {
pub fn now() -> Self { pub fn now() -> Self {
Self { Self {
start: DateTime::now().ok(), start: DateTime::now(),
} }
} }
pub fn elapsed(&self) -> Option<DayTimeDuration> { pub fn elapsed(&self) -> Option<DayTimeDuration> {
DateTime::now().ok()?.checked_sub(self.start?) DateTime::now().checked_sub(self.start)
} }
} }

Loading…
Cancel
Save