indieweb 0.10.0

A collection of utilities for working with the IndieWeb.
Documentation
use std::str::FromStr;

#[derive(Debug, Clone)]
pub struct PublishDelay(pub iso8601_duration::Duration);

impl PublishDelay {
    pub fn new(duration: iso8601_duration::Duration) -> Self {
        Self(duration)
    }

    pub fn duration(&self) -> &iso8601_duration::Duration {
        &self.0
    }

    pub fn to_seconds(&self) -> Option<f32> {
        self.0.num_seconds()
    }

    pub fn to_std_duration(&self) -> Option<std::time::Duration> {
        self.0.to_std()
    }
}

impl PartialEq for PublishDelay {
    fn eq(&self, other: &Self) -> bool {
        format!("{}", self.0) == format!("{}", other.0)
    }
}

impl Eq for PublishDelay {}

impl std::hash::Hash for PublishDelay {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        format!("{}", self.0).hash(state);
    }
}

impl FromStr for PublishDelay {
    type Err = crate::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse::<iso8601_duration::Duration>()
            .map(PublishDelay)
            .map_err(|_| crate::Error::InvalidDuration(s.to_string()))
    }
}

impl std::fmt::Display for PublishDelay {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl serde::Serialize for PublishDelay {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.to_string().serialize(serializer)
    }
}

impl<'de> serde::Deserialize<'de> for PublishDelay {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        String::deserialize(deserializer).and_then(|s| s.parse().map_err(serde::de::Error::custom))
    }
}

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

    #[test]
    fn parse_duration_minutes() {
        let delay: PublishDelay = "PT3M".parse().unwrap();
        assert_eq!(delay.to_seconds(), Some(180.0));
    }

    #[test]
    fn parse_duration_hours_minutes() {
        let delay: PublishDelay = "PT1H30M".parse().unwrap();
        assert_eq!(delay.to_seconds(), Some(5400.0));
    }

    #[test]
    fn parse_duration_days() {
        let delay: PublishDelay = "P1D".parse().unwrap();
        assert_eq!(delay.to_seconds(), Some(86400.0));
    }

    #[test]
    fn parse_duration_weeks() {
        let delay: PublishDelay = "P1W".parse().unwrap();
        assert_eq!(delay.to_seconds(), Some(604800.0));
    }

    #[test]
    fn parse_duration_invalid() {
        assert!("invalid".parse::<PublishDelay>().is_err());
        assert!("PT".parse::<PublishDelay>().is_err());
    }

    #[test]
    fn duration_serialization() {
        let delay: PublishDelay = "PT3M".parse().unwrap();
        assert_eq!(serde_json::to_string(&delay).unwrap(), "\"PT3M\"");
    }

    #[test]
    fn duration_deserialization() {
        let delay: PublishDelay = serde_json::from_str("\"PT3M\"").unwrap();
        assert_eq!(delay.to_seconds(), Some(180.0));
    }

    #[test]
    fn duration_equality() {
        let d1: PublishDelay = "PT3M".parse().unwrap();
        let d2: PublishDelay = "PT3M".parse().unwrap();
        let d3: PublishDelay = "PT5M".parse().unwrap();
        assert_eq!(d1, d2);
        assert_ne!(d1, d3);
    }
}