diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs index 2e833a1..d3c68f6 100644 --- a/aero-dav/src/caldecoder.rs +++ b/aero-dav/src/caldecoder.rs @@ -323,9 +323,13 @@ 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?; @@ -565,16 +569,6 @@ impl QRead for CompFilter { } impl QRead for CompFilterRules { - 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) - } - CompFilterMatch::qread(xml).await.map(CompFilterRules::Matches) - } -} - -impl QRead for CompFilterMatch { async fn qread(xml: &mut Reader) -> Result { let mut time_range = None; let mut prop_filter = Vec::new(); @@ -582,6 +576,12 @@ impl QRead for CompFilterMatch { 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?; @@ -596,11 +596,17 @@ impl QRead for CompFilterMatch { match (&time_range, &prop_filter[..], &comp_filter[..]) { (None, [], []) => Err(ParsingError::Recoverable), - _ => Ok(CompFilterMatch { time_range, prop_filter, comp_filter }), + _ => 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?; @@ -612,16 +618,6 @@ impl QRead for PropFilter { } impl QRead for PropFilterRules { - 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) - } - PropFilterMatch::qread(xml).await.map(PropFilterRules::Match) - } -} - -impl QRead for PropFilterMatch { async fn qread(xml: &mut Reader) -> Result { let mut time_range = None; let mut time_or_text = None; @@ -629,6 +625,12 @@ impl QRead for PropFilterMatch { 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_read(&mut time_or_text, &mut dirty).await?; xml.maybe_push(&mut param_filter, &mut dirty).await?; @@ -643,11 +645,17 @@ impl QRead for PropFilterMatch { match (&time_range, &time_or_text, ¶m_filter[..]) { (None, None, []) => Err(ParsingError::Recoverable), - _ => Ok(PropFilterMatch { time_range, time_or_text, param_filter }), + _ => Ok(PropFilterRules::Match(PropFilterMatch { time_range, 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?; @@ -922,4 +930,222 @@ END:VCALENDAR]]> 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::PropValue(vec![ + dav::Property::GetEtag("\"fffff-abcd2\"".into()), + 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::PropValue(vec![ + dav::Property::GetEtag("\"fffff-abcd3\"".into()), + 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 { + time_range: None, + 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) + + } } diff --git a/aero-dav/src/xml.rs b/aero-dav/src/xml.rs index 2f3b7a6..1f8a6b1 100644 --- a/aero-dav/src/xml.rs +++ b/aero-dav/src/xml.rs @@ -113,7 +113,7 @@ impl Reader { } } - fn parent_has_child(&self) -> bool { + pub fn parent_has_child(&self) -> bool { matches!(self.parents.last(), Some(Event::Start(_)) | None) } @@ -238,7 +238,7 @@ impl Reader { } pub async fn open(&mut self, ns: &[u8], key: &str) -> Result, ParsingError> { - //println!("try open tag {:?}", key); + //println!("try open tag {:?}, on {:?}", key, self.peek()); let evt = match self.peek() { Event::Empty(_) if self.is_tag(ns, key) => { // hack to make `prev_attr` works @@ -253,7 +253,7 @@ impl Reader { _ => return Err(ParsingError::Recoverable), }; - println!("open tag {:?}", evt); + //println!("open tag {:?}", evt); self.parents.push(evt.clone()); Ok(evt) } @@ -278,7 +278,7 @@ impl Reader { // find stop tag pub async fn close(&mut self) -> Result, ParsingError> { - println!("close tag {:?}", self.parents.last()); + //println!("close tag {:?}", self.parents.last()); // Handle the empty case if !self.parent_has_child() {