From 02939068e351ce5e37f5258c3ecb45480f3d3656 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 20 Nov 2017 18:36:04 +0100 Subject: Add out of bounds check and error propagation for NaiveDate::from_ymd() calls * Added recursion limit for error_chain * Added new error for out-of-bounds error * Added IntoTimeType helper trait --- src/error.rs | 5 ++ src/lib.rs | 2 + src/parser/iterator.rs | 9 ++-- src/parser/mod.rs | 12 +++-- src/parser/timetype.rs | 138 +++++++++++++++++++++++++++---------------------- src/timetype.rs | 13 +++++ 6 files changed, 107 insertions(+), 72 deletions(-) diff --git a/src/error.rs b/src/error.rs index b9e7949..43413d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -59,6 +59,11 @@ error_chain! { display("Cannot compare Month to non-Moment TimeType: {:?}", tt_rep) } + OutOfBounds(y: i32, mo: u32, d: u32, hr: u32, mi: u32, s: u32) { + description("Out of bounds error") + display("Out of bounds: {}-{}-{}T{}:{}:{}", y, mo, d, hr, mi, s) + } + NotADateInsideIterator { description("Cannot calculate date for iterator") display("Cannot calculate date for iterator") diff --git a/src/lib.rs b/src/lib.rs index d39493a..d8e1d7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![recursion_limit="256"] + #[macro_use] extern crate error_chain; extern crate chrono; diff --git a/src/parser/iterator.rs b/src/parser/iterator.rs index a1db5ae..f06edd0 100644 --- a/src/parser/iterator.rs +++ b/src/parser/iterator.rs @@ -1,6 +1,7 @@ use nom::whitespace::sp; use parser::timetype::*; +use timetype::IntoTimeType; use timetype; use iter; use error; @@ -95,22 +96,22 @@ impl Iterator { match self.2 { Some(UntilSpec::Exact(e)) => { - let base = try!(into_ndt(self.0.into())); - let e = try!(into_ndt(e.into())); + let base = try!(into_ndt(self.0.into_timetype()?)); + let e = try!(into_ndt(e.into_timetype()?)); iter::Iter::build(base, recur) .map(|it| UserIterator::UntilIterator(it.until(e))) }, Some(UntilSpec::Times(i)) => { - let base = try!(into_ndt(self.0.into())); + let base = try!(into_ndt(self.0.into_timetype()?)); iter::Iter::build(base, recur) .map(|it| it.times(i)) .map(UserIterator::TimesIter) }, None => { - let base = try!(into_ndt(self.0.into())); + let base = try!(into_ndt(self.0.into_timetype()?)); iter::Iter::build(base, recur) .map(UserIterator::Iterator) }, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 884ba95..f67a3cb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -52,6 +52,7 @@ mod iterator; use error::Result; use error::KairosErrorKind as KEK; use iter::Iter; +use timetype::IntoTimeType; use parser::timetype::timetype; use parser::iterator::iterator; @@ -60,15 +61,16 @@ pub enum Parsed { TimeType(::timetype::TimeType) } -named!(do_parse, alt_complete!( - do_parse!(it: iterator >> (Parsed::Iterator(it.into_user_iterator()))) | - do_parse!(tt: timetype >> (Parsed::TimeType(tt.into()))) +named!(do_parse>, alt_complete!( + do_parse!(it: iterator >> (Ok(Parsed::Iterator(it.into_user_iterator())))) | + do_parse!(tt: timetype >> (tt.into_timetype().map(Parsed::TimeType))) )); pub fn parse(s: &str) -> Result { match do_parse(s.as_bytes()) { - IResult::Done(_, o) => Ok(o), - IResult::Error(e) => Err(e).map_err(From::from), + IResult::Done(_, Ok(o)) => Ok(o), + IResult::Done(_, Err(e)) => Err(e), + IResult::Error(e) => Err(e).map_err(From::from), IResult::Incomplete(Needed::Unknown) => Err(KEK::UnknownParserError.into()), IResult::Incomplete(Needed::Size(_)) => Err(KEK::UnknownParserError.into()), diff --git a/src/parser/timetype.rs b/src/parser/timetype.rs index 62e321b..bdd7eba 100644 --- a/src/parser/timetype.rs +++ b/src/parser/timetype.rs @@ -5,7 +5,10 @@ use nom::digit; use nom::whitespace::sp; use chrono::NaiveDate; +use timetype::IntoTimeType; use timetype; +use error::Result; +use error::KairosErrorKind as KEK; named!(pub integer, alt!( map_res!( @@ -76,9 +79,9 @@ named!(pub amount_parser, do_parse!( #[derive(Debug, PartialEq, Eq)] pub struct Amount(i64, Unit); -impl Into for Amount { - fn into(self) -> timetype::TimeType { - match self.1 { +impl IntoTimeType for Amount { + fn into_timetype(self) -> Result { + Ok(match self.1 { Unit::Second => timetype::TimeType::seconds(self.0), Unit::Minute => timetype::TimeType::minutes(self.0), Unit::Hour => timetype::TimeType::hours(self.0), @@ -86,7 +89,7 @@ impl Into for Amount { Unit::Week => timetype::TimeType::weeks(self.0), Unit::Month => timetype::TimeType::months(self.0), Unit::Year => timetype::TimeType::years(self.0), - } + }) } } @@ -110,22 +113,18 @@ pub struct AmountExpr { next: Option<(Operator, Box)>, } -impl Into for AmountExpr { - fn into(self) -> timetype::TimeType { - let mut amount = self.amount.into(); +impl IntoTimeType for AmountExpr { + fn into_timetype(self) -> Result { + let mut amount = self.amount.into_timetype()?; if let Some((op, other_amonut_expr)) = self.next { match op { - Operator::Plus => { - amount = amount + (*other_amonut_expr).into(); - }, - Operator::Minus => { - amount = amount - (*other_amonut_expr).into(); - }, + Operator::Plus => amount = amount + (*other_amonut_expr).into_timetype()?, + Operator::Minus => amount = amount - (*other_amonut_expr).into_timetype()?, } } - amount + Ok(amount) } } @@ -150,56 +149,69 @@ pub enum ExactDate { Iso8601DateTime(::iso8601::DateTime) } -impl Into for ExactDate { - fn into(self) -> timetype::TimeType { +impl IntoTimeType for ExactDate { + fn into_timetype(self) -> Result { match self { - ExactDate::Today => timetype::TimeType::today(), - ExactDate::Yesterday => timetype::TimeType::today() - timetype::TimeType::days(1), - ExactDate::Tomorrow => timetype::TimeType::today() + timetype::TimeType::days(1), + ExactDate::Today => Ok(timetype::TimeType::today()), + ExactDate::Yesterday => Ok(timetype::TimeType::today() - timetype::TimeType::days(1)), + ExactDate::Tomorrow => Ok(timetype::TimeType::today() + timetype::TimeType::days(1)), ExactDate::Iso8601Date(date) => { match date { - ::iso8601::Date::YMD { year, month, day } => { - let ndt = NaiveDate::from_ymd(year, month, day).and_hms(0, 0, 0); - timetype::TimeType::moment(ndt) - }, - ::iso8601::Date::Week { year, ww, d } => { - let ndt = NaiveDate::from_ymd(year, 1, 1).and_hms(0, 0, 0); - timetype::TimeType::moment(ndt) + ::iso8601::Date::YMD { year, month, day } => NaiveDate::from_ymd_opt(year, month, day) + .ok_or(KEK::OutOfBounds(year, month, day, 0, 0, 0).into()) + .map(|ndt| ndt.and_hms(0, 0, 0)) + .map(timetype::TimeType::moment), + + ::iso8601::Date::Week { year, ww, d } => NaiveDate::from_ymd_opt(year, 1, 1) + .ok_or(KEK::OutOfBounds(year, 1, 1, 0, 0, 0).into()) + .map(|ndt| ndt.and_hms(0, 0, 0)) + .map(timetype::TimeType::moment) + .map(|m| { + m + timetype::TimeType::weeks(ww as i64) + timetype::TimeType::days(d as i64) - }, - ::iso8601::Date::Ordinal { year, ddd } => { - let ndt = NaiveDate::from_ymd(year, 1, 1).and_hms(0, 0, 0); - timetype::TimeType::moment(ndt) - + timetype::TimeType::days(ddd as i64) - }, + }), + + ::iso8601::Date::Ordinal { year, ddd } => NaiveDate::from_ymd_opt(year, 1, 1) + .ok_or(KEK::OutOfBounds(year, 1, 1, 0, 0, 0).into()) + .map(|ndt| ndt.and_hms(0, 0, 0)) + .map(timetype::TimeType::moment) + .map(|m| m + timetype::TimeType::days(ddd as i64)), } }, ExactDate::Iso8601DateTime(::iso8601::DateTime { date, time }) => { let (hour, minute, second) = (time.hour, time.minute, time.second); match date { - ::iso8601::Date::YMD { year, month, day } => { - let ndt = NaiveDate::from_ymd(year, month, day).and_hms(hour, minute, second); - timetype::TimeType::moment(ndt) - }, - ::iso8601::Date::Week { year, ww, d } => { - let ndt = NaiveDate::from_ymd(year, 1, 1).and_hms(0, 0, 0); - timetype::TimeType::moment(ndt) + ::iso8601::Date::YMD { year, month, day } => NaiveDate::from_ymd_opt(year, month, day) + .and_then(|ndt| ndt.and_hms_opt(hour, minute, second)) + .ok_or(KEK::OutOfBounds(year, month, day, hour, minute, second).into()) + .map(timetype::TimeType::moment), + + ::iso8601::Date::Week { year, ww, d } => NaiveDate::from_ymd_opt(year, 1, 1) + .ok_or(KEK::OutOfBounds(year, 1, 1, 0, 0, 0).into()) + .map(|ndt| ndt.and_hms(0, 0, 0)) + .map(timetype::TimeType::moment) + .map(|m| { + m + timetype::TimeType::weeks(ww as i64) + timetype::TimeType::days(d as i64) + timetype::TimeType::hours(hour as i64) + timetype::TimeType::minutes(minute as i64) + timetype::TimeType::seconds(second as i64) - }, - ::iso8601::Date::Ordinal { year, ddd } => { - let ndt = NaiveDate::from_ymd(year, 1, 1).and_hms(0, 0, 0); - timetype::TimeType::moment(ndt) + }), + + ::iso8601::Date::Ordinal { year, ddd } => NaiveDate::from_ymd_opt(year, 1, 1) + .ok_or(KEK::OutOfBounds(year, 1, 1, 0, 0, 0).into()) + .map(|ndt| ndt.and_hms(0, 0, 0)) + .map(timetype::TimeType::moment) + .map(|m| { + m + timetype::TimeType::days(ddd as i64) + timetype::TimeType::hours(hour as i64) + timetype::TimeType::minutes(minute as i64) + timetype::TimeType::seconds(second as i64) - }, + }), } }, } @@ -217,13 +229,13 @@ named!(pub date, do_parse!( #[derive(Debug, PartialEq, Eq)] pub struct Date(ExactDate, Option<(Operator, AmountExpr)>); -impl Into for Date { - fn into(self) -> timetype::TimeType { - let base : timetype::TimeType = self.0.into(); +impl IntoTimeType for Date { + fn into_timetype(self) -> Result { + let base : timetype::TimeType = self.0.into_timetype()?; match self.1 { - Some((Operator::Plus, amount)) => base + amount.into(), - Some((Operator::Minus, amount)) => base - amount.into(), - None => base, + Some((Operator::Plus, amount)) => Ok(base + amount.into_timetype()?), + Some((Operator::Minus, amount)) => Ok(base - amount.into_timetype()?), + None => Ok(base), } } } @@ -234,11 +246,11 @@ pub enum TimeType { AmountExpr(AmountExpr), } -impl Into for TimeType { - fn into(self) -> timetype::TimeType { +impl IntoTimeType for TimeType { + fn into_timetype(self) -> Result { match self { - TimeType::Date(d) => d.into(), - TimeType::AmountExpr(a) => a.into(), + TimeType::Date(d) => d.into_timetype(), + TimeType::AmountExpr(a) => a.into_timetype(), } } } @@ -414,7 +426,7 @@ mod tests { println!("{:#?}", o); - let calc_res : timetype::TimeType = o.into(); + let calc_res : timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -437,7 +449,7 @@ mod tests { println!("{:#?}", o); - let calc_res : timetype::TimeType = o.into(); + let calc_res : timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -458,7 +470,7 @@ mod tests { assert!(res.is_done()); let (_, o) = res.unwrap(); - let calc_res : timetype::TimeType = o.into(); + let calc_res : timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -476,7 +488,7 @@ mod tests { assert!(res.is_done()); let (_, o) = res.unwrap(); - let calc_res : timetype::TimeType = o.into(); + let calc_res : timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -494,7 +506,7 @@ mod tests { assert!(res.is_done(), "Not done: {:?}", res.unwrap_err().description()); let (_, o) = res.unwrap(); - let calc_res : timetype::TimeType = o.into(); + let calc_res : timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -511,7 +523,7 @@ mod tests { println!("{:#?}", o); - let calc_res : timetype::TimeType = o.into(); + let calc_res : timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -532,7 +544,7 @@ mod tests { assert!(res.is_done()); let (_, o) = res.unwrap(); - let calc_res : ::timetype::TimeType = o.into(); + let calc_res : ::timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -550,7 +562,7 @@ mod tests { assert!(res.is_done(), "Not done: {:?}", res.unwrap_err().description()); let (_, o) = res.unwrap(); - let calc_res : ::timetype::TimeType = o.into(); + let calc_res : ::timetype::TimeType = o.into_timetype().unwrap(); let calc_res = calc_res.calculate(); assert!(calc_res.is_ok()); @@ -567,7 +579,7 @@ mod tests { println!("{:#?}", o); - let calc_res : ::timetype::TimeType = o.into(); + let calc_res : ::timetype::TimeType = o.into_timetype().unwrap(); println!("{:#?}", calc_res); let calc_res = calc_res.calculate(); diff --git a/src/timetype.rs b/src/timetype.rs index 6725edd..fa6fe11 100644 --- a/src/timetype.rs +++ b/src/timetype.rs @@ -438,6 +438,19 @@ impl TimeType { } +/// Helper trait for converting things into a TimeType object +/// +/// Until `TryInto` is stabilized in Rust, we need a helper trait for this. +pub trait IntoTimeType { + fn into_timetype(self) -> Result; +} + +impl IntoTimeType for TimeType { + fn into_timetype(self) -> Result { + Ok(self) + } +} + fn do_calculate(tt: TimeType) -> Result { use timetype::TimeType as TT; -- cgit v1.2.3