diff --git a/Cargo.toml b/Cargo.toml index 5091c6d..e3971fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duration-str" -version = "0.7.1" +version = "0.8.0" authors = ["baoyachi "] edition = "2021" description = "duration string parser" @@ -14,13 +14,13 @@ license = "Apache-2.0" default = ["chrono", "serde", "time"] [dependencies] -nom = "7.1.1" thiserror = "1.0.37" chrono = { version = "0.4.23", optional = true } time = { version = "0.3.17", optional = true } serde = { version = "1.0.147", features = ["derive"], optional = true } rust_decimal = { version = "1.29.1", default-features = false } +winnow = "0.6.6" [dev-dependencies] serde_json = { version = "1.0.87" } diff --git a/src/lib.rs b/src/lib.rs index 2ded47d..1ec3d74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,19 +165,19 @@ //! } //! ``` -#[cfg(all(feature = "chrono", feature = "serde"))] -use chrono::Duration as CDuration; +mod parser; +#[cfg(feature = "serde")] +mod serde; + +pub use parser::parse; +#[cfg(feature = "serde")] +pub use serde::*; -use dls_parser::*; -use nom::combinator::opt; -use nom::sequence::tuple; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; -use std::convert::TryFrom; +use std::str::FromStr; use std::time::Duration; use thiserror::Error; -#[cfg(all(feature = "time", feature = "serde"))] -use time::Duration as TDuration; #[cfg(feature = "chrono")] pub use naive_date::{ @@ -212,6 +212,31 @@ enum TimeUnit { NanoSecond, } +impl FromStr for TimeUnit { + type Err = DError; + + fn from_str(s: &str) -> Result { + match &*s.to_lowercase() { + "y" | "year" => Ok(TimeUnit::Year), + "mon" | "month" => Ok(TimeUnit::Month), + "w" | "week" => Ok(TimeUnit::Week), + "d" | "day" => Ok(TimeUnit::Day), + "h" | "hour" | "hr" => Ok(TimeUnit::Hour), + "m" | "min" | "minute" => Ok(TimeUnit::Minute), + "s" | "sec" | "second" => Ok(TimeUnit::Second), + "ms" | "msec" | "millisecond" => Ok(TimeUnit::MilliSecond), + "µs" | "µsec" | "µsecond" | "us" | "usec" | "usecond" | "microsecond" => { + Ok(TimeUnit::MicroSecond) + } + "ns" | "nsec" | "nanosecond" => Ok(TimeUnit::NanoSecond), + _ => Err(DError::ParseError(format!( + "expect one of [y,mon,w,d,h,m,s,ms,µs,us,ns] or their longer forms.but find:{}", + s, + ))), + } + } +} + const ONE_MICROSECOND_NANOSECOND: u64 = 1000; const ONE_MILLISECOND_NANOSECOND: u64 = 1000 * ONE_MICROSECOND_NANOSECOND; const ONE_SECOND_NANOSECOND: u64 = 1000 * ONE_MILLISECOND_NANOSECOND; @@ -334,107 +359,7 @@ impl ToString for CondUnit { } } -mod dls_parser { - use crate::{CondUnit, TimeUnit, PLUS, STAR}; - use nom::{ - character::complete::{digit1, multispace0}, - combinator::opt, - error::{ErrorKind, ParseError}, - sequence::tuple, - AsChar, IResult, InputTakeAtPosition, - }; - - pub(crate) fn unit1>(input: T) -> IResult - where - T: InputTakeAtPosition, - ::Item: AsChar + Copy, - { - input.split_at_position1_complete( - |item| !(item.is_alpha() || item.as_char() == 'µ'), - ErrorKind::Alpha, - ) - } - - pub(crate) fn time_unit(input: &str) -> IResult<&str, TimeUnit> { - let (input, out) = unit1(input)?; - match out.to_lowercase().as_str() { - "y" | "year" => Ok((input, TimeUnit::Year)), - "mon" | "month" => Ok((input, TimeUnit::Month)), - "w" | "week" => Ok((input, TimeUnit::Week)), - "d" | "day" => Ok((input, TimeUnit::Day)), - "h" | "hour" | "hr" => Ok((input, TimeUnit::Hour)), - "m" | "min" | "minute" => Ok((input, TimeUnit::Minute)), - "s" | "sec" | "second" => Ok((input, TimeUnit::Second)), - "ms" | "msec" | "millisecond" => Ok((input, TimeUnit::MilliSecond)), - "µs" | "µsec" | "µsecond" | "us" | "usec" | "usecond" | "microsecond" => { - Ok((input, TimeUnit::MicroSecond)) - } - "ns" | "nsec" | "nanosecond" => Ok((input, TimeUnit::NanoSecond)), - _ => Err(nom::Err::Error(nom::error::Error::new( - "expect one of [y,mon,w,d,h,m,s,ms,µs,us,ns] or their longer forms", - ErrorKind::Alpha, - ))), - } - } - - pub(crate) fn cond_unit(input: &str) -> IResult<&str, CondUnit> { - let (input, out) = input - .split_at_position1_complete(|item| !matches!(item, '+' | '*'), ErrorKind::Char)?; - match out { - PLUS => Ok((input, CondUnit::Plus)), - STAR => Ok((input, CondUnit::Star)), - _ => Err(nom::Err::Error(nom::error::Error::new( - "expect one of [+,*]", - ErrorKind::Char, - ))), - } - } - - pub(crate) fn parse_expr_time(input: &str) -> IResult<&str, (&str, TimeUnit)> { - tuple((digit1, time_unit))(input) - } - - pub(crate) fn cond_time(input: &str) -> IResult<&str, Vec<(&str, CondUnit, TimeUnit)>> { - let mut vec = vec![]; - let mut input = input; - while !input.trim().is_empty() { - let (in_input, (_, opt_cond, _, out, opt_unit)) = - tuple((multispace0, opt(cond_unit), multispace0, digit1, opt(unit1)))(input)?; - input = in_input; - // Add by default. - let cond = opt_cond.unwrap_or(CondUnit::Plus); - // Parse unit, default is seconds. - let time_unit = opt_unit.map_or_else( - || Ok(TimeUnit::Second), - |unit| time_unit(unit).map(|(_, time_unit)| time_unit), - )?; - vec.push((out, cond, time_unit)); - } - Ok(("", vec)) - } -} - -/// parse string to `std::time::Duration` -pub fn parse(input: impl AsRef) -> DResult { - let (in_input, ((time_str, time_unit), cond_opt)) = - tuple((parse_expr_time, opt(cond_time)))(input.as_ref()) - .map_err(|e| DError::DSLError(format!("{}", e)))?; - if !in_input.is_empty() && cond_opt.is_none() { - return Err(DError::DSLError(format!( - "unsupported duration string: [{}], caused by: [{}],", - input.as_ref(), - in_input - ))); - } - let (init_cond, init_duration) = cond_opt - .map(|val| val.calc()) - .unwrap_or_else(|| Ok(CondUnit::init()))?; - let unit_time = time_unit.duration(time_str)?; - let duration = init_cond.calc(unit_time, init_duration)?; - Ok(duration) -} - -/// convert Into to `std::time::Duration` +/// convert `Into` to `std::time::Duration` /// /// # Example /// @@ -474,7 +399,7 @@ pub fn parse_std(input: impl AsRef) -> DResult { parse(input.as_ref()) } -/// convert Into to `chrono::Duration` +/// convert `Into` to `chrono::Duration` /// /// # Example /// @@ -518,7 +443,7 @@ pub fn parse_chrono(input: impl AsRef) -> DResult { Ok(duration) } -/// convert Into to `time::Duration` +/// convert `Into` to `time::Duration` /// /// # Example /// @@ -611,468 +536,3 @@ mod naive_date { gen_naive_date_func!(after_naive_date_time, after_naive_date, TimeHistory::After); } - -#[cfg(feature = "serde")] -macro_rules! des_duration { - ($name:ident,$duration_type:ident,$fn_name:ident,$parse:ident) => { - struct $name; - impl<'de> serde::de::Visitor<'de> for $name { - type Value = $duration_type; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("expect duration string,e.g:'1min+30'") - } - - fn visit_str(self, s: &str) -> Result - where - E: serde::de::Error, - { - let duration = $parse(s).map_err(serde::de::Error::custom)?; - Ok(duration) - } - } - - pub fn $fn_name<'de, D>(deserializer: D) -> Result<$duration_type, D::Error> - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any($name) - } - }; -} - -#[cfg(feature = "serde")] -macro_rules! des_option_duration { - ($name:ident,$duration_type:ident,$fn_name:ident,$parse:ident) => { - struct $name; - impl<'de> serde::de::Visitor<'de> for $name { - type Value = Option<$duration_type>; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("expect duration string,e.g:'1min+30'") - } - - fn visit_some(self, d: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::Deserialize; - let s: Option = Option::deserialize(d)?; - if let Some(s) = s { - let duration = $parse(s).map_err(serde::de::Error::custom)?; - return Ok(Some(duration)); - } - Ok(None) - } - - fn visit_none(self) -> Result - where - E: serde::de::Error, - { - Ok(None) - } - } - - pub fn $fn_name<'de, D>(deserializer: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_option($name) - } - }; -} - -#[cfg(feature = "serde")] -des_duration!(DurationStd, Duration, deserialize_duration, parse_std); - -#[cfg(feature = "serde")] -des_option_duration!( - OptionDurationStd, - Duration, - deserialize_option_duration, - parse_std -); - -#[cfg(all(feature = "chrono", feature = "serde"))] -des_duration!( - DurationChrono, - CDuration, - deserialize_duration_chrono, - parse_chrono -); - -#[cfg(all(feature = "chrono", feature = "serde"))] -des_option_duration!( - OptionDurationChrono, - CDuration, - deserialize_option_duration_chrono, - parse_chrono -); - -#[cfg(all(feature = "time", feature = "serde"))] -des_duration!( - DurationTime, - TDuration, - deserialize_duration_time, - parse_time -); - -#[cfg(all(feature = "time", feature = "serde"))] -des_option_duration!( - OptionDurationTime, - TDuration, - deserialize_option_duration_time, - parse_time -); - -#[cfg(test)] -#[allow(clippy::identity_op)] -mod tests { - use super::*; - - #[test] - fn test_time_unit() { - let (input, format) = time_unit("m123").unwrap(); - assert_eq!(input, "123"); - assert_eq!(format, TimeUnit::Minute); - } - - #[test] - fn test_parse_expr_time() { - let (input, (out, format)) = parse_expr_time("123m").unwrap(); - assert_eq!(input, ""); - assert_eq!(out, "123"); - assert_eq!(format, TimeUnit::Minute); - } - - #[test] - fn test_cond_unit() { - let (input, format) = cond_unit("*123").unwrap(); - assert_eq!(input, "123"); - assert_eq!(format, CondUnit::Star); - } - - #[test] - fn test_cond_time() { - let (input, out) = cond_time(" * 60").unwrap(); - assert_eq!(input, ""); - assert_eq!(out, vec![("60", CondUnit::Star, TimeUnit::Second)]); - } - - #[test] - fn test_cond_time2() { - let (input, out) = cond_time(" * 60*30").unwrap(); - assert_eq!(input, ""); - assert_eq!( - out, - vec![ - ("60", CondUnit::Star, TimeUnit::Second), - ("30", CondUnit::Star, TimeUnit::Second), - ] - ); - } - - #[test] - fn test_duration_parse1() { - let duration = parse("1m+31").unwrap(); - assert_eq!(duration, Duration::new(91, 0)) - } - - #[test] - fn test_duration_parse2() { - let duration = parse("1m*60").unwrap(); - assert_eq!(duration, Duration::new(3600, 0)) - } - - #[test] - fn test_duration_parse3() { - let duration = parse("1m*60*20").unwrap(); - assert_eq!(duration, Duration::new(72000, 0)) - } - - #[test] - fn test_duration_parse4() { - let duration = parse("1m+60+24").unwrap(); - assert_eq!(duration, Duration::new(144, 0)) - } - - #[test] - fn test_duration_parse5() { - let duration = parse("1m+60+24 ").unwrap(); - assert_eq!(duration, Duration::new(144, 0)) - } - - #[test] - fn test_duration_parse6() { - let duration = parse("0m").unwrap(); - assert_eq!(duration, Duration::new(0, 0)) - } - - #[test] - fn test_duration_parse7() { - assert!(parse("0m+3-5").is_err()) - } - - #[test] - fn test_duration_parse8() { - let duration = parse("1hr").unwrap(); - assert_eq!(duration, Duration::new(3600, 0)) - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_duration() { - use serde::*; - #[derive(Debug, Deserialize)] - struct Config { - #[serde(deserialize_with = "deserialize_duration")] - time_ticker: Duration, - } - let json = r#"{"time_ticker":"1y+30"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!( - config.time_ticker, - Duration::from_nanos(ONE_YEAR_NANOSECOND) + Duration::from_secs(30) - ); - } - - #[test] - fn test_parse() { - let duration = parse("1d").unwrap(); - assert_eq!(duration, Duration::new(24 * 60 * 60, 0)); - - let duration = parse("3m+31").unwrap(); //the default duration unit is second. - assert_eq!(duration, Duration::new(211, 0)); - - let duration = parse("3m + 31").unwrap(); //the default duration unit is second. - assert_eq!(duration, Duration::new(211, 0)); - - let duration = parse("3m + 13s + 29ms").unwrap(); - assert_eq!(duration, Duration::new(193, 29 * 1000 * 1000 + 0 + 0)); - - let duration = parse("3m + 1s + 29ms +17µs").unwrap(); - assert_eq!( - duration, - Duration::new(181, 29 * 1000 * 1000 + 17 * 1000 + 0) - ); - - let duration = parse("1m*10").unwrap(); //the default duration unit is second. - assert_eq!(duration, Duration::new(600, 0)); - - let duration = parse("1m*10ms").unwrap(); - assert_eq!(duration, Duration::new(0, 600 * 1000 * 1000)); - - let duration = parse("1m * 1ns").unwrap(); - assert_eq!(duration, Duration::new(0, 60)); - - let duration = parse("1m * 1m").unwrap(); - assert_eq!(duration, Duration::new(3600, 0)); - } - - #[test] - fn test_overflow_plus() { - let result = parse("10000000000000000y+60"); - assert_eq!(result, Err(DError::OverflowError)); - } - - #[test] - fn test_max_mul() { - let duration = parse("580y*1").unwrap(); - assert_eq!( - duration, - std::time::Duration::from_millis(18290880000) * 1000 - ); - } - - #[test] - fn test_overflow_mul() { - let result = parse("580y*2"); - assert_eq!(result, Err(DError::OverflowError)); - } -} - -#[cfg(all(test, feature = "chrono"))] -mod chrono_tests { - use super::*; - use chrono::{Datelike, Utc}; - - #[test] - fn test_parse_chrono() { - use chrono::Duration; - let duration = parse_chrono("1m+60+24 ").unwrap(); - assert_eq!(duration, Duration::seconds(144)) - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_duration_chrono() { - use chrono::Duration; - use serde::*; - #[derive(Debug, Deserialize)] - struct Config { - #[serde(deserialize_with = "deserialize_duration_chrono")] - time_ticker: Duration, - } - let json = r#"{"time_ticker":"1y+30"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!( - config.time_ticker, - Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30) - ); - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_option_duration_chrono() { - use chrono::Duration; - use serde::*; - #[derive(Debug, Deserialize)] - struct Config { - #[serde(deserialize_with = "deserialize_option_duration_chrono")] - time_ticker: Option, - } - let json = r#"{"time_ticker":"1y+30"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!( - config.time_ticker, - Some(Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30)) - ); - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_duration() { - use serde::*; - #[derive(Debug, Deserialize)] - struct Config { - #[serde(deserialize_with = "deserialize_duration")] - time_ticker: Duration, - } - let json = r#"{"time_ticker":"1min+30"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!(config.time_ticker, Duration::from_secs(90)); - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_option_duration() { - use serde::*; - #[derive(Debug, Deserialize)] - struct Config { - #[serde(deserialize_with = "deserialize_option_duration")] - time_ticker: Option, - } - let json = r#"{"time_ticker":"1min+30"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!(config.time_ticker, Some(Duration::from_secs(90))); - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_option_duration2() { - use serde::*; - #[derive(Debug, Deserialize, PartialEq)] - struct Config { - #[serde(default, deserialize_with = "deserialize_option_duration")] - time_ticker: Option, - name: String, - } - let json = r#"{"time_ticker":null,"name":"foo"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - - assert_eq!( - config, - Config { - time_ticker: None, - name: "foo".into(), - } - ); - - let json = r#"{"name":"foo"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!( - config, - Config { - time_ticker: None, - name: "foo".into(), - } - ); - } - - #[test] - fn test_after_naive_date_time() { - let date = Utc::now().naive_utc().date(); - let jd = date.num_days_from_ce() + 180; - let date = after_naive_date_time("180d").unwrap(); - assert_eq!(date.num_days_from_ce(), jd) - } - - #[test] - fn test_after_naive_date() { - let date = Utc::now().naive_utc().date(); - let jd = date.num_days_from_ce() + 180; - let date = after_naive_date("180d").unwrap(); - assert_eq!(date.num_days_from_ce(), jd) - } - - #[test] - fn test_before_naive_date_time() { - let date = Utc::now().naive_utc().date(); - let jd = date.num_days_from_ce() - 180; - let date = before_naive_date_time("180d").unwrap(); - assert_eq!(date.num_days_from_ce(), jd) - } - - #[test] - fn test_before_naive_date() { - let date = Utc::now().naive_utc().date(); - let jd = date.num_days_from_ce() - 180; - let date = before_naive_date("180d").unwrap(); - assert_eq!(date.num_days_from_ce(), jd) - } -} - -#[cfg(all(test, feature = "time"))] -mod time_tests { - use super::*; - use serde::*; - use time::Duration; - - #[test] - fn test_parse_time() { - let duration = parse_time("1m+60+24 ").unwrap(); - assert_eq!(duration, Duration::seconds(144)) - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_duration_time() { - #[derive(Debug, Deserialize)] - struct Config { - #[serde(deserialize_with = "deserialize_duration_time")] - time_ticker: Duration, - } - let json = r#"{"time_ticker":"1y+30"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!( - config.time_ticker, - Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30) - ); - } - - #[cfg(feature = "serde")] - #[test] - fn test_deserialize_option_duration_time() { - #[derive(Debug, Deserialize)] - struct Config { - #[serde(deserialize_with = "deserialize_option_duration_time")] - time_ticker: Option, - } - let json = r#"{"time_ticker":"1y+30"}"#; - let config: Config = serde_json::from_str(json).unwrap(); - assert_eq!( - config.time_ticker, - Some(Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30)) - ); - } -} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..5458acf --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,323 @@ +use crate::{Calc, CondUnit, DError, DResult, TimeUnit}; +use std::time::Duration; +use winnow::ascii::{digit1, multispace0}; +use winnow::combinator::{alt, opt}; +use winnow::stream::AsChar; +use winnow::token::take_while; +use winnow::PResult; +use winnow::Parser; + +fn unit_abbr(input: &mut &str) -> PResult { + take_while(1.., |c: char| c.is_alpha() || c == 'µ') + .try_map(str::parse) + .parse_next(input) +} + +fn cond_unit(input: &mut &str) -> PResult { + alt(('+'.value(CondUnit::Plus), '*'.value(CondUnit::Star))).parse_next(input) +} + +pub(crate) fn parse_expr_time(input: &mut &str) -> PResult { + (digit1, unit_abbr) + .try_map(|(v, unit)| unit.duration(v)) + .parse_next(input) +} + +pub(crate) fn cond_time<'a>(input: &mut &'a str) -> PResult> { + let mut vec = vec![]; + while !input.trim().is_empty() { + let (cond, out, time_unit) = ( + multispace0, + opt(cond_unit).map(|x| x.unwrap_or(CondUnit::Plus)), + multispace0, + digit1, + // Add by default. + // Parse unit, default is seconds. + opt(unit_abbr).map(|x| x.unwrap_or(TimeUnit::Second)), + multispace0, + ) + .map(|x| (x.1, x.3, x.4)) + .parse_next(input)?; + + vec.push((out, cond, time_unit)); + } + Ok(vec) +} + +pub fn parse(input: impl AsRef) -> DResult { + let input = input.as_ref(); + let (unit_time, cond_opt) = (parse_expr_time, opt(cond_time)) + .parse(input) + .map_err(|e| DError::DSLError(format!("{}", e)))?; + + let (init_cond, init_duration) = cond_opt + .map(|val| val.calc()) + .unwrap_or_else(|| Ok(CondUnit::init()))?; + let duration = init_cond.calc(unit_time, init_duration)?; + Ok(duration) +} + +#[cfg(test)] +#[allow(clippy::identity_op)] +mod tests { + use super::*; + use crate::DError::DSLError; + use crate::{CondUnit, DError, TimeUnit}; + use winnow::Partial; + + #[test] + fn test_time_unit_abbr() { + assert_eq!( + unit_abbr.parse_peek(&Partial::new("y")), + Ok(("", TimeUnit::Year)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("mon")), + Ok(("", TimeUnit::Month)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("w")), + Ok(("", TimeUnit::Week)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("d")), + Ok(("", TimeUnit::Day)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("h")), + Ok(("", TimeUnit::Hour)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("m")), + Ok(("", TimeUnit::Minute)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("s")), + Ok(("", TimeUnit::Second)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("ms")), + Ok(("", TimeUnit::MilliSecond)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("µs")), + Ok(("", TimeUnit::MicroSecond)) + ); + assert_eq!( + unit_abbr.parse_peek(&Partial::new("ns")), + Ok(("", TimeUnit::NanoSecond)) + ); + } + + #[test] + fn test_time_unit() { + let (input, format) = unit_abbr.parse_peek("m123").unwrap(); + assert_eq!(input, "123"); + assert_eq!(format, TimeUnit::Minute); + } + + #[test] + fn test_parse_expr_time() { + let (input, val) = parse_expr_time.parse_peek("123m").unwrap(); + assert_eq!(input, ""); + assert_eq!(val, 7380000000000); + } + + #[test] + fn test_cond_unit() { + let (input, format) = cond_unit.parse_peek("*123").unwrap(); + assert_eq!(input, "123"); + assert_eq!(format, CondUnit::Star); + } + + #[test] + fn test_cond_time() { + let (input, out) = cond_time.parse_peek(" * 60").unwrap(); + assert_eq!(input, ""); + assert_eq!(out, vec![("60", CondUnit::Star, TimeUnit::Second)]); + } + + #[test] + fn test_cond_time2() { + let (input, out) = cond_time.parse_peek(" * 60*30").unwrap(); + assert_eq!(input, ""); + assert_eq!( + out, + vec![ + ("60", CondUnit::Star, TimeUnit::Second), + ("30", CondUnit::Star, TimeUnit::Second), + ] + ); + } + + #[test] + fn test_duration_parse1() { + let duration = crate::parse("1m+31").unwrap(); + assert_eq!(duration, Duration::new(91, 0)) + } + + #[test] + fn test_duration_parse2() { + let duration = crate::parse("1m*60").unwrap(); + assert_eq!(duration, Duration::new(3600, 0)) + } + + #[test] + fn test_duration_parse3() { + let duration = crate::parse("1m*60*20").unwrap(); + assert_eq!(duration, Duration::new(72000, 0)) + } + + #[test] + fn test_duration_parse4() { + let duration = crate::parse("1m+60+24").unwrap(); + assert_eq!(duration, Duration::new(144, 0)) + } + + #[test] + fn test_duration_parse5() { + let duration = crate::parse("1m+60+24 ").unwrap(); + assert_eq!(duration, Duration::new(144, 0)) + } + + #[test] + fn test_duration_parse6() { + let duration = crate::parse("0m").unwrap(); + assert_eq!(duration, Duration::new(0, 0)) + } + + #[test] + fn test_duration_parse7() { + assert!(crate::parse("0m+3-5").is_err()) + } + + #[test] + fn test_duration_parse8() { + let duration = crate::parse("1hr").unwrap(); + assert_eq!(duration, Duration::new(3600, 0)) + } + + #[test] + fn test_parse() { + let duration = crate::parse("1d").unwrap(); + assert_eq!(duration, Duration::new(24 * 60 * 60, 0)); + + let duration = crate::parse("3m+31").unwrap(); //the default duration unit is second. + assert_eq!(duration, Duration::new(211, 0)); + + let duration = crate::parse("3m + 31").unwrap(); //the default duration unit is second. + assert_eq!(duration, Duration::new(211, 0)); + + let duration = crate::parse("3m + 13s + 29ms").unwrap(); + assert_eq!(duration, Duration::new(193, 29 * 1000 * 1000 + 0 + 0)); + + let duration = crate::parse("3m + 1s + 29ms +17µs").unwrap(); + assert_eq!( + duration, + Duration::new(181, 29 * 1000 * 1000 + 17 * 1000 + 0) + ); + + let duration = crate::parse("1m*10").unwrap(); //the default duration unit is second. + assert_eq!(duration, Duration::new(600, 0)); + + let duration = crate::parse("1m*10ms").unwrap(); + assert_eq!(duration, Duration::new(0, 600 * 1000 * 1000)); + + let duration = crate::parse("1m * 1ns").unwrap(); + assert_eq!(duration, Duration::new(0, 60)); + + let duration = crate::parse("1m * 1m").unwrap(); + assert_eq!(duration, Duration::new(3600, 0)); + } + + #[test] + fn test_overflow_plus() { + let result = crate::parse("10000000000000000y+60"); + assert_eq!( + result, + Err(DSLError( + r#" +10000000000000000y+60 +^ +overflow error"# + .trim() + .to_string() + )) + ); + } + + #[test] + fn test_max_mul() { + let duration = crate::parse("580y*1").unwrap(); + assert_eq!( + duration, + std::time::Duration::from_millis(18290880000) * 1000 + ); + } + + #[test] + fn test_overflow_mul() { + let result = crate::parse("580y*2"); + assert_eq!(result, Err(DError::OverflowError)); + } +} + +#[cfg(all(test, feature = "chrono"))] +mod chrono_tests { + use crate::{ + after_naive_date, after_naive_date_time, before_naive_date, before_naive_date_time, + parse_chrono, + }; + use chrono::{Datelike, Utc}; + + #[test] + fn test_parse_chrono() { + use chrono::Duration; + let duration = parse_chrono("1m+60+24 ").unwrap(); + assert_eq!(duration, Duration::seconds(144)) + } + + #[test] + fn test_after_naive_date_time() { + let date = Utc::now().naive_utc().date(); + let jd = date.num_days_from_ce() + 180; + let date = after_naive_date_time("180d").unwrap(); + assert_eq!(date.num_days_from_ce(), jd) + } + + #[test] + fn test_after_naive_date() { + let date = Utc::now().naive_utc().date(); + let jd = date.num_days_from_ce() + 180; + let date = after_naive_date("180d").unwrap(); + assert_eq!(date.num_days_from_ce(), jd) + } + + #[test] + fn test_before_naive_date_time() { + let date = Utc::now().naive_utc().date(); + let jd = date.num_days_from_ce() - 180; + let date = before_naive_date_time("180d").unwrap(); + assert_eq!(date.num_days_from_ce(), jd) + } + + #[test] + fn test_before_naive_date() { + let date = Utc::now().naive_utc().date(); + let jd = date.num_days_from_ce() - 180; + let date = before_naive_date("180d").unwrap(); + assert_eq!(date.num_days_from_ce(), jd) + } +} + +#[cfg(all(test, feature = "time"))] +mod time_tests { + use crate::parse_time; + use time::Duration; + + #[test] + fn test_parse_time() { + let duration = parse_time("1m+60+24 ").unwrap(); + assert_eq!(duration, Duration::seconds(144)) + } +} diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..fd482ed --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,270 @@ +use crate::{parse_chrono, parse_std, parse_time}; +use std::time::Duration; + +#[cfg(all(feature = "chrono", feature = "serde"))] +use chrono::Duration as CDuration; + +#[cfg(all(feature = "time", feature = "serde"))] +use time::Duration as TDuration; + +#[cfg(feature = "serde")] +macro_rules! des_duration { + ($name:ident,$duration_type:ident,$fn_name:ident,$parse:ident) => { + struct $name; + impl<'de> serde::de::Visitor<'de> for $name { + type Value = $duration_type; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("expect duration string,e.g:'1min+30'") + } + + fn visit_str(self, s: &str) -> Result + where + E: serde::de::Error, + { + let duration = $parse(s).map_err(serde::de::Error::custom)?; + Ok(duration) + } + } + + pub fn $fn_name<'de, D>(deserializer: D) -> Result<$duration_type, D::Error> + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any($name) + } + }; +} + +#[cfg(feature = "serde")] +macro_rules! des_option_duration { + ($name:ident,$duration_type:ident,$fn_name:ident,$parse:ident) => { + struct $name; + impl<'de> serde::de::Visitor<'de> for $name { + type Value = Option<$duration_type>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("expect duration string,e.g:'1min+30'") + } + + fn visit_some(self, d: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::Deserialize; + let s: Option = Option::deserialize(d)?; + if let Some(s) = s { + let duration = $parse(s).map_err(serde::de::Error::custom)?; + return Ok(Some(duration)); + } + Ok(None) + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(None) + } + } + + pub fn $fn_name<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_option($name) + } + }; +} + +#[cfg(feature = "serde")] +des_duration!(DurationStd, Duration, deserialize_duration, parse_std); + +#[cfg(feature = "serde")] +des_option_duration!( + OptionDurationStd, + Duration, + deserialize_option_duration, + parse_std +); + +#[cfg(all(feature = "chrono", feature = "serde"))] +des_duration!( + DurationChrono, + CDuration, + deserialize_duration_chrono, + parse_chrono +); + +#[cfg(all(feature = "chrono", feature = "serde"))] +des_option_duration!( + OptionDurationChrono, + CDuration, + deserialize_option_duration_chrono, + parse_chrono +); + +#[cfg(all(feature = "time", feature = "serde"))] +des_duration!( + DurationTime, + TDuration, + deserialize_duration_time, + parse_time +); + +#[cfg(all(feature = "time", feature = "serde"))] +des_option_duration!( + OptionDurationTime, + TDuration, + deserialize_option_duration_time, + parse_time +); + +#[cfg(all(test, feature = "time"))] +mod tests { + use super::*; + use crate::ONE_YEAR_NANOSECOND; + use serde::*; + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_duration_time() { + #[derive(Debug, Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_duration_time")] + time_ticker: TDuration, + } + let json = r#"{"time_ticker":"1y+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config.time_ticker, + TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30) + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_option_duration_time() { + use TDuration; + + #[derive(Debug, Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_option_duration_time")] + time_ticker: Option, + } + let json = r#"{"time_ticker":"1y+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config.time_ticker, + Some(TDuration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + TDuration::seconds(30)) + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_duration_chrono() { + use chrono::Duration; + #[derive(Debug, serde::Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_duration_chrono")] + time_ticker: Duration, + } + let json = r#"{"time_ticker":"1y+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config.time_ticker, + Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30) + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_option_duration_chrono() { + use chrono::Duration; + #[derive(Debug, serde::Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_option_duration_chrono")] + time_ticker: Option, + } + let json = r#"{"time_ticker":"1y+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config.time_ticker, + Some(Duration::nanoseconds(ONE_YEAR_NANOSECOND as i64) + Duration::seconds(30)) + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_duration() { + #[derive(Debug, serde::Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_duration")] + time_ticker: std::time::Duration, + } + let json = r#"{"time_ticker":"1min+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!(config.time_ticker, std::time::Duration::from_secs(90)); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_option_duration() { + #[derive(Debug, serde::Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_option_duration")] + time_ticker: Option, + } + let json = r#"{"time_ticker":"1min+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!(config.time_ticker, Some(std::time::Duration::from_secs(90))); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_duration2() { + #[derive(Debug, serde::Deserialize)] + struct Config { + #[serde(deserialize_with = "deserialize_duration")] + time_ticker: std::time::Duration, + } + let json = r#"{"time_ticker":"1y+30"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config.time_ticker, + std::time::Duration::from_nanos(ONE_YEAR_NANOSECOND) + + std::time::Duration::from_secs(30) + ); + } + + #[cfg(feature = "serde")] + #[test] + fn test_deserialize_option_duration2() { + #[derive(Debug, serde::Deserialize, PartialEq)] + struct Config { + #[serde(default, deserialize_with = "deserialize_option_duration")] + time_ticker: Option, + name: String, + } + let json = r#"{"time_ticker":null,"name":"foo"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + + assert_eq!( + config, + Config { + time_ticker: None, + name: "foo".into(), + } + ); + + let json = r#"{"name":"foo"}"#; + let config: Config = serde_json::from_str(json).unwrap(); + assert_eq!( + config, + Config { + time_ticker: None, + name: "foo".into(), + } + ); + } +}