use std::str::FromStr;
use std::time::Duration;
use thiserror::Error;
use crate::job_trigger::JobTrigger;
pub struct IntervalTrigger {
interval: Duration,
}
impl FromStr for IntervalTrigger {
type Err = IntervalTriggerFromStrError;
fn from_str(schedule: &str) -> Result<Self, Self::Err> {
Ok(Self {
interval: parse_duration(schedule).map_err(IntervalTriggerFromStrError)?,
})
}
}
impl IntervalTrigger {
pub fn new(duration: Duration) -> Self {
Self {
interval: duration
}
}
pub fn interval(&self) -> Duration {
self.interval
}
}
#[async_trait::async_trait]
impl JobTrigger for IntervalTrigger {
async fn next(&self) {
tracing::debug!("waiting {}ms", self.interval.as_millis());
tokio::time::sleep(self.interval).await;
}
}
#[derive(Debug, Error)]
#[error(transparent)]
pub struct IntervalTriggerFromStrError(ParseDurationError);
#[derive(Debug, Error)]
enum ParseDurationError {
#[error("Unexpected character")]
UnexpectedCharacter,
#[error("Invalid order")]
InvalidOrder,
}
fn parse_duration(duration_str: &str) -> Result<Duration, ParseDurationError> {
if !duration_str.starts_with("PT") {
return Err(ParseDurationError::UnexpectedCharacter);
}
let mut value = 0;
let mut hours_parsed = false;
let mut minutes_parsed = false;
let mut seconds_parsed = false;
let mut duration = Duration::ZERO;
for character in duration_str.trim_start_matches("PT").chars() {
if let Some(digit) = character.to_digit(10) {
value = value * 10 + digit;
continue;
}
match character.to_ascii_lowercase() {
'h' => {
if hours_parsed || minutes_parsed || seconds_parsed {
return Err(ParseDurationError::InvalidOrder);
}
duration += value * Duration::from_secs(3600);
hours_parsed = true;
value = 0;
}
'm' => {
if minutes_parsed || seconds_parsed {
return Err(ParseDurationError::InvalidOrder);
}
duration += value * Duration::from_secs(60);
minutes_parsed = true;
value = 0;
}
's' => {
if seconds_parsed {
return Err(ParseDurationError::InvalidOrder);
}
duration += value * Duration::from_secs(1);
seconds_parsed = true;
value = 0;
}
_ => return Err(ParseDurationError::UnexpectedCharacter)
}
}
Ok(duration)
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::parse_duration;
#[test]
fn test_parser() {
assert("PT1S", 1);
assert("PT1M", 60);
assert("PT1H", 3600);
assert("PT13S", 13);
assert("PT13M", 13 * 60);
assert("PT13H", 13 * 3600);
assert("PT1039H219M13S", 1039 * 3600 + 219 * 60 + 13);
assert_error("PT1S1H", "Invalid order");
assert_error("PT-1H", "Unexpected character");
assert_error("P2DT1H", "Unexpected character");
}
fn assert(string: &str, expected_duration_seconds: u64) {
let duration = parse_duration(string).unwrap();
assert_eq!(duration, Duration::from_secs(expected_duration_seconds))
}
fn assert_error(string: &str, error_message: &str) {
let error = parse_duration(string).unwrap_err();
assert_eq!(error.to_string(), error_message)
}
}