silent 2.16.1

Silent Web Framework
Documentation
use chrono::{DateTime, Local, Utc};
use cron::Schedule;
use serde::{Serialize, Serializer};
use std::str::FromStr;

#[derive(Debug, Clone)]
pub enum ProcessTime {
    Datetime(DateTime<Local>),
    Crontab(Box<Schedule>),
}

impl Serialize for ProcessTime {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            ProcessTime::Datetime(d) => serializer.serialize_str(&d.to_string()),
            ProcessTime::Crontab(s) => serializer.serialize_str(&s.to_string()),
        }
    }
}

impl TryFrom<String> for ProcessTime {
    type Error = anyhow::Error;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        match DateTime::<Local>::from_str(&value) {
            Ok(datetime) => Ok(ProcessTime::Datetime(datetime)),
            Err(_) => Ok(ProcessTime::Crontab(Box::from(Schedule::from_str(&value)?))),
        }
    }
}

impl TryFrom<&str> for ProcessTime {
    type Error = anyhow::Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match DateTime::<Local>::from_str(value) {
            Ok(datetime) => Ok(ProcessTime::Datetime(datetime)),
            Err(_) => Ok(ProcessTime::Crontab(Box::from(Schedule::from_str(value)?))),
        }
    }
}

impl TryFrom<DateTime<Local>> for ProcessTime {
    type Error = anyhow::Error;

    fn try_from(value: DateTime<Local>) -> Result<Self, Self::Error> {
        Ok(ProcessTime::Datetime(value))
    }
}

impl TryFrom<DateTime<Utc>> for ProcessTime {
    type Error = anyhow::Error;

    fn try_from(value: DateTime<Utc>) -> Result<Self, Self::Error> {
        Ok(ProcessTime::Datetime(value.into()))
    }
}

impl ProcessTime {
    pub(crate) fn is_active(&self) -> bool {
        match self {
            ProcessTime::Datetime(datetime) => {
                datetime.timestamp_millis() <= Local::now().timestamp_millis()
            }
            ProcessTime::Crontab(crontab) => crontab.includes(Local::now()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_process_time() {
        let datetime = Local::now();
        let process_time =
            ProcessTime::Datetime(datetime + chrono::TimeDelta::try_seconds(10).unwrap());
        assert!(!process_time.is_active());
        let process_time = ProcessTime::try_from(datetime).unwrap();
        assert!(process_time.is_active());
        let process_time =
            ProcessTime::Crontab(Box::from(Schedule::from_str("* * * * * *").unwrap()));
        assert!(process_time.is_active());
        let process_time =
            ProcessTime::Crontab(Box::from(Schedule::from_str("0 0 0 1 1 ? 2015").unwrap()));
        assert!(!process_time.is_active());
        assert!(ProcessTime::try_from("2023-01-01T00:00:00Z".to_string()).is_ok());
        assert!(ProcessTime::try_from("2023-01-01 00:00:00").is_err());
    }
}