Skip to content

Commit

Permalink
Provide details when displaying component errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Dec 18, 2019
1 parent 7528763 commit ab09c47
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 62 deletions.
16 changes: 10 additions & 6 deletions src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -990,13 +990,17 @@ impl Date {

// Verification for all components is done at parse time.
match items {
items!(year, month, day) => Ok(internals::Date::from_ymd_unchecked(year, month.get(), day.get())),
items!(year, ordinal_day) => Ok(internals::Date::from_yo_unchecked(year, ordinal_day.get())),
items!(week_based_year, iso_week, weekday) => Ok(internals::Date::from_iso_ywd_unchecked(
week_based_year,
iso_week.get(),
weekday,
items!(year, month, day) => Ok(internals::Date::from_ymd_unchecked(
year,
month.get(),
day.get(),
)),
items!(year, ordinal_day) => {
Ok(internals::Date::from_yo_unchecked(year, ordinal_day.get()))
}
items!(week_based_year, iso_week, weekday) => Ok(
internals::Date::from_iso_ywd_unchecked(week_based_year, iso_week.get(), weekday),
),
items!(year, sunday_week, weekday) => Ok(internals::Date::from_yo_unchecked(
year,
#[allow(clippy::cast_sign_loss)]
Expand Down
39 changes: 35 additions & 4 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use crate::format::ParseError;
#[cfg(not(feature = "std"))]
use crate::no_std_prelude::*;
use core::fmt;

/// A unified error type for anything returned by a method in the time crate.
///
/// This can be used when you either don't know or don't care about the exact
/// error returned. `Result<_, time::Error>` will work in these situations.
// Boxing the `ComponentRangeError` reduces the size of `Error` from 72 bytes to
// 16.
#[allow(clippy::missing_docs_in_private_items)] // variants only
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
ConversionRange(ConversionRangeError),
ComponentRange(ComponentRangeError),
ComponentRange(Box<ComponentRangeError>),
Parse(ParseError),
}

Expand Down Expand Up @@ -53,22 +57,49 @@ impl From<ConversionRangeError> for Error {

/// An error type indicating that a component provided to a method was out of
/// range, causing a failure.
// i64 is the narrowest type fitting all use cases. This eliminates the need
// for a type parameter.
#[allow(missing_copy_implementations)] // Non-copy fields may be added.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComponentRangeError;
pub struct ComponentRangeError {
/// Name of the component.
pub(crate) name: &'static str,
/// Minimum allowed value, inclusive.
pub(crate) minimum: i64,
/// Maximum allowed value, inclusive.
pub(crate) maximum: i64,
/// Value that was provided.
pub(crate) value: i64,
/// The minimum and/or maximum is only valid with the following values.
pub(crate) given: Vec<(&'static str, i64)>,
}

impl fmt::Display for ComponentRangeError {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("A component's value is out of range")
write!(
f,
"{} must be in the range {}..={}",
self.name, self.minimum, self.maximum
)?;

let mut iter = self.given.iter();
if let Some((name, value)) = iter.next() {
write!(f, " given {}={}", name, value)?;
for (name, value) in iter {
write!(f, ", {}={}", name, value)?;
}
}

write!(f, " (was {})", self.value)
}
}

impl From<ComponentRangeError> for Error {
#[inline(always)]
fn from(original: ComponentRangeError) -> Self {
Self::ComponentRange(original)
Self::ComponentRange(Box::new(original))
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/internals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ impl Date {
#[inline]
pub(crate) fn from_iso_ywd_unchecked(year: i32, week: u8, weekday: Weekday) -> crate::Date {
let ordinal = week as u16 * 7 + weekday.iso_weekday_number() as u16
- (Self::from_yo_unchecked(year, 4).weekday().iso_weekday_number() as u16 + 3);
- (Self::from_yo_unchecked(year, 4)
.weekday()
.iso_weekday_number() as u16
+ 3);

if ordinal < 1 {
return Self::from_yo_unchecked(year - 1, ordinal + days_in_year(year - 1));
Expand Down
37 changes: 16 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@
)]
#![warn(
unused_extern_crates,
box_pointers,
missing_copy_implementations,
missing_debug_implementations,
single_use_lifetimes,
Expand Down Expand Up @@ -200,17 +199,6 @@ macro_rules! assert_value_in_range {
}
};

($value:ident in $start:expr => exclusive $end:expr) => {
if !($start..$end).contains(&$value) {
panic!(
concat!(stringify!($value), " must be in the range {}..{} (was {})"),
$start,
$end,
$value,
);
}
};

($value:ident in $start:expr => $end:expr, given $($conditional:ident),+ $(,)?) => {
if !($start..=$end).contains(&$value) {
panic!(
Expand All @@ -224,23 +212,30 @@ macro_rules! assert_value_in_range {
};
}

// TODO Some of the formatting can likely be performed at compile-time.
/// Returns `None` if the value is not in range.
macro_rules! ensure_value_in_range {
($value:ident in $start:expr => $end:expr) => {
if !($start..=$end).contains(&$value) {
return Err(ComponentRangeError);
}
};

($value:ident in $start:expr => exclusive $end:expr) => {
if !($start..$end).contains(&$value) {
return Err(ComponentRangeError);
return Err(ComponentRangeError {
name: stringify!($value),
minimum: i64::from($start),
maximum: i64::from($end),
value: i64::from($value),
given: Vec::new(),
});
}
};

($value:ident in $start:expr => $end:expr,given $($conditional:ident),+ $(,)?) => {
($value:ident in $start:expr => $end:expr, given $($conditional:ident),+ $(,)?) => {
if !($start..=$end).contains(&$value) {
return Err(ComponentRangeError);
return Err(ComponentRangeError {
name: stringify!($value),
minimum: i64::from($start),
maximum: i64::from($end),
value: i64::from($value),
given: vec![$((stringify!($conditional), i64::from($conditional))),+],
});
};
};
}
Expand Down
60 changes: 30 additions & 30 deletions src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ impl Time {
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms(hour: u8, minute: u8, second: u8) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
assert_value_in_range!(second in 0 => exclusive 60);
assert_value_in_range!(hour in 0 => 23);
assert_value_in_range!(minute in 0 => 59);
assert_value_in_range!(second in 0 => 59);
Self {
hour,
minute,
Expand All @@ -116,9 +116,9 @@ impl Time {
/// ```
#[inline(always)]
pub fn try_from_hms(hour: u8, minute: u8, second: u8) -> Result<Self, ComponentRangeError> {
ensure_value_in_range!(hour in 0 => exclusive 24);
ensure_value_in_range!(minute in 0 => exclusive 60);
ensure_value_in_range!(second in 0 => exclusive 60);
ensure_value_in_range!(hour in 0 => 23);
ensure_value_in_range!(minute in 0 => 59);
ensure_value_in_range!(second in 0 => 59);
Ok(Self {
hour,
minute,
Expand Down Expand Up @@ -164,10 +164,10 @@ impl Time {
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms_milli(hour: u8, minute: u8, second: u8, millisecond: u16) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
assert_value_in_range!(second in 0 => exclusive 60);
assert_value_in_range!(millisecond in 0 => exclusive 1_000);
assert_value_in_range!(hour in 0 => 23);
assert_value_in_range!(minute in 0 => 59);
assert_value_in_range!(second in 0 => 59);
assert_value_in_range!(millisecond in 0 => 999);
Self {
hour,
minute,
Expand Down Expand Up @@ -199,10 +199,10 @@ impl Time {
second: u8,
millisecond: u16,
) -> Result<Self, ComponentRangeError> {
ensure_value_in_range!(hour in 0 => exclusive 24);
ensure_value_in_range!(minute in 0 => exclusive 60);
ensure_value_in_range!(second in 0 => exclusive 60);
ensure_value_in_range!(millisecond in 0 => exclusive 1_000);
ensure_value_in_range!(hour in 0 => 23);
ensure_value_in_range!(minute in 0 => 59);
ensure_value_in_range!(second in 0 => 59);
ensure_value_in_range!(millisecond in 0 => 999);
Ok(Self {
hour,
minute,
Expand Down Expand Up @@ -248,10 +248,10 @@ impl Time {
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms_micro(hour: u8, minute: u8, second: u8, microsecond: u32) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
assert_value_in_range!(second in 0 => exclusive 60);
assert_value_in_range!(microsecond in 0 => exclusive 1_000_000);
assert_value_in_range!(hour in 0 => 23);
assert_value_in_range!(minute in 0 => 59);
assert_value_in_range!(second in 0 => 59);
assert_value_in_range!(microsecond in 0 => 999_999);
Self {
hour,
minute,
Expand Down Expand Up @@ -283,10 +283,10 @@ impl Time {
second: u8,
microsecond: u32,
) -> Result<Self, ComponentRangeError> {
ensure_value_in_range!(hour in 0 => exclusive 24);
ensure_value_in_range!(minute in 0 => exclusive 60);
ensure_value_in_range!(second in 0 => exclusive 60);
ensure_value_in_range!(microsecond in 0 => exclusive 1_000_000);
ensure_value_in_range!(hour in 0 => 23);
ensure_value_in_range!(minute in 0 => 59);
ensure_value_in_range!(second in 0 => 59);
ensure_value_in_range!(microsecond in 0 => 999_999);
Ok(Self {
hour,
minute,
Expand Down Expand Up @@ -331,10 +331,10 @@ impl Time {
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms_nano(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
assert_value_in_range!(second in 0 => exclusive 60);
assert_value_in_range!(nanosecond in 0 => exclusive 1_000_000_000);
assert_value_in_range!(hour in 0 => 23);
assert_value_in_range!(minute in 0 => 59);
assert_value_in_range!(second in 0 => 59);
assert_value_in_range!(nanosecond in 0 => 999_999_999);
Self {
hour,
minute,
Expand Down Expand Up @@ -366,10 +366,10 @@ impl Time {
second: u8,
nanosecond: u32,
) -> Result<Self, ComponentRangeError> {
ensure_value_in_range!(hour in 0 => exclusive 24);
ensure_value_in_range!(minute in 0 => exclusive 60);
ensure_value_in_range!(second in 0 => exclusive 60);
ensure_value_in_range!(nanosecond in 0 => exclusive 1_000_000_000);
ensure_value_in_range!(hour in 0 => 23);
ensure_value_in_range!(minute in 0 => 59);
ensure_value_in_range!(second in 0 => 59);
ensure_value_in_range!(nanosecond in 0 => 999_999_999);
Ok(Self {
hour,
minute,
Expand Down

0 comments on commit ab09c47

Please sign in to comment.