use chrono::NaiveDateTime; use quick_xml::events::Event; use super::caltypes::*; use super::error::ParsingError; use super::types as dav; use super::xml::{IRead, QRead, Reader, CAL_URN, DAV_URN}; // ---- ROOT ELEMENTS --- impl QRead> for MkCalendar { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "mkcalendar").await?; let set = xml.find().await?; xml.close().await?; Ok(MkCalendar(set)) } } impl QRead> for MkCalendarResponse { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "mkcalendar-response").await?; let propstats = xml.collect().await?; xml.close().await?; Ok(MkCalendarResponse(propstats)) } } impl QRead> for Report { async fn qread(xml: &mut Reader) -> Result { match CalendarQuery::::qread(xml).await { Err(ParsingError::Recoverable) => (), otherwise => return otherwise.map(Self::Query), } match CalendarMultiget::::qread(xml).await { Err(ParsingError::Recoverable) => (), otherwise => return otherwise.map(Self::Multiget), } FreeBusyQuery::qread(xml).await.map(Self::FreeBusy) } } impl QRead> for CalendarQuery { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "calendar-query").await?; let (mut selector, mut filter, mut timezone) = (None, None, None); loop { let mut dirty = false; xml.maybe_read(&mut selector, &mut dirty).await?; xml.maybe_read(&mut filter, &mut dirty).await?; xml.maybe_read(&mut timezone, &mut dirty).await?; if !dirty { match xml.peek() { Event::End(_) => break, _ => xml.skip().await?, }; } } xml.close().await?; match filter { Some(filter) => Ok(CalendarQuery { selector, filter, timezone, }), _ => Err(ParsingError::MissingChild), } } } impl QRead> for CalendarMultiget { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "calendar-multiget").await?; let mut selector = None; let mut href = Vec::new(); loop { let mut dirty = false; xml.maybe_read(&mut selector, &mut dirty).await?; xml.maybe_push(&mut href, &mut dirty).await?; if !dirty { match xml.peek() { Event::End(_) => break, _ => xml.skip().await?, }; } } xml.close().await?; Ok(CalendarMultiget { selector, href }) } } impl QRead for FreeBusyQuery { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "free-busy-query").await?; let range = xml.find().await?; xml.close().await?; Ok(FreeBusyQuery(range)) } } // ---- EXTENSIONS --- impl QRead for Violation { async fn qread(xml: &mut Reader) -> Result { if xml .maybe_open(DAV_URN, "resource-must-be-null") .await? .is_some() { xml.close().await?; Ok(Self::ResourceMustBeNull) } else if xml.maybe_open(DAV_URN, "need-privileges").await?.is_some() { xml.close().await?; Ok(Self::NeedPrivileges) } else if xml .maybe_open(CAL_URN, "calendar-collection-location-ok") .await? .is_some() { xml.close().await?; Ok(Self::CalendarCollectionLocationOk) } else if xml .maybe_open(CAL_URN, "valid-calendar-data") .await? .is_some() { xml.close().await?; Ok(Self::ValidCalendarData) } else if xml .maybe_open(CAL_URN, "initialize-calendar-collection") .await? .is_some() { xml.close().await?; Ok(Self::InitializeCalendarCollection) } else if xml .maybe_open(CAL_URN, "supported-calendar-data") .await? .is_some() { xml.close().await?; Ok(Self::SupportedCalendarData) } else if xml .maybe_open(CAL_URN, "valid-calendar-object-resource") .await? .is_some() { xml.close().await?; Ok(Self::ValidCalendarObjectResource) } else if xml .maybe_open(CAL_URN, "supported-calendar-component") .await? .is_some() { xml.close().await?; Ok(Self::SupportedCalendarComponent) } else if xml.maybe_open(CAL_URN, "no-uid-conflict").await?.is_some() { let href = xml.find().await?; xml.close().await?; Ok(Self::NoUidConflict(href)) } else if xml .maybe_open(CAL_URN, "max-resource-size") .await? .is_some() { xml.close().await?; Ok(Self::MaxResourceSize) } else if xml.maybe_open(CAL_URN, "min-date-time").await?.is_some() { xml.close().await?; Ok(Self::MinDateTime) } else if xml.maybe_open(CAL_URN, "max-date-time").await?.is_some() { xml.close().await?; Ok(Self::MaxDateTime) } else if xml.maybe_open(CAL_URN, "max-instances").await?.is_some() { xml.close().await?; Ok(Self::MaxInstances) } else if xml .maybe_open(CAL_URN, "max-attendees-per-instance") .await? .is_some() { xml.close().await?; Ok(Self::MaxAttendeesPerInstance) } else if xml.maybe_open(CAL_URN, "valid-filter").await?.is_some() { xml.close().await?; Ok(Self::ValidFilter) } else if xml.maybe_open(CAL_URN, "supported-filter").await?.is_some() { let (mut comp, mut prop, mut param) = (Vec::new(), Vec::new(), Vec::new()); loop { let mut dirty = false; xml.maybe_push(&mut comp, &mut dirty).await?; xml.maybe_push(&mut prop, &mut dirty).await?; xml.maybe_push(&mut param, &mut dirty).await?; if !dirty { match xml.peek() { Event::End(_) => break, _ => xml.skip().await?, }; } } xml.close().await?; Ok(Self::SupportedFilter { comp, prop, param }) } else if xml .maybe_open(CAL_URN, "number-of-matches-within-limits") .await? .is_some() { xml.close().await?; Ok(Self::NumberOfMatchesWithinLimits) } else { Err(ParsingError::Recoverable) } } } impl QRead for Property { async fn qread(xml: &mut Reader) -> Result { if xml .maybe_open_start(CAL_URN, "calendar-home-set") .await? .is_some() { let href = xml.find().await?; xml.close().await?; return Ok(Property::CalendarHomeSet(href)); } if xml .maybe_open_start(CAL_URN, "calendar-description") .await? .is_some() { let lang = xml.prev_attr("xml:lang"); let text = xml.tag_string().await?; xml.close().await?; return Ok(Property::CalendarDescription { lang, text }); } if xml .maybe_open_start(CAL_URN, "calendar-timezone") .await? .is_some() { let tz = xml.tag_string().await?; xml.close().await?; return Ok(Property::CalendarTimezone(tz)); } if xml .maybe_open_start(CAL_URN, "supported-calendar-component-set") .await? .is_some() { let comp = xml.collect().await?; xml.close().await?; return Ok(Property::SupportedCalendarComponentSet(comp)); } if xml .maybe_open_start(CAL_URN, "supported-calendar-data") .await? .is_some() { let mime = xml.collect().await?; xml.close().await?; return Ok(Property::SupportedCalendarData(mime)); } if xml .maybe_open_start(CAL_URN, "max-resource-size") .await? .is_some() { let sz = xml.tag_string().await?.parse::()?; xml.close().await?; return Ok(Property::MaxResourceSize(sz)); } if xml .maybe_open_start(CAL_URN, "max-date-time") .await? .is_some() { let dtstr = xml.tag_string().await?; let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); xml.close().await?; return Ok(Property::MaxDateTime(dt)); } if xml .maybe_open_start(CAL_URN, "max-instances") .await? .is_some() { let sz = xml.tag_string().await?.parse::()?; xml.close().await?; return Ok(Property::MaxInstances(sz)); } if xml .maybe_open_start(CAL_URN, "max-attendees-per-instance") .await? .is_some() { let sz = xml.tag_string().await?.parse::()?; xml.close().await?; return Ok(Property::MaxAttendeesPerInstance(sz)); } if xml .maybe_open_start(CAL_URN, "supported-collation-set") .await? .is_some() { let cols = xml.collect().await?; xml.close().await?; return Ok(Property::SupportedCollationSet(cols)); } let mut dirty = false; let mut caldata: Option = None; xml.maybe_read(&mut caldata, &mut dirty).await?; if let Some(cal) = caldata { return Ok(Property::CalendarData(cal)); } Err(ParsingError::Recoverable) } } impl QRead for PropertyRequest { async fn qread(xml: &mut Reader) -> Result { if xml .maybe_open(CAL_URN, "calendar-home-set") .await? .is_some() { xml.close().await?; return Ok(Self::CalendarHomeSet); } if xml .maybe_open(CAL_URN, "calendar-description") .await? .is_some() { xml.close().await?; return Ok(Self::CalendarDescription); } if xml .maybe_open(CAL_URN, "calendar-timezone") .await? .is_some() { xml.close().await?; return Ok(Self::CalendarTimezone); } if xml .maybe_open(CAL_URN, "supported-calendar-component-set") .await? .is_some() { xml.close().await?; return Ok(Self::SupportedCalendarComponentSet); } if xml .maybe_open(CAL_URN, "supported-calendar-data") .await? .is_some() { xml.close().await?; return Ok(Self::SupportedCalendarData); } if xml .maybe_open(CAL_URN, "max-resource-size") .await? .is_some() { xml.close().await?; return Ok(Self::MaxResourceSize); } if xml.maybe_open(CAL_URN, "min-date-time").await?.is_some() { xml.close().await?; return Ok(Self::MinDateTime); } if xml.maybe_open(CAL_URN, "max-date-time").await?.is_some() { xml.close().await?; return Ok(Self::MaxDateTime); } if xml.maybe_open(CAL_URN, "max-instances").await?.is_some() { xml.close().await?; return Ok(Self::MaxInstances); } if xml .maybe_open(CAL_URN, "max-attendees-per-instance") .await? .is_some() { xml.close().await?; return Ok(Self::MaxAttendeesPerInstance); } if xml .maybe_open(CAL_URN, "supported-collation-set") .await? .is_some() { xml.close().await?; return Ok(Self::SupportedCollationSet); } let mut dirty = false; let mut m_cdr = None; xml.maybe_read(&mut m_cdr, &mut dirty).await?; m_cdr .ok_or(ParsingError::Recoverable) .map(Self::CalendarData) } } impl QRead for ResourceType { async fn qread(xml: &mut Reader) -> Result { if xml.maybe_open(CAL_URN, "calendar").await?.is_some() { xml.close().await?; return Ok(Self::Calendar); } Err(ParsingError::Recoverable) } } // ---- INNER XML ---- impl QRead for SupportedCollation { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "supported-collation").await?; let col = Collation::new(xml.tag_string().await?); xml.close().await?; Ok(SupportedCollation(col)) } } impl QRead for CalendarDataPayload { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "calendar-data").await?; let mime = CalendarDataSupport::qread(xml).await.ok(); let payload = xml.tag_string().await?; xml.close().await?; Ok(CalendarDataPayload { mime, payload }) } } impl QRead for CalendarDataSupport { async fn qread(xml: &mut Reader) -> Result { let ct = xml.prev_attr("content-type"); let vs = xml.prev_attr("version"); match (ct, vs) { (Some(content_type), Some(version)) => Ok(Self { content_type, version, }), _ => Err(ParsingError::Recoverable), } } } impl QRead for CalendarDataRequest { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "calendar-data").await?; let mime = CalendarDataSupport::qread(xml).await.ok(); let (mut comp, mut recurrence, mut limit_freebusy_set) = (None, None, None); if !xml.parent_has_child() { return Ok(Self { mime, comp, recurrence, limit_freebusy_set, }); } loop { let mut dirty = false; xml.maybe_read(&mut comp, &mut dirty).await?; xml.maybe_read(&mut recurrence, &mut dirty).await?; xml.maybe_read(&mut limit_freebusy_set, &mut dirty).await?; if !dirty { match xml.peek() { Event::End(_) => break, _ => xml.skip().await?, }; } } xml.close().await?; Ok(Self { mime, comp, recurrence, limit_freebusy_set, }) } } impl QRead for CalendarDataEmpty { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "calendar-data").await?; let mime = CalendarDataSupport::qread(xml).await.ok(); xml.close().await?; Ok(Self(mime)) } } impl QRead for Comp { async fn qread(xml: &mut Reader) -> Result { let (mut prop_kind, mut comp_kind) = (None, None); let bs = xml.open(CAL_URN, "comp").await?; let name = Component::new( xml.prev_attr("name") .ok_or(ParsingError::MissingAttribute)?, ); // Return early if it's an empty tag if matches!(bs, Event::Empty(_)) { xml.close().await?; return Ok(Self { name, prop_kind, comp_kind, }); } loop { let mut dirty = false; let (mut tmp_prop_kind, mut tmp_comp_kind): (Option, Option) = (None, None); xml.maybe_read(&mut tmp_prop_kind, &mut dirty).await?; Box::pin(xml.maybe_read(&mut tmp_comp_kind, &mut dirty)).await?; //@FIXME hack // Merge match (tmp_prop_kind, &mut prop_kind) { (Some(PropKind::Prop(mut a)), Some(PropKind::Prop(ref mut b))) => b.append(&mut a), (Some(PropKind::AllProp), v) => *v = Some(PropKind::AllProp), (Some(x), b) => *b = Some(x), (None, _) => (), }; match (tmp_comp_kind, &mut comp_kind) { (Some(CompKind::Comp(mut a)), Some(CompKind::Comp(ref mut b))) => b.append(&mut a), (Some(CompKind::AllComp), v) => *v = Some(CompKind::AllComp), (Some(a), b) => *b = Some(a), (None, _) => (), }; if !dirty { match xml.peek() { Event::End(_) => break, _ => xml.skip().await?, }; } } xml.close().await?; Ok(Self { name, prop_kind, comp_kind, }) } } impl QRead for CompSupport { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "comp").await?; let inner = Component::new( xml.prev_attr("name") .ok_or(ParsingError::MissingAttribute)?, ); xml.close().await?; Ok(Self(inner)) } } impl QRead for CompKind { async fn qread(xml: &mut Reader) -> Result { let mut comp = Vec::new(); loop { let mut dirty = false; if xml.maybe_open(CAL_URN, "allcomp").await?.is_some() { xml.close().await?; return Ok(CompKind::AllComp); } xml.maybe_push(&mut comp, &mut dirty).await?; if !dirty { break; } } match &comp[..] { [] => Err(ParsingError::Recoverable), _ => Ok(CompKind::Comp(comp)), } } } impl QRead for PropKind { async fn qread(xml: &mut Reader) -> Result { let mut prop = Vec::new(); loop { let mut dirty = false; if xml.maybe_open(CAL_URN, "allprop").await?.is_some() { xml.close().await?; return Ok(PropKind::AllProp); } xml.maybe_push(&mut prop, &mut dirty).await?; if !dirty { break; } } match &prop[..] { [] => Err(ParsingError::Recoverable), _ => Ok(PropKind::Prop(prop)), } } } impl QRead for RecurrenceModifier { async fn qread(xml: &mut Reader) -> Result { match Expand::qread(xml).await { Err(ParsingError::Recoverable) => (), otherwise => return otherwise.map(RecurrenceModifier::Expand), } LimitRecurrenceSet::qread(xml) .await .map(RecurrenceModifier::LimitRecurrenceSet) } } impl QRead for Expand { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "expand").await?; let (rstart, rend) = match (xml.prev_attr("start"), xml.prev_attr("end")) { (Some(start), Some(end)) => (start, end), _ => return Err(ParsingError::MissingAttribute), }; let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); if start > end { return Err(ParsingError::InvalidValue); } xml.close().await?; Ok(Expand(start, end)) } } impl QRead for LimitRecurrenceSet { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "limit-recurrence-set").await?; let (rstart, rend) = match (xml.prev_attr("start"), xml.prev_attr("end")) { (Some(start), Some(end)) => (start, end), _ => return Err(ParsingError::MissingAttribute), }; let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); if start > end { return Err(ParsingError::InvalidValue); } xml.close().await?; Ok(LimitRecurrenceSet(start, end)) } } impl QRead for LimitFreebusySet { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "limit-freebusy-set").await?; let (rstart, rend) = match (xml.prev_attr("start"), xml.prev_attr("end")) { (Some(start), Some(end)) => (start, end), _ => return Err(ParsingError::MissingAttribute), }; let start = NaiveDateTime::parse_from_str(rstart.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); let end = NaiveDateTime::parse_from_str(rend.as_str(), CALDAV_DATETIME_FMT)?.and_utc(); if start > end { return Err(ParsingError::InvalidValue); } xml.close().await?; Ok(LimitFreebusySet(start, end)) } } impl QRead> for CalendarSelector { async fn qread(xml: &mut Reader) -> Result { // allprop if let Some(_) = xml.maybe_open(DAV_URN, "allprop").await? { xml.close().await?; return Ok(Self::AllProp); } // propname if let Some(_) = xml.maybe_open(DAV_URN, "propname").await? { xml.close().await?; return Ok(Self::PropName); } // prop let (mut maybe_prop, mut dirty) = (None, false); xml.maybe_read::>(&mut maybe_prop, &mut dirty) .await?; if let Some(prop) = maybe_prop { return Ok(Self::Prop(prop)); } Err(ParsingError::Recoverable) } } impl QRead for CompFilter { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "comp-filter").await?; let name = Component::new( xml.prev_attr("name") .ok_or(ParsingError::MissingAttribute)?, ); let additional_rules = Box::pin(xml.maybe_find()).await?; xml.close().await?; Ok(Self { name, additional_rules, }) } } impl QRead for CompFilterRules { async fn qread(xml: &mut Reader) -> Result { let mut time_range = None; let mut prop_filter = Vec::new(); let mut comp_filter = Vec::new(); loop { let mut dirty = false; if xml.maybe_open(CAL_URN, "is-not-defined").await?.is_some() { xml.close().await?; return Ok(Self::IsNotDefined); } xml.maybe_read(&mut time_range, &mut dirty).await?; xml.maybe_push(&mut prop_filter, &mut dirty).await?; xml.maybe_push(&mut comp_filter, &mut dirty).await?; if !dirty { match xml.peek() { Event::End(_) => break, _ => xml.skip().await?, }; } } match (&time_range, &prop_filter[..], &comp_filter[..]) { (None, [], []) => Err(ParsingError::Recoverable), _ => Ok(Self::Matches(CompFilterMatch { time_range, prop_filter, comp_filter, })), } } } impl QRead for CompFilterMatch { async fn qread(_xml: &mut Reader) -> Result { unreachable!(); } } impl QRead for PropFilter { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "prop-filter").await?; let name = ComponentProperty( xml.prev_attr("name") .ok_or(ParsingError::MissingAttribute)?, ); let additional_rules = xml.maybe_find().await?; xml.close().await?; Ok(Self { name, additional_rules, }) } } impl QRead for PropFilterRules { async fn qread(xml: &mut Reader) -> Result { let mut time_or_text = None; let mut param_filter = Vec::new(); loop { let mut dirty = false; if xml.maybe_open(CAL_URN, "is-not-defined").await?.is_some() { xml.close().await?; return Ok(Self::IsNotDefined); } xml.maybe_read(&mut time_or_text, &mut dirty).await?; xml.maybe_push(&mut param_filter, &mut dirty).await?; if !dirty { match xml.peek() { Event::End(_) => break, _ => xml.skip().await?, }; } } match (&time_or_text, ¶m_filter[..]) { (None, []) => Err(ParsingError::Recoverable), _ => Ok(PropFilterRules::Match(PropFilterMatch { time_or_text, param_filter, })), } } } impl QRead for PropFilterMatch { async fn qread(_xml: &mut Reader) -> Result { unreachable!(); } } impl QRead for ParamFilter { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "param-filter").await?; let name = PropertyParameter( xml.prev_attr("name") .ok_or(ParsingError::MissingAttribute)?, ); let additional_rules = xml.maybe_find().await?; xml.close().await?; Ok(Self { name, additional_rules, }) } } impl QRead for TimeOrText { async fn qread(xml: &mut Reader) -> Result { match TimeRange::qread(xml).await { Err(ParsingError::Recoverable) => (), otherwise => return otherwise.map(Self::Time), } TextMatch::qread(xml).await.map(Self::Text) } } impl QRead for TextMatch { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "text-match").await?; let collation = xml.prev_attr("collation").map(Collation::new); let negate_condition = xml.prev_attr("negate-condition").map(|v| v == "yes"); let text = xml.tag_string().await?; xml.close().await?; Ok(Self { collation, negate_condition, text, }) } } impl QRead for ParamFilterMatch { async fn qread(xml: &mut Reader) -> Result { if xml.maybe_open(CAL_URN, "is-not-defined").await?.is_some() { xml.close().await?; return Ok(Self::IsNotDefined); } TextMatch::qread(xml).await.map(Self::Match) } } impl QRead for TimeZone { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "timezone").await?; let inner = xml.tag_string().await?; xml.close().await?; Ok(Self(inner)) } } impl QRead for Filter { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "filter").await?; let comp_filter = xml.find().await?; xml.close().await?; Ok(Self(comp_filter)) } } impl QRead for TimeRange { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "time-range").await?; let start = match xml.prev_attr("start") { Some(r) => { Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc()) } _ => None, }; let end = match xml.prev_attr("end") { Some(r) => { Some(NaiveDateTime::parse_from_str(r.as_str(), CALDAV_DATETIME_FMT)?.and_utc()) } _ => None, }; xml.close().await?; match (start, end) { (Some(start), Some(end)) => { if start > end { return Err(ParsingError::InvalidValue); } Ok(TimeRange::FullRange(start, end)) } (Some(start), None) => Ok(TimeRange::OnlyStart(start)), (None, Some(end)) => Ok(TimeRange::OnlyEnd(end)), (None, None) => Err(ParsingError::MissingAttribute), } } } impl QRead for CalProp { async fn qread(xml: &mut Reader) -> Result { xml.open(CAL_URN, "prop").await?; let name = ComponentProperty( xml.prev_attr("name") .ok_or(ParsingError::MissingAttribute)?, ); let novalue = xml.prev_attr("novalue").map(|v| v == "yes"); xml.close().await?; Ok(Self { name, novalue }) } } #[cfg(test)] mod tests { use super::*; use crate::realization::Calendar; use crate::xml::Node; use chrono::{TimeZone, Utc}; //use quick_reader::NsReader; async fn deserialize>(src: &str) -> T { let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes())) .await .unwrap(); rdr.find().await.unwrap() } #[tokio::test] async fn basic_mkcalendar() { let expected = MkCalendar(dav::Set(dav::PropValue(vec![dav::Property::DisplayName( "Lisa's Events".into(), )]))); let src = r#" Lisa's Events "#; let got = deserialize::>(src).await; assert_eq!(got, expected) } #[tokio::test] async fn rfc_mkcalendar() { let expected = MkCalendar(dav::Set(dav::PropValue(vec![ dav::Property::DisplayName("Lisa's Events".into()), dav::Property::Extension(Property::CalendarDescription { lang: Some("en".into()), text: "Calendar restricted to events.".into(), }), dav::Property::Extension(Property::SupportedCalendarComponentSet(vec![ CompSupport(Component::VEvent) ])), dav::Property::Extension(Property::CalendarTimezone("BEGIN:VCALENDAR\nPRODID:-//Example Corp.//CalDAV Client//EN\nVERSION:2.0\nEND:VCALENDAR".into())), ]))); let src = r#" Lisa's Events Calendar restricted to events. "#; let got = deserialize::>(src).await; assert_eq!(got, expected) } #[tokio::test] async fn rfc_calendar_query() { let expected = CalendarQuery { selector: Some(CalendarSelector::Prop(dav::PropName(vec![ dav::PropertyRequest::GetEtag, dav::PropertyRequest::Extension(PropertyRequest::CalendarData( CalendarDataRequest { mime: None, comp: Some(Comp { name: Component::VCalendar, prop_kind: Some(PropKind::Prop(vec![CalProp { name: ComponentProperty("VERSION".into()), novalue: None, }])), comp_kind: Some(CompKind::Comp(vec![ Comp { name: Component::VEvent, prop_kind: Some(PropKind::Prop(vec![ CalProp { name: ComponentProperty("SUMMARY".into()), novalue: None, }, CalProp { name: ComponentProperty("UID".into()), novalue: None, }, CalProp { name: ComponentProperty("DTSTART".into()), novalue: None, }, CalProp { name: ComponentProperty("DTEND".into()), novalue: None, }, CalProp { name: ComponentProperty("DURATION".into()), novalue: None, }, CalProp { name: ComponentProperty("RRULE".into()), novalue: None, }, CalProp { name: ComponentProperty("RDATE".into()), novalue: None, }, CalProp { name: ComponentProperty("EXRULE".into()), novalue: None, }, CalProp { name: ComponentProperty("EXDATE".into()), novalue: None, }, CalProp { name: ComponentProperty("RECURRENCE-ID".into()), novalue: None, }, ])), comp_kind: None, }, Comp { name: Component::VTimeZone, prop_kind: None, comp_kind: None, }, ])), }), recurrence: None, limit_freebusy_set: None, }, )), ]))), filter: Filter(CompFilter { name: Component::VCalendar, additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { prop_filter: vec![], comp_filter: vec![CompFilter { name: Component::VEvent, additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { prop_filter: vec![], comp_filter: vec![], time_range: Some(TimeRange::FullRange( Utc.with_ymd_and_hms(2006, 1, 4, 0, 0, 0).unwrap(), Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(), )), })), }], time_range: None, })), }), timezone: None, }; let src = r#" "#; let got = deserialize::>(src).await; assert_eq!(got, expected) } #[tokio::test] async fn rfc_calendar_query_res() { let expected = dav::Multistatus:: { responses: vec![ dav::Response { status_or_propstat: dav::StatusOrPropstat::PropStat( dav::Href("http://cal.example.com/bernard/work/abcd2.ics".into()), vec![dav::PropStat { prop: dav::AnyProp(vec![ dav::AnyProperty::Value(dav::Property::GetEtag( "\"fffff-abcd2\"".into(), )), dav::AnyProperty::Value(dav::Property::Extension( Property::CalendarData(CalendarDataPayload { mime: None, payload: "BEGIN:VCALENDAR".into(), }), )), ]), status: dav::Status(http::status::StatusCode::OK), error: None, responsedescription: None, }], ), error: None, location: None, responsedescription: None, }, dav::Response { status_or_propstat: dav::StatusOrPropstat::PropStat( dav::Href("http://cal.example.com/bernard/work/abcd3.ics".into()), vec![dav::PropStat { prop: dav::AnyProp(vec![ dav::AnyProperty::Value(dav::Property::GetEtag( "\"fffff-abcd3\"".into(), )), dav::AnyProperty::Value(dav::Property::Extension( Property::CalendarData(CalendarDataPayload { mime: None, payload: "BEGIN:VCALENDAR".into(), }), )), ]), status: dav::Status(http::status::StatusCode::OK), error: None, responsedescription: None, }], ), error: None, location: None, responsedescription: None, }, ], responsedescription: None, }; let src = r#" http://cal.example.com/bernard/work/abcd2.ics "fffff-abcd2" BEGIN:VCALENDAR HTTP/1.1 200 OK http://cal.example.com/bernard/work/abcd3.ics "fffff-abcd3" BEGIN:VCALENDAR HTTP/1.1 200 OK "#; let got = deserialize::>(src).await; assert_eq!(got, expected) } #[tokio::test] async fn rfc_recurring_evt() { let expected = CalendarQuery:: { selector: Some(CalendarSelector::Prop(dav::PropName(vec![ dav::PropertyRequest::Extension(PropertyRequest::CalendarData( CalendarDataRequest { mime: None, comp: None, recurrence: Some(RecurrenceModifier::LimitRecurrenceSet( LimitRecurrenceSet( Utc.with_ymd_and_hms(2006, 1, 3, 0, 0, 0).unwrap(), Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(), ), )), limit_freebusy_set: None, }, )), ]))), filter: Filter(CompFilter { name: Component::VCalendar, additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { prop_filter: vec![], comp_filter: vec![CompFilter { name: Component::VEvent, additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { prop_filter: vec![], comp_filter: vec![], time_range: Some(TimeRange::FullRange( Utc.with_ymd_and_hms(2006, 1, 3, 0, 0, 0).unwrap(), Utc.with_ymd_and_hms(2006, 1, 5, 0, 0, 0).unwrap(), )), })), }], time_range: None, })), }), timezone: None, }; let src = r#" "#; let got = deserialize::>(src).await; assert_eq!(got, expected) } #[tokio::test] async fn rfc_pending_todos() { let expected = CalendarQuery:: { selector: Some(CalendarSelector::Prop(dav::PropName(vec![ dav::PropertyRequest::GetEtag, dav::PropertyRequest::Extension(PropertyRequest::CalendarData( CalendarDataRequest { mime: None, comp: None, recurrence: None, limit_freebusy_set: None, }, )), ]))), filter: Filter(CompFilter { name: Component::VCalendar, additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { time_range: None, prop_filter: vec![], comp_filter: vec![CompFilter { name: Component::VTodo, additional_rules: Some(CompFilterRules::Matches(CompFilterMatch { time_range: None, comp_filter: vec![], prop_filter: vec![ PropFilter { name: ComponentProperty("COMPLETED".into()), additional_rules: Some(PropFilterRules::IsNotDefined), }, PropFilter { name: ComponentProperty("STATUS".into()), additional_rules: Some(PropFilterRules::Match( PropFilterMatch { param_filter: vec![], time_or_text: Some(TimeOrText::Text(TextMatch { collation: None, negate_condition: Some(true), text: "CANCELLED".into(), })), }, )), }, ], })), }], })), }), timezone: None, }; let src = r#" CANCELLED "#; let got = deserialize::>(src).await; assert_eq!(got, expected) } }