caldata 0.16.2

Ical/Vcard parser for Rust
Documentation
use std::collections::HashMap;

use crate::{
    generator::Emitter,
    parser::{ContentLine, ContentLineParams, ICalProperty, ParseProp, ParserError},
    types::{CalDateOrDateTime, Value},
};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum RecurIdRange {
    #[default]
    This,
    ThisAndFuture,
}
#[derive(Debug, Clone)]
pub struct IcalRECURIDProperty(
    pub CalDateOrDateTime,
    pub ContentLineParams,
    pub RecurIdRange,
);
impl ICalProperty for IcalRECURIDProperty {
    const NAME: &'static str = "RECURRENCE-ID";
    const DEFAULT_TYPE: &'static str = "DATE-TIME";

    fn parse_prop(
        prop: &ContentLine,
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
    ) -> Result<Self, ParserError> {
        let dt = ParseProp::parse_prop(prop, timezones, Self::DEFAULT_TYPE)?;
        let range = match prop.params.get_param("RANGE") {
            Some("THISANDFUTURE") => RecurIdRange::ThisAndFuture,
            None => RecurIdRange::This,
            _ => return Err(ParserError::InvalidPropertyType(prop.generate())),
        };
        Ok(Self(dt, prop.params.clone(), range))
    }

    fn utc_or_local(self) -> Self {
        let Self(dt, mut params, range) = self;
        params.remove("TZID");
        Self(dt.utc_or_local(), params, range)
    }
}
impl IcalRECURIDProperty {
    pub fn validate_dtstart(&self, dtstart: &CalDateOrDateTime) -> Result<(), ParserError> {
        if (self.0.is_date() != dtstart.is_date())
            || (self.0.timezone().is_local() != dtstart.timezone().is_local())
        {
            return Err(ParserError::DtstartNotMatchingRecurId);
        }
        Ok(())
    }
}

impl From<IcalRECURIDProperty> for crate::property::ContentLine {
    fn from(value: IcalRECURIDProperty) -> Self {
        let mut params = value.1;
        let value_type = value.0.value_type();
        if value_type != IcalRECURIDProperty::DEFAULT_TYPE {
            params.replace_param("VALUE".to_owned(), value_type.to_owned());
        }
        if value.2 == RecurIdRange::ThisAndFuture {
            params.replace_param("RANGE".to_owned(), "THISANDFUTURE".to_owned());
        }
        Self {
            name: IcalRECURIDProperty::NAME.to_owned(),
            params,
            value: value.0.format(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::IcalRECURIDProperty;
    use crate::{generator::Emitter, parser::ICalProperty, property::ContentLine};
    use rstest::rstest;

    #[rstest]
    #[case("RECURRENCE-ID;VALUE=DATE:19960401\r\n")]
    #[case("RECURRENCE-ID;RANGE=THISANDFUTURE:19960120T120000Z\r\n")]
    fn roundtrip(#[case] input: &str) {
        let content_line = crate::ContentLineParser::from_slice(input.as_bytes())
            .next()
            .unwrap()
            .unwrap();
        let prop = IcalRECURIDProperty::parse_prop(&content_line, None).unwrap();
        let roundtrip: ContentLine = prop.into();
        similar_asserts::assert_eq!(roundtrip.generate(), input);
    }
}