From 7c8973d92d9de9704834dd3026aadfc3a2f56239 Mon Sep 17 00:00:00 2001 From: baoyachi Date: Mon, 22 Apr 2024 01:30:37 +0800 Subject: [PATCH] refactor Error report code. --- Cargo.toml | 2 +- src/lib.rs | 26 ++++------ src/parser.rs | 140 ++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 126 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ee0656d..7ed9513 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "duration-str" -version = "0.8.1" +version = "0.9.0" authors = ["baoyachi "] edition = "2021" description = "duration string parser" diff --git a/src/lib.rs b/src/lib.rs index bd4090d..81c460d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,9 +188,7 @@ pub type DResult = Result; #[derive(Error, Debug, PartialEq)] pub enum DError { - #[error("dls express error: `{0}`")] - DSLError(String), - #[error("parser error: `{0}`")] + #[error("`{0}`")] ParseError(String), #[error("`{0}`")] NormalError(String), @@ -198,7 +196,7 @@ pub enum DError { OverflowError, } -#[derive(Debug, Eq, PartialEq, Default)] +#[derive(Debug, Eq, PartialEq, Default, Clone)] enum TimeUnit { Year, Month, @@ -396,7 +394,7 @@ impl ToString for CondUnit { /// let duration = parse("1m * 10").unwrap(); /// assert_eq!(duration,Duration::new(600,0)); /// ``` -pub fn parse_std(input: impl AsRef) -> DResult { +pub fn parse_std(input: impl AsRef) -> Result { parse(input.as_ref()) } @@ -437,10 +435,9 @@ pub fn parse_std(input: impl AsRef) -> DResult { /// assert_eq!(duration,Duration::seconds(600)); /// ``` #[cfg(feature = "chrono")] -pub fn parse_chrono(input: impl AsRef) -> DResult { +pub fn parse_chrono(input: impl AsRef) -> Result { let std_duration = parse_std(input)?; - let duration = chrono::Duration::from_std(std_duration) - .map_err(|e| DError::ParseError(format!("{}", e)))?; + let duration = chrono::Duration::from_std(std_duration).map_err(|e| e.to_string())?; Ok(duration) } @@ -481,16 +478,15 @@ pub fn parse_chrono(input: impl AsRef) -> DResult { /// assert_eq!(duration,Duration::seconds(600)); /// ``` #[cfg(feature = "time")] -pub fn parse_time(input: impl AsRef) -> DResult { +pub fn parse_time(input: impl AsRef) -> Result { let std_duration = parse_std(input)?; - let duration = - time::Duration::try_from(std_duration).map_err(|e| DError::ParseError(format!("{}", e)))?; + let duration = time::Duration::try_from(std_duration).map_err(|e| e.to_string())?; Ok(duration) } #[cfg(feature = "chrono")] mod naive_date { - use crate::{parse_chrono, DResult}; + use crate::parse_chrono; use chrono::Utc; #[allow(dead_code)] @@ -503,7 +499,7 @@ mod naive_date { pub fn calc_naive_date_time( input: impl AsRef, history: TimeHistory, - ) -> DResult { + ) -> Result { let duration = parse_chrono(input)?; let time = match history { TimeHistory::Before => (Utc::now() - duration).naive_utc(), @@ -516,13 +512,13 @@ mod naive_date { ($date_time:ident,$date:ident,$history:expr) => { #[allow(dead_code)] #[cfg(feature = "chrono")] - pub fn $date_time(input: impl AsRef) -> DResult { + pub fn $date_time(input: impl AsRef) -> Result { calc_naive_date_time(input, $history) } #[allow(dead_code)] #[cfg(feature = "chrono")] - pub fn $date(input: impl AsRef) -> DResult { + pub fn $date(input: impl AsRef) -> Result { let date: chrono::NaiveDateTime = calc_naive_date_time(input, $history)?; Ok(date.date()) } diff --git a/src/parser.rs b/src/parser.rs index 09f6b04..dd1476c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,34 +1,92 @@ -use crate::{Calc, CondUnit, DError, DResult, TimeUnit}; +use crate::{Calc, CondUnit, TimeUnit}; +use std::fmt::{Display, Formatter}; use std::time::Duration; use winnow::ascii::{digit1, multispace0}; -use winnow::combinator::{alt, opt}; -use winnow::stream::AsChar; +use winnow::combinator::{alt, eof, opt}; +use winnow::error::{ErrMode, ErrorKind, FromExternalError, ParserError}; +use winnow::stream::{AsChar, Stream}; 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) +#[derive(Debug, PartialEq, Eq)] +pub struct PError { + partial_input: I, + kind: ErrorKind, + cause: String, +} + +impl PError { + fn new(input: I, kind: ErrorKind) -> Self { + PError { + partial_input: input, + kind, + cause: "".to_string(), + } + } +} + +impl ParserError for PError { + fn from_error_kind(input: &I, kind: ErrorKind) -> Self { + PError::new(input.clone(), kind) + } + + fn append(self, _: &I, _: &::Checkpoint, _: ErrorKind) -> Self { + self + } } -fn cond_unit(input: &mut &str) -> PResult { +impl FromExternalError for PError { + #[inline] + fn from_external_error(input: &I, kind: ErrorKind, e: E) -> Self { + let mut err = Self::new(input.clone(), kind); + { + err.cause = e.to_string(); + } + err + } +} + +impl Display for PError +where + I: Display, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "partial_input:`{}`,{}", self.partial_input, self.kind)?; + if !self.cause.is_empty() { + write!(f, ",{}", self.cause)?; + } + Ok(()) + } +} + +fn unit_abbr<'a>(input: &mut &'a str) -> PResult> { + let checkpoint = input.checkpoint(); + let val = take_while(1.., |c: char| c.is_alpha() || c == 'µ').parse_next(input)?; + str::parse(val).map_err(|err| { + input.reset(&checkpoint); + ErrMode::from_external_error(input, ErrorKind::Fail, err) + }) +} + +fn cond_unit<'a>(input: &mut &'a str) -> PResult> { alt(('+'.value(CondUnit::Plus), '*'.value(CondUnit::Star))).parse_next(input) } -pub(crate) fn parse_expr_time(input: &mut &str) -> PResult { +pub(crate) fn parse_expr_time<'a>(input: &mut &'a str) -> PResult> { ( multispace0, digit1, - opt(unit_abbr).map(Option::unwrap_or_default), + alt(((multispace0, eof).value(TimeUnit::default()), unit_abbr)), ) .map(|x| (x.1, x.2)) .try_map(|(v, unit)| unit.duration(v)) .parse_next(input) } -pub(crate) fn cond_time<'a>(input: &mut &'a str) -> PResult> { +pub(crate) fn cond_time<'a>( + input: &mut &'a str, +) -> PResult, PError<&'a str>> { let mut vec = vec![]; while !input.trim().is_empty() { let (cond, out, time_unit) = ( @@ -49,16 +107,19 @@ pub(crate) fn cond_time<'a>(input: &mut &'a str) -> PResult) -> DResult { +pub fn parse(input: impl AsRef) -> Result { 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)))?; + .map_err(|e| 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)?; + .unwrap_or_else(|| Ok(CondUnit::init())) + .map_err(|err| err.to_string())?; + let duration = init_cond + .calc(unit_time, init_duration) + .map_err(|err| err.to_string())?; Ok(duration) } @@ -66,8 +127,7 @@ pub fn parse(input: impl AsRef) -> DResult { #[allow(clippy::identity_op)] mod tests { use super::*; - use crate::DError::DSLError; - use crate::{CondUnit, DError, TimeUnit}; + use crate::{CondUnit, TimeUnit}; use winnow::Partial; #[test] @@ -160,6 +220,12 @@ mod tests { let duration = parse("0").unwrap(); assert_eq!(duration, Duration::new(0, 0)); + let duration = parse("0 ").unwrap(); + assert_eq!(duration, Duration::new(0, 0)); + + let duration = parse(" 0 ").unwrap(); + assert_eq!(duration, Duration::new(0, 0)); + let duration = parse("1").unwrap(); assert_eq!(duration, Duration::new(1, 0)); @@ -190,7 +256,31 @@ mod tests { #[test] fn test_duration_err() { - assert!(parse("0m+3-5").is_err()) + assert_eq!( + parse("0m+3-5").err().unwrap(), + r#" +0m+3-5 + ^ +partial_input:`+3-5`,error Eof"# + .trim() + ); + + let err = format!("{}", parse("0mxyz").err().unwrap()); + assert_eq!(err, r#" +0mxyz + ^ +partial_input:`mxyz`,error Fail,`expect one of [y,mon,w,d,h,m,s,ms,µs,us,ns] or their longer forms.but find:mxyz`"#.trim()); + + //TODO lost cause, need fix + let err = format!("{}", parse("3ms-2ms").err().unwrap()); + assert_eq!( + err, + r#" +3ms-2ms + ^ +partial_input:`-2ms`,error Eof"# + .trim() + ); } #[test] @@ -231,14 +321,12 @@ mod tests { let result = parse("10000000000000000y+60"); assert_eq!( result, - Err(DSLError( - r#" + Err(r#" 10000000000000000y+60 ^ -overflow error"# - .trim() - .to_string() - )) +partial_input:`10000000000000000y+60`,error Verify,overflow error"# + .trim() + .to_string()) ); } @@ -253,8 +341,8 @@ overflow error"# #[test] fn test_overflow_mul() { - let result = parse("580y*2"); - assert_eq!(result, Err(DError::OverflowError)); + let err = parse("580y*2").err().unwrap(); + assert_eq!(err, "overflow error"); } }