Skip to content

Commit

Permalink
Merge pull request #35 from baoyachi/issue/33
Browse files Browse the repository at this point in the history
refactor Error report code.
  • Loading branch information
baoyachi authored Apr 21, 2024
2 parents 6bd0076 + f8cfbb5 commit fd6b124
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "duration-str"
version = "0.8.1-pre"
version = "0.9.0-pre"
authors = ["baoyachi <liaoymxsdl@gmail.com>"]
edition = "2021"
description = "duration string parser"
Expand Down
26 changes: 11 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,15 @@ pub type DResult<T> = Result<T, DError>;

#[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),
#[error("overflow error")]
OverflowError,
}

#[derive(Debug, Eq, PartialEq, Default)]
#[derive(Debug, Eq, PartialEq, Default, Clone)]
enum TimeUnit {
Year,
Month,
Expand Down Expand Up @@ -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<str>) -> DResult<Duration> {
pub fn parse_std(input: impl AsRef<str>) -> Result<Duration, String> {
parse(input.as_ref())
}

Expand Down Expand Up @@ -437,10 +435,9 @@ pub fn parse_std(input: impl AsRef<str>) -> DResult<Duration> {
/// assert_eq!(duration,Duration::seconds(600));
/// ```
#[cfg(feature = "chrono")]
pub fn parse_chrono(input: impl AsRef<str>) -> DResult<chrono::Duration> {
pub fn parse_chrono(input: impl AsRef<str>) -> Result<chrono::Duration, String> {
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)
}

Expand Down Expand Up @@ -481,16 +478,15 @@ pub fn parse_chrono(input: impl AsRef<str>) -> DResult<chrono::Duration> {
/// assert_eq!(duration,Duration::seconds(600));
/// ```
#[cfg(feature = "time")]
pub fn parse_time(input: impl AsRef<str>) -> DResult<time::Duration> {
pub fn parse_time(input: impl AsRef<str>) -> Result<time::Duration, String> {
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)]
Expand All @@ -503,7 +499,7 @@ mod naive_date {
pub fn calc_naive_date_time(
input: impl AsRef<str>,
history: TimeHistory,
) -> DResult<chrono::NaiveDateTime> {
) -> Result<chrono::NaiveDateTime, String> {
let duration = parse_chrono(input)?;
let time = match history {
TimeHistory::Before => (Utc::now() - duration).naive_utc(),
Expand All @@ -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<str>) -> DResult<chrono::NaiveDateTime> {
pub fn $date_time(input: impl AsRef<str>) -> Result<chrono::NaiveDateTime, String> {
calc_naive_date_time(input, $history)
}

#[allow(dead_code)]
#[cfg(feature = "chrono")]
pub fn $date(input: impl AsRef<str>) -> DResult<chrono::NaiveDate> {
pub fn $date(input: impl AsRef<str>) -> Result<chrono::NaiveDate, String> {
let date: chrono::NaiveDateTime = calc_naive_date_time(input, $history)?;
Ok(date.date())
}
Expand Down
140 changes: 114 additions & 26 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -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<TimeUnit> {
take_while(1.., |c: char| c.is_alpha() || c == 'µ')
.try_map(str::parse)
.parse_next(input)
#[derive(Debug, PartialEq, Eq)]
pub struct PError<I> {
partial_input: I,
kind: ErrorKind,
cause: String,
}

impl<I> PError<I> {
fn new(input: I, kind: ErrorKind) -> Self {
PError {
partial_input: input,
kind,
cause: "".to_string(),
}
}
}

impl<I: Stream + Clone> ParserError<I> for PError<I> {
fn from_error_kind(input: &I, kind: ErrorKind) -> Self {
PError::new(input.clone(), kind)
}

fn append(self, _: &I, _: &<I as Stream>::Checkpoint, _: ErrorKind) -> Self {
self
}
}

fn cond_unit(input: &mut &str) -> PResult<CondUnit> {
impl<I: Clone, E: std::error::Error + Send + Sync + 'static> FromExternalError<I, E> for PError<I> {
#[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<I> Display for PError<I>
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<TimeUnit, PError<&'a str>> {
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<CondUnit, PError<&'a str>> {
alt(('+'.value(CondUnit::Plus), '*'.value(CondUnit::Star))).parse_next(input)
}

pub(crate) fn parse_expr_time(input: &mut &str) -> PResult<u64> {
pub(crate) fn parse_expr_time<'a>(input: &mut &'a str) -> PResult<u64, PError<&'a str>> {
(
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<Vec<(&'a str, CondUnit, TimeUnit)>> {
pub(crate) fn cond_time<'a>(
input: &mut &'a str,
) -> PResult<Vec<(&'a str, CondUnit, TimeUnit)>, PError<&'a str>> {
let mut vec = vec![];
while !input.trim().is_empty() {
let (cond, out, time_unit) = (
Expand All @@ -49,25 +107,27 @@ pub(crate) fn cond_time<'a>(input: &mut &'a str) -> PResult<Vec<(&'a str, CondUn
Ok(vec)
}

pub fn parse(input: impl AsRef<str>) -> DResult<Duration> {
pub fn parse(input: impl AsRef<str>) -> Result<Duration, String> {
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)
}

#[cfg(test)]
#[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]
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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())
);
}

Expand All @@ -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");
}
}

Expand Down

0 comments on commit fd6b124

Please sign in to comment.