diff --git a/src/epoch_nanoseconds.rs b/src/epoch_nanoseconds.rs index 02dd14291..0a931ba17 100644 --- a/src/epoch_nanoseconds.rs +++ b/src/epoch_nanoseconds.rs @@ -1,6 +1,6 @@ use num_traits::FromPrimitive; -use crate::{TemporalError, NS_MAX_INSTANT}; +use crate::{error::ErrorMessage, TemporalError, NS_MAX_INSTANT}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct EpochNanoseconds(pub(crate) i128); @@ -9,8 +9,7 @@ impl TryFrom for EpochNanoseconds { type Error = TemporalError; fn try_from(value: i128) -> Result { if !is_valid_epoch_nanos(&value) { - return Err(TemporalError::range() - .with_message("Instant nanoseconds are not within a valid epoch range.")); + return Err(TemporalError::range().with_enum(ErrorMessage::InstantOutOfRange)); } Ok(Self(value)) } @@ -20,8 +19,7 @@ impl TryFrom for EpochNanoseconds { type Error = TemporalError; fn try_from(value: u128) -> Result { if (NS_MAX_INSTANT as u128) < value { - return Err(TemporalError::range() - .with_message("Instant nanoseconds are not within a valid epoch range.")); + return Err(TemporalError::range().with_enum(ErrorMessage::InstantOutOfRange)); } Ok(Self(value as i128)) } @@ -31,8 +29,7 @@ impl TryFrom for EpochNanoseconds { type Error = TemporalError; fn try_from(value: f64) -> Result { let Some(value) = i128::from_f64(value) else { - return Err(TemporalError::range() - .with_message("Instant nanoseconds are not within a valid epoch range.")); + return Err(TemporalError::range().with_enum(ErrorMessage::InstantOutOfRange)); }; Self::try_from(value) } diff --git a/src/error.rs b/src/error.rs index 5777df22b..d71842031 100644 --- a/src/error.rs +++ b/src/error.rs @@ -108,6 +108,14 @@ impl TemporalError { self } + /// Add a message enum to the error. + #[inline] + #[must_use] + pub(crate) fn with_enum(mut self, msg: ErrorMessage) -> Self { + self.msg = msg.to_string().into(); + self + } + /// Returns this error's kind. #[inline] #[must_use] @@ -146,3 +154,54 @@ impl fmt::Display for TemporalError { Ok(()) } } + +/// The error message +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum ErrorMessage { + InstantOutOfRange, + + NumberNotFinite, + NumberNotIntegral, + NumberNotPositive, + NumberOutOfRange, + + FractionalDigitsPrecisionInvalid, + + SmallestUnitNotTimeUnit, + SmallestUnitLargerThanLargestUnit, + UnitNotDate, + UnitNotTime, + UnitRequired, + UnitNoAutoDuringComparison, + RoundToUnitInvalid, + RoundingModeInvalid, + CalendarNameInvalid, + OffsetOptionInvalid, + TimeZoneNameInvalid, +} + +impl ErrorMessage { + pub fn to_string(self) -> &'static str { + match self { + Self::InstantOutOfRange => "Instant nanoseconds are not within a valid epoch range.", + Self::NumberNotFinite => "number value is not a finite value.", + Self::NumberNotIntegral => "value must be integral.", + Self::NumberNotPositive => "integer must be positive.", + Self::NumberOutOfRange => "number exceeded a valid range.", + Self::FractionalDigitsPrecisionInvalid => "Invalid fractionalDigits precision value", + Self::SmallestUnitNotTimeUnit => "smallestUnit must be a valid time unit.", + Self::SmallestUnitLargerThanLargestUnit => { + "smallestUnit was larger than largestunit in DifferenceeSettings" + } + Self::UnitNotDate => "Unit was not part of the date unit group.", + Self::UnitNotTime => "Unit was not part of the time unit group.", + Self::UnitRequired => "Unit is required", + Self::UnitNoAutoDuringComparison => "'auto' units are not allowed during comparison", + Self::RoundToUnitInvalid => "Invalid roundTo unit provided.", + Self::RoundingModeInvalid => "Invalid roundingMode option provided", + Self::CalendarNameInvalid => "Invalid calendarName option provided", + Self::OffsetOptionInvalid => "Invalid offsetOption option provided", + Self::TimeZoneNameInvalid => "Invalid timeZoneName option provided", + } + } +} diff --git a/src/options.rs b/src/options.rs index 0db9c9395..f926db791 100644 --- a/src/options.rs +++ b/src/options.rs @@ -4,7 +4,7 @@ //! operation may be completed. use crate::parsers::Precision; -use crate::{TemporalError, TemporalResult, MS_PER_DAY, NS_PER_DAY}; +use crate::{error::ErrorMessage, TemporalError, TemporalResult, MS_PER_DAY, NS_PER_DAY}; use core::ops::Add; use core::{fmt, str::FromStr}; @@ -71,54 +71,50 @@ impl ToStringRoundingOptions { rounding_mode, increment: RoundingIncrement::ONE, }), - None => { - match self.precision { - Precision::Auto => Ok(ResolvedToStringRoundingOptions { - precision: Precision::Auto, - smallest_unit: Unit::Nanosecond, + None => match self.precision { + Precision::Auto => Ok(ResolvedToStringRoundingOptions { + precision: Precision::Auto, + smallest_unit: Unit::Nanosecond, + rounding_mode, + increment: RoundingIncrement::ONE, + }), + Precision::Digit(0) => Ok(ResolvedToStringRoundingOptions { + precision: Precision::Digit(0), + smallest_unit: Unit::Second, + rounding_mode, + increment: RoundingIncrement::ONE, + }), + Precision::Digit(d) if (1..=3).contains(&d) => { + Ok(ResolvedToStringRoundingOptions { + precision: Precision::Digit(d), + smallest_unit: Unit::Millisecond, rounding_mode, - increment: RoundingIncrement::ONE, - }), - Precision::Digit(0) => Ok(ResolvedToStringRoundingOptions { - precision: Precision::Digit(0), - smallest_unit: Unit::Second, + increment: RoundingIncrement::try_new(10_u32.pow(3 - d as u32)) + .expect("a valid increment"), + }) + } + Precision::Digit(d) if (4..=6).contains(&d) => { + Ok(ResolvedToStringRoundingOptions { + precision: Precision::Digit(d), + smallest_unit: Unit::Microsecond, rounding_mode, - increment: RoundingIncrement::ONE, - }), - Precision::Digit(d) if (1..=3).contains(&d) => { - Ok(ResolvedToStringRoundingOptions { - precision: Precision::Digit(d), - smallest_unit: Unit::Millisecond, - rounding_mode, - increment: RoundingIncrement::try_new(10_u32.pow(3 - d as u32)) - .expect("a valid increment"), - }) - } - Precision::Digit(d) if (4..=6).contains(&d) => { - Ok(ResolvedToStringRoundingOptions { - precision: Precision::Digit(d), - smallest_unit: Unit::Microsecond, - rounding_mode, - increment: RoundingIncrement::try_new(10_u32.pow(6 - d as u32)) - .expect("a valid increment"), - }) - } - Precision::Digit(d) if (7..=9).contains(&d) => { - Ok(ResolvedToStringRoundingOptions { - precision: Precision::Digit(d), - smallest_unit: Unit::Nanosecond, - rounding_mode, - increment: RoundingIncrement::try_new(10_u32.pow(9 - d as u32)) - .expect("a valid increment"), - }) - } - _ => Err(TemporalError::range() - .with_message("Invalid fractionalDigits precision value")), + increment: RoundingIncrement::try_new(10_u32.pow(6 - d as u32)) + .expect("a valid increment"), + }) } - } - _ => { - Err(TemporalError::range().with_message("smallestUnit must be a valid time unit.")) - } + Precision::Digit(d) if (7..=9).contains(&d) => { + Ok(ResolvedToStringRoundingOptions { + precision: Precision::Digit(d), + smallest_unit: Unit::Nanosecond, + rounding_mode, + increment: RoundingIncrement::try_new(10_u32.pow(9 - d as u32)) + .expect("a valid increment"), + }) + } + _ => Err(TemporalError::range() + .with_enum(ErrorMessage::FractionalDigitsPrecisionInvalid)), + }, + _ => Err(TemporalError::range().with_enum(ErrorMessage::SmallestUnitNotTimeUnit)), } } } @@ -212,8 +208,9 @@ impl ResolvedRoundingOptions { .unwrap_unit_or(smallest_unit.max(fallback_largest)); // 11. If LargerOfTwoUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. if largest_unit < smallest_unit { - return Err(TemporalError::range() - .with_message("smallestUnit was larger than largestunit in DifferenceeSettings")); + return Err( + TemporalError::range().with_enum(ErrorMessage::SmallestUnitLargerThanLargestUnit) + ); } // 12. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit). @@ -243,7 +240,7 @@ impl ResolvedRoundingOptions { } else { let maximum = smallest_unit .to_maximum_rounding_increment() - .ok_or(TemporalError::range().with_message("smallestUnit must be a time unit."))?; + .ok_or(TemporalError::range().with_enum(ErrorMessage::SmallestUnitNotTimeUnit))?; (maximum, false) }; @@ -268,7 +265,7 @@ impl ResolvedRoundingOptions { Unit::Millisecond => MS_PER_DAY as u64, Unit::Microsecond => MS_PER_DAY as u64 * 1000, Unit::Nanosecond => NS_PER_DAY, - _ => return Err(TemporalError::range().with_message("Invalid roundTo unit provided.")), + _ => return Err(TemporalError::range().with_enum(ErrorMessage::RoundToUnitInvalid)), }; increment.validate(maximum, true)?; @@ -302,7 +299,7 @@ impl UnitGroup { extra_unit: Option, ) -> TemporalResult { let Some(unit) = unit else { - return Err(TemporalError::range().with_message("Unit is required.")); + return Err(TemporalError::range().with_enum(ErrorMessage::UnitRequired)); }; self.validate_unit(Some(unit), extra_unit)?; Ok(unit) @@ -315,15 +312,13 @@ impl UnitGroup { Some(unit) if !unit.is_time_unit() => Ok(()), None => Ok(()), _ if unit == extra_unit => Ok(()), - _ => Err(TemporalError::range() - .with_message("Unit was not part of the date unit group.")), + _ => Err(TemporalError::range().with_enum(ErrorMessage::UnitNotDate)), }, UnitGroup::Time => match unit { Some(unit) if unit.is_time_unit() => Ok(()), None => Ok(()), _ if unit == extra_unit => Ok(()), - _ => Err(TemporalError::range() - .with_message("Unit was not part of the time unit group.")), + _ => Err(TemporalError::range().with_enum(ErrorMessage::UnitNotTime)), }, UnitGroup::DateTime => Ok(()), } @@ -473,7 +468,7 @@ impl Unit { } // NOTE(HalidOdat): deviation from specification. - Err(TemporalError::assert().with_message("auto cannot be used for comparison")) + Err(TemporalError::assert().with_enum(ErrorMessage::UnitNoAutoDuringComparison)) } /// Helper method for getting the index into the [`UNIT_VALUE_TABLE`]. @@ -494,8 +489,7 @@ impl Unit { } } - Err(TemporalError::assert() - .with_message("auto does not exist in the spec Table 21 Value column")) + Err(TemporalError::assert().with_enum(ErrorMessage::UnitNoAutoDuringComparison)) } } @@ -875,7 +869,7 @@ impl FromStr for RoundingMode { "halfExpand" => Ok(Self::HalfExpand), "halfTrunc" => Ok(Self::HalfTrunc), "halfEven" => Ok(Self::HalfEven), - _ => Err(TemporalError::range().with_message("RoundingMode not an accepted value.")), + _ => Err(TemporalError::range().with_enum(ErrorMessage::RoundingModeInvalid)), } } } @@ -933,7 +927,7 @@ impl FromStr for DisplayCalendar { "always" => Ok(Self::Always), "never" => Ok(Self::Never), "critical" => Ok(Self::Critical), - _ => Err(TemporalError::range().with_message("Invalid calendarName provided.")), + _ => Err(TemporalError::range().with_enum(ErrorMessage::CalendarNameInvalid)), } } } @@ -962,7 +956,7 @@ impl FromStr for DisplayOffset { match s { "auto" => Ok(Self::Auto), "never" => Ok(Self::Never), - _ => Err(TemporalError::range().with_message("Invalid offset option provided.")), + _ => Err(TemporalError::range().with_enum(ErrorMessage::OffsetOptionInvalid)), } } } @@ -997,7 +991,7 @@ impl FromStr for DisplayTimeZone { "auto" => Ok(Self::Auto), "never" => Ok(Self::Never), "critical" => Ok(Self::Critical), - _ => Err(TemporalError::range().with_message("Invalid timeZoneName option provided.")), + _ => Err(TemporalError::range().with_enum(ErrorMessage::TimeZoneNameInvalid)), } } } diff --git a/src/primitive.rs b/src/primitive.rs index ab8c4018f..89d6cc897 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -2,7 +2,7 @@ use core::cmp::Ordering; -use crate::{TemporalError, TemporalResult}; +use crate::{error::ErrorMessage, TemporalError, TemporalResult}; use num_traits::float::FloatCore; use num_traits::{AsPrimitive, FromPrimitive, PrimInt}; @@ -44,7 +44,7 @@ impl FiniteF64 { pub fn checked_add(&self, other: &Self) -> TemporalResult { let result = Self(self.0 + other.0); if !result.0.is_finite() { - return Err(TemporalError::range().with_message("number value is not a finite value.")); + return Err(TemporalError::range().with_enum(ErrorMessage::NumberNotFinite)); } Ok(result) } @@ -53,7 +53,7 @@ impl FiniteF64 { pub fn checked_mul_add(&self, a: FiniteF64, b: FiniteF64) -> TemporalResult { let result = Self(core_maths::CoreFloat::mul_add(self.0, a.0, b.0)); if !result.0.is_finite() { - return Err(TemporalError::range().with_message("number value is not a finite value.")); + return Err(TemporalError::range().with_enum(ErrorMessage::NumberNotFinite)); } Ok(result) } @@ -62,7 +62,7 @@ impl FiniteF64 { pub fn checked_div(&self, other: &Self) -> TemporalResult { let result = Self(self.0 / other.0); if !result.0.is_finite() { - return Err(TemporalError::range().with_message("number value is not a finite value.")); + return Err(TemporalError::range().with_enum(ErrorMessage::NumberNotFinite)); } Ok(result) } @@ -81,7 +81,7 @@ impl FiniteF64 { f64: AsPrimitive, { if self.0 != FloatCore::trunc(self.0) { - return Err(TemporalError::range().with_message("value must be integral.")); + return Err(TemporalError::range().with_enum(ErrorMessage::NumberNotIntegral)); } Ok(self.0.as_()) } @@ -106,7 +106,7 @@ impl FiniteF64 { { let truncated_value = self.as_integer_with_truncation::(); if truncated_value <= 0i8.as_() { - return Err(TemporalError::range().with_message("integer must be positive.")); + return Err(TemporalError::range().with_enum(ErrorMessage::NumberNotPositive)); } Ok(truncated_value) } @@ -128,7 +128,7 @@ impl TryFrom for FiniteF64 { type Error = TemporalError; fn try_from(value: f64) -> Result { if !value.is_finite() { - return Err(TemporalError::range().with_message("number value is not a finite value.")); + return Err(TemporalError::range().with_enum(ErrorMessage::NumberNotFinite)); } Ok(Self(value)) } @@ -138,7 +138,7 @@ impl TryFrom for FiniteF64 { type Error = TemporalError; fn try_from(value: i64) -> Result { let result = f64::from_i64(value) - .ok_or(TemporalError::range().with_message("number exceeded a valid range."))?; + .ok_or(TemporalError::range().with_enum(ErrorMessage::NumberOutOfRange))?; Ok(Self(result)) } } @@ -147,7 +147,7 @@ impl TryFrom for FiniteF64 { type Error = TemporalError; fn try_from(value: u64) -> Result { let result = f64::from_u64(value) - .ok_or(TemporalError::range().with_message("number exceeded a valid range."))?; + .ok_or(TemporalError::range().with_enum(ErrorMessage::NumberOutOfRange))?; Ok(Self(result)) } } @@ -156,7 +156,7 @@ impl TryFrom for FiniteF64 { type Error = TemporalError; fn try_from(value: i128) -> Result { let result = f64::from_i128(value) - .ok_or(TemporalError::range().with_message("number exceeded a valid range."))?; + .ok_or(TemporalError::range().with_enum(ErrorMessage::NumberOutOfRange))?; debug_assert!(result.is_finite()); Ok(Self(result)) } @@ -166,7 +166,7 @@ impl TryFrom for FiniteF64 { type Error = TemporalError; fn try_from(value: u128) -> Result { let result = f64::from_u128(value) - .ok_or(TemporalError::range().with_message("number exceeded a valid range."))?; + .ok_or(TemporalError::range().with_enum(ErrorMessage::NumberOutOfRange))?; debug_assert!(result.is_finite()); Ok(Self(result)) }