mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 07:02:24 +00:00
Migrate propfind and report to rustical_xml
This commit is contained in:
@@ -13,16 +13,16 @@ use rustical_dav::{
|
||||
xml::{multistatus::ResponseElement, MultistatusElement, PropElement, PropfindType},
|
||||
};
|
||||
use rustical_store::{auth::User, CalendarObject, CalendarStore};
|
||||
use serde::Deserialize;
|
||||
use rustical_xml::XmlDeserialize;
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
// <!ELEMENT calendar-query ((DAV:allprop | DAV:propname | DAV:prop)?, href+)>
|
||||
pub struct CalendarMultigetRequest {
|
||||
#[serde(flatten)]
|
||||
prop: PropfindType,
|
||||
href: Vec<String>,
|
||||
pub(crate) struct CalendarMultigetRequest {
|
||||
#[xml(ty = "untagged")]
|
||||
pub(crate) prop: PropfindType,
|
||||
#[xml(flatten)]
|
||||
pub(crate) href: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn get_objects_calendar_multiget<C: CalendarStore + ?Sized>(
|
||||
@@ -78,7 +78,10 @@ pub async fn handle_calendar_multiget<C: CalendarStore + ?Sized>(
|
||||
PropfindType::Propname => {
|
||||
vec!["propname".to_owned()]
|
||||
}
|
||||
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags.into_inner(),
|
||||
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags
|
||||
.into_iter()
|
||||
.map(|propname| propname.name)
|
||||
.collect(),
|
||||
};
|
||||
let props: Vec<&str> = props.iter().map(String::as_str).collect();
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use actix_web::HttpRequest;
|
||||
use chrono::{DateTime, Utc};
|
||||
use rustical_dav::{
|
||||
resource::{CommonPropertiesProp, EitherProp, Resource},
|
||||
xml::{MultistatusElement, PropElement, PropfindType},
|
||||
};
|
||||
use rustical_store::{auth::User, CalendarObject, CalendarStore};
|
||||
use serde::Deserialize;
|
||||
use rustical_store::{auth::User, calendar::UtcDateTime, CalendarObject, CalendarStore};
|
||||
use rustical_xml::{Unit, XmlDeserialize};
|
||||
|
||||
use crate::{
|
||||
calendar_object::resource::{CalendarObjectProp, CalendarObjectResource},
|
||||
@@ -14,70 +15,57 @@ use crate::{
|
||||
|
||||
// TODO: Implement all the other filters
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
struct TimeRangeElement {
|
||||
#[serde(
|
||||
rename = "@start",
|
||||
deserialize_with = "rustical_store::calendar::deserialize_utc_datetime",
|
||||
default
|
||||
)]
|
||||
start: Option<DateTime<Utc>>,
|
||||
#[serde(
|
||||
rename = "@end",
|
||||
deserialize_with = "rustical_store::calendar::deserialize_utc_datetime",
|
||||
default
|
||||
)]
|
||||
end: Option<DateTime<Utc>>,
|
||||
pub(crate) struct TimeRangeElement {
|
||||
#[xml(ty = "attr")]
|
||||
pub(crate) start: Option<UtcDateTime>,
|
||||
#[xml(ty = "attr")]
|
||||
pub(crate) end: Option<UtcDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
struct ParamFilterElement {
|
||||
is_not_defined: Option<()>,
|
||||
is_not_defined: Option<Unit>,
|
||||
text_match: Option<TextMatchElement>,
|
||||
|
||||
#[serde(rename = "@name")]
|
||||
#[xml(ty = "attr")]
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
struct TextMatchElement {
|
||||
#[serde(rename = "@collation")]
|
||||
#[xml(ty = "attr")]
|
||||
collation: String,
|
||||
#[serde(rename = "@negate-collation")]
|
||||
#[xml(ty = "attr")]
|
||||
negate_collation: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
struct PropFilterElement {
|
||||
is_not_defined: Option<()>,
|
||||
pub(crate) struct PropFilterElement {
|
||||
is_not_defined: Option<Unit>,
|
||||
time_range: Option<TimeRangeElement>,
|
||||
text_match: Option<TextMatchElement>,
|
||||
#[serde(default)]
|
||||
#[xml(flatten)]
|
||||
param_filter: Vec<ParamFilterElement>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
// https://datatracker.ietf.org/doc/html/rfc4791#section-9.7.1
|
||||
struct CompFilterElement {
|
||||
is_not_defined: Option<()>,
|
||||
time_range: Option<TimeRangeElement>,
|
||||
#[serde(default)]
|
||||
prop_filter: Vec<PropFilterElement>,
|
||||
#[serde(default)]
|
||||
comp_filter: Vec<CompFilterElement>,
|
||||
pub(crate) struct CompFilterElement {
|
||||
pub(crate) is_not_defined: Option<Unit>,
|
||||
pub(crate) time_range: Option<TimeRangeElement>,
|
||||
#[xml(flatten)]
|
||||
pub(crate) prop_filter: Vec<PropFilterElement>,
|
||||
#[xml(flatten)]
|
||||
pub(crate) comp_filter: Vec<CompFilterElement>,
|
||||
|
||||
#[serde(rename = "@name")]
|
||||
name: String,
|
||||
#[xml(ty = "attr")]
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
impl CompFilterElement {
|
||||
@@ -146,14 +134,14 @@ impl CompFilterElement {
|
||||
if let Some(time_range) = &self.time_range {
|
||||
if let Some(start) = &time_range.start {
|
||||
if let Some(first_occurence) = cal_object.get_first_occurence().unwrap_or(None) {
|
||||
if start > &first_occurence.utc() {
|
||||
if start.deref() > &first_occurence.utc() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Some(end) = &time_range.end {
|
||||
if let Some(last_occurence) = cal_object.get_last_occurence().unwrap_or(None) {
|
||||
if end < &last_occurence.utc() {
|
||||
if end.deref() < &last_occurence.utc() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -164,12 +152,11 @@ impl CompFilterElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
// https://datatracker.ietf.org/doc/html/rfc4791#section-9.7
|
||||
struct FilterElement {
|
||||
comp_filter: CompFilterElement,
|
||||
pub(crate) struct FilterElement {
|
||||
pub(crate) comp_filter: CompFilterElement,
|
||||
}
|
||||
|
||||
impl FilterElement {
|
||||
@@ -178,15 +165,14 @@ impl FilterElement {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
// <!ELEMENT calendar-query ((DAV:allprop | DAV:propname | DAV:prop)?, filter, timezone?)>
|
||||
pub struct CalendarQueryRequest {
|
||||
#[serde(flatten)]
|
||||
#[xml(ty = "untagged")]
|
||||
pub prop: PropfindType,
|
||||
filter: Option<FilterElement>,
|
||||
timezone: Option<String>,
|
||||
pub(crate) filter: Option<FilterElement>,
|
||||
pub(crate) timezone: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_objects_calendar_query<C: CalendarStore + ?Sized>(
|
||||
@@ -220,7 +206,10 @@ pub async fn handle_calendar_query<C: CalendarStore + ?Sized>(
|
||||
PropfindType::Propname => {
|
||||
vec!["propname".to_owned()]
|
||||
}
|
||||
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags.into_inner(),
|
||||
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags
|
||||
.into_iter()
|
||||
.map(|propname| propname.name)
|
||||
.collect(),
|
||||
};
|
||||
let props: Vec<&str> = props.iter().map(String::as_str).collect();
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use actix_web::{
|
||||
use calendar_multiget::{handle_calendar_multiget, CalendarMultigetRequest};
|
||||
use calendar_query::{handle_calendar_query, CalendarQueryRequest};
|
||||
use rustical_store::{auth::User, CalendarStore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use rustical_xml::{XmlDeserialize, XmlDocument};
|
||||
use sync_collection::{handle_sync_collection, SyncCollectionRequest};
|
||||
use tracing::instrument;
|
||||
|
||||
@@ -14,17 +14,8 @@ mod calendar_multiget;
|
||||
mod calendar_query;
|
||||
mod sync_collection;
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PropQuery {
|
||||
Allprop,
|
||||
Prop,
|
||||
Propname,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ReportRequest {
|
||||
#[derive(XmlDeserialize, XmlDocument, Clone, Debug, PartialEq)]
|
||||
pub(crate) enum ReportRequest {
|
||||
CalendarMultiget(CalendarMultigetRequest),
|
||||
CalendarQuery(CalendarQueryRequest),
|
||||
SyncCollection(SyncCollectionRequest),
|
||||
@@ -43,7 +34,7 @@ pub async fn route_report_calendar<C: CalendarStore + ?Sized>(
|
||||
return Err(Error::Unauthorized);
|
||||
}
|
||||
|
||||
let request: ReportRequest = quick_xml::de::from_str(&body)?;
|
||||
let request = ReportRequest::parse_str(&body)?;
|
||||
|
||||
Ok(match request.clone() {
|
||||
ReportRequest::CalendarQuery(cal_query) => {
|
||||
@@ -81,3 +72,66 @@ pub async fn route_report_calendar<C: CalendarStore + ?Sized>(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use calendar_query::{CompFilterElement, FilterElement, TimeRangeElement};
|
||||
use rustical_dav::xml::{PropElement, PropfindType, Propname};
|
||||
use rustical_store::calendar::UtcDateTime;
|
||||
use rustical_xml::Value;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_xml_sync_collection() {
|
||||
let report_request = ReportRequest::parse_str(
|
||||
r#"
|
||||
<?xml version='1.0' encoding='UTF-8' ?>
|
||||
<CAL:calendar-query xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav">
|
||||
<prop>
|
||||
<getetag />
|
||||
</prop>
|
||||
<CAL:filter>
|
||||
<CAL:comp-filter name="VCALENDAR">
|
||||
<CAL:comp-filter name="VEVENT">
|
||||
<CAL:time-range start="20240924T143437Z" />
|
||||
</CAL:comp-filter>
|
||||
</CAL:comp-filter>
|
||||
</CAL:filter>
|
||||
</CAL:calendar-query>"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
report_request,
|
||||
ReportRequest::CalendarQuery(CalendarQueryRequest {
|
||||
prop: PropfindType::Prop(PropElement {
|
||||
prop: vec![Propname {
|
||||
name: "getetag".to_owned()
|
||||
}]
|
||||
}),
|
||||
filter: Some(FilterElement {
|
||||
comp_filter: CompFilterElement {
|
||||
is_not_defined: None,
|
||||
time_range: None,
|
||||
prop_filter: vec![],
|
||||
comp_filter: vec![CompFilterElement {
|
||||
is_not_defined: None,
|
||||
time_range: Some(TimeRangeElement {
|
||||
start: Some(
|
||||
<UtcDateTime as Value>::deserialize("20240924T143437Z")
|
||||
.unwrap()
|
||||
),
|
||||
end: None
|
||||
}),
|
||||
prop_filter: vec![],
|
||||
comp_filter: vec![],
|
||||
name: "VEVENT".to_owned()
|
||||
}],
|
||||
name: "VCALENDAR".to_owned()
|
||||
}
|
||||
}),
|
||||
timezone: None,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,34 +9,49 @@ use rustical_store::{
|
||||
synctoken::{format_synctoken, parse_synctoken},
|
||||
CalendarStore,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use rustical_xml::{Value, XmlDeserialize};
|
||||
|
||||
use crate::{
|
||||
calendar_object::resource::{CalendarObjectProp, CalendarObjectResource},
|
||||
Error,
|
||||
};
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
enum SyncLevel {
|
||||
#[serde(rename = "1")]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) enum SyncLevel {
|
||||
One,
|
||||
Infinity,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
impl Value for SyncLevel {
|
||||
fn deserialize(val: &str) -> Result<Self, rustical_xml::XmlDeError> {
|
||||
Ok(match val {
|
||||
"1" => Self::One,
|
||||
"Infinity" => Self::Infinity,
|
||||
// TODO: proper error
|
||||
_ => return Err(rustical_xml::XmlDeError::UnknownError),
|
||||
})
|
||||
}
|
||||
fn serialize(&self) -> String {
|
||||
match self {
|
||||
SyncLevel::One => "1",
|
||||
SyncLevel::Infinity => "Infinity",
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(XmlDeserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
// <!ELEMENT sync-collection (sync-token, sync-level, limit?, prop)>
|
||||
// <!-- DAV:limit defined in RFC 5323, Section 5.17 -->
|
||||
// <!-- DAV:prop defined in RFC 4918, Section 14.18 -->
|
||||
pub struct SyncCollectionRequest {
|
||||
sync_token: String,
|
||||
sync_level: SyncLevel,
|
||||
timezone: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub(crate) struct SyncCollectionRequest {
|
||||
pub(crate) sync_token: String,
|
||||
pub(crate) sync_level: SyncLevel,
|
||||
pub(crate) timezone: Option<String>,
|
||||
#[xml(ty = "untagged")]
|
||||
pub prop: PropfindType,
|
||||
limit: Option<u64>,
|
||||
pub(crate) limit: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn handle_sync_collection<C: CalendarStore + ?Sized>(
|
||||
@@ -55,7 +70,10 @@ pub async fn handle_sync_collection<C: CalendarStore + ?Sized>(
|
||||
PropfindType::Propname => {
|
||||
vec!["propname".to_owned()]
|
||||
}
|
||||
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags.into_inner(),
|
||||
PropfindType::Prop(PropElement { prop: prop_tags }) => prop_tags
|
||||
.into_iter()
|
||||
.map(|propname| propname.name)
|
||||
.collect(),
|
||||
};
|
||||
let props: Vec<&str> = props.iter().map(String::as_str).collect();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user