From 92fd28cdbb02e50d85d756f7da99b7d4bbc00da5 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Fri, 18 Jul 2025 17:39:57 +0200 Subject: [PATCH] caldav: calendar-query fix xml --- Cargo.lock | 22 ++-- .../calendar/methods/report/calendar_query.rs | 109 +++++++++++++++++- 2 files changed, 118 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f71884..861713f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2999,7 +2999,7 @@ dependencies = [ [[package]] name = "rustical" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "argon2", @@ -3042,7 +3042,7 @@ dependencies = [ [[package]] name = "rustical_caldav" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-std", "async-trait", @@ -3080,7 +3080,7 @@ dependencies = [ [[package]] name = "rustical_carddav" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "axum", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "rustical_dav" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "axum", @@ -3137,7 +3137,7 @@ dependencies = [ [[package]] name = "rustical_dav_push" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "axum", @@ -3163,7 +3163,7 @@ dependencies = [ [[package]] name = "rustical_frontend" -version = "0.5.0" +version = "0.5.1" dependencies = [ "askama", "askama_web", @@ -3196,7 +3196,7 @@ dependencies = [ [[package]] name = "rustical_ical" -version = "0.5.0" +version = "0.5.1" dependencies = [ "axum", "chrono", @@ -3214,7 +3214,7 @@ dependencies = [ [[package]] name = "rustical_oidc" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "axum", @@ -3229,7 +3229,7 @@ dependencies = [ [[package]] name = "rustical_store" -version = "0.5.0" +version = "0.5.1" dependencies = [ "anyhow", "async-trait", @@ -3263,7 +3263,7 @@ dependencies = [ [[package]] name = "rustical_store_sqlite" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", "chrono", @@ -3284,7 +3284,7 @@ dependencies = [ [[package]] name = "rustical_xml" -version = "0.5.0" +version = "0.5.1" dependencies = [ "quick-xml", "thiserror 2.0.12", diff --git a/crates/caldav/src/calendar/methods/report/calendar_query.rs b/crates/caldav/src/calendar/methods/report/calendar_query.rs index 1d3a945..fc26f5e 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query.rs @@ -16,6 +16,7 @@ pub(crate) struct TimeRangeElement { #[derive(XmlDeserialize, Clone, Debug, PartialEq)] #[allow(dead_code)] +// https://www.rfc-editor.org/rfc/rfc4791#section-9.7.3 struct ParamFilterElement { #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] is_not_defined: Option<()>, @@ -32,11 +33,13 @@ struct TextMatchElement { #[xml(ty = "attr")] collation: String, #[xml(ty = "attr")] - negate_collation: String, + // "yes" or "no", default: "no" + negate_condition: Option, } #[derive(XmlDeserialize, Clone, Debug, PartialEq)] #[allow(dead_code)] +// https://www.rfc-editor.org/rfc/rfc4791#section-9.7.2 pub(crate) struct PropFilterElement { #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] is_not_defined: Option<()>, @@ -46,6 +49,9 @@ pub(crate) struct PropFilterElement { text_match: Option, #[xml(ns = "rustical_dav::namespace::NS_CALDAV", flatten)] param_filter: Vec, + + #[xml(ty = "attr")] + name: String, } #[derive(XmlDeserialize, Clone, Debug, PartialEq)] @@ -61,7 +67,7 @@ pub(crate) struct CompFilterElement { #[xml(ns = "rustical_dav::namespace::NS_CALDAV", flatten)] pub(crate) comp_filter: Vec, - #[xml(ns = "rustical_dav::namespace::NS_CALDAV", ty = "attr")] + #[xml(ty = "attr")] pub(crate) name: String, } @@ -203,3 +209,102 @@ pub async fn get_objects_calendar_query( } Ok(objects) } + +#[cfg(test)] +mod tests { + use rustical_dav::xml::PropElement; + use rustical_xml::XmlDocument; + + use crate::{ + calendar::methods::report::{ + ReportRequest, + calendar_query::{ + CalendarQueryRequest, CompFilterElement, FilterElement, ParamFilterElement, + PropFilterElement, TextMatchElement, + }, + }, + calendar_object::{CalendarObjectPropName, CalendarObjectPropWrapperName}, + }; + + #[test] + fn calendar_query_7_8_7() { + const INPUT: &str = r#" + + + + + + + + + + + mailto:lisa@example.com + + NEEDS-ACTION + + + + + + + "#; + + let report = ReportRequest::parse_str(INPUT).unwrap(); + let calendar_query: CalendarQueryRequest = + if let ReportRequest::CalendarQuery(query) = report { + query + } else { + panic!() + }; + assert_eq!( + calendar_query, + CalendarQueryRequest { + prop: rustical_dav::xml::PropfindType::Prop(PropElement( + vec![ + CalendarObjectPropWrapperName::CalendarObject( + CalendarObjectPropName::Getetag, + ), + CalendarObjectPropWrapperName::CalendarObject( + CalendarObjectPropName::CalendarData(Default::default()) + ), + ], + vec![] + )), + filter: Some(FilterElement { + comp_filter: CompFilterElement { + is_not_defined: None, + time_range: None, + prop_filter: vec![], + comp_filter: vec![CompFilterElement { + prop_filter: vec![PropFilterElement { + name: "ATTENDEE".to_owned(), + text_match: Some(TextMatchElement { + collation: "i;ascii-casemap".to_owned(), + negate_condition: None + }), + is_not_defined: None, + param_filter: vec![ParamFilterElement { + is_not_defined: None, + name: "PARTSTAT".to_owned(), + text_match: Some(TextMatchElement { + collation: "i;ascii-casemap".to_owned(), + negate_condition: None + }), + }], + time_range: None + }], + comp_filter: vec![], + is_not_defined: None, + name: "VEVENT".to_owned(), + time_range: None + }], + name: "VCALENDAR".to_owned() + } + }), + timezone: None, + timezone_id: None + } + ) + } +}