From 1dda9dea8dbc66217eb46171e9103976d5abcd60 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 18 Jan 2025 22:23:56 +0100 Subject: [PATCH] Group some DAV properties in extensions --- crates/caldav/src/calendar/resource.rs | 292 +++++++++++---------- crates/carddav/src/addressbook/resource.rs | 150 ++++++----- crates/dav/src/extensions/davpush.rs | 37 +++ crates/dav/src/extensions/mod.rs | 5 + crates/dav/src/extensions/synctoken.rs | 39 +++ crates/dav/src/lib.rs | 1 + 6 files changed, 326 insertions(+), 198 deletions(-) create mode 100644 crates/dav/src/extensions/davpush.rs create mode 100644 crates/dav/src/extensions/mod.rs create mode 100644 crates/dav/src/extensions/synctoken.rs diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 70e34a9..5f277aa 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -10,8 +10,10 @@ use actix_web::http::Method; use actix_web::web; use async_trait::async_trait; use derive_more::derive::{From, Into}; +use rustical_dav::extensions::{ + DavPushExtension, DavPushExtensionProp, SyncTokenExtension, SyncTokenExtensionProp, +}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::push::Transports; use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; @@ -31,13 +33,6 @@ pub enum CalendarProp { #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] Getcontenttype(&'static str), - // WebDav Push - #[xml(skip_deserializing)] - #[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] - Transports(Transports), - #[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] - Topic(String), - // CalDAV (RFC 4791) #[xml(ns = "rustical_dav::namespace::NS_ICAL")] CalendarColor(Option), @@ -61,18 +56,18 @@ pub enum CalendarProp { #[xml(skip_deserializing)] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] SupportedReportSet(SupportedReportSet), - - // Collection Synchronization (RFC 6578) - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - SyncToken(String), - - // CalendarServer - #[xml(ns = "rustical_dav::namespace::NS_CALENDARSERVER")] - Getctag(String), #[xml(ns = "rustical_dav::namespace::NS_CALENDARSERVER")] Source(Option), } +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[xml(unit_variants_ident = "CalendarPropWrapperName", untagged)] +pub enum CalendarPropWrapper { + Calendar(CalendarProp), + SyncToken(SyncTokenExtensionProp), + DavPush(DavPushExtensionProp), +} + #[derive(Clone, Debug, From, Into)] pub struct CalendarResource { pub cal: Calendar, @@ -85,15 +80,21 @@ impl From for Calendar { } } -impl CalendarResource { +impl SyncTokenExtension for CalendarResource { fn get_synctoken(&self) -> String { self.cal.format_synctoken() } } +impl DavPushExtension for CalendarResource { + fn get_topic(&self) -> String { + self.cal.push_topic.to_owned() + } +} + impl Resource for CalendarResource { - type PropName = CalendarPropName; - type Prop = CalendarProp; + type PropName = CalendarPropWrapperName; + type Prop = CalendarPropWrapper; type Error = Error; type PrincipalResource = PrincipalResource; @@ -121,43 +122,55 @@ impl Resource for CalendarResource { prop: &Self::PropName, ) -> Result { Ok(match prop { - CalendarPropName::Displayname => { - CalendarProp::Displayname(self.cal.displayname.clone()) + CalendarPropWrapperName::Calendar(prop) => CalendarPropWrapper::Calendar(match prop { + CalendarPropName::Displayname => { + CalendarProp::Displayname(self.cal.displayname.clone()) + } + CalendarPropName::CalendarColor => { + CalendarProp::CalendarColor(self.cal.color.clone()) + } + CalendarPropName::CalendarDescription => { + CalendarProp::CalendarDescription(self.cal.description.clone()) + } + CalendarPropName::CalendarTimezone => { + CalendarProp::CalendarTimezone(self.cal.timezone.clone()) + } + // chrono_tz uses the IANA database + CalendarPropName::TimezoneServiceSet => CalendarProp::TimezoneServiceSet( + "https://www.iana.org/time-zones".to_owned().into(), + ), + CalendarPropName::CalendarTimezoneId => { + CalendarProp::CalendarTimezoneId(self.cal.timezone_id.clone()) + } + CalendarPropName::CalendarOrder => { + CalendarProp::CalendarOrder(Some(self.cal.order)) + } + CalendarPropName::SupportedCalendarComponentSet => { + CalendarProp::SupportedCalendarComponentSet( + SupportedCalendarComponentSet::default(), + ) + } + CalendarPropName::SupportedCalendarData => { + CalendarProp::SupportedCalendarData(SupportedCalendarData::default()) + } + CalendarPropName::Getcontenttype => { + CalendarProp::Getcontenttype("text/calendar;charset=utf-8") + } + CalendarPropName::MaxResourceSize => CalendarProp::MaxResourceSize(10000000), + CalendarPropName::SupportedReportSet => { + CalendarProp::SupportedReportSet(SupportedReportSet::default()) + } + // CalendarPropName::SyncToken => CalendarProp::SyncToken(self.get_synctoken()), + // CalendarPropName::Getctag => CalendarProp::Getctag(self.get_synctoken()), + CalendarPropName::Source => CalendarProp::Source( + self.cal.subscription_url.to_owned().map(HrefElement::from), + ), + }), + CalendarPropWrapperName::SyncToken(prop) => { + CalendarPropWrapper::SyncToken(::get_prop(self, prop)?) } - CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.cal.color.clone()), - CalendarPropName::CalendarDescription => { - CalendarProp::CalendarDescription(self.cal.description.clone()) - } - CalendarPropName::CalendarTimezone => { - CalendarProp::CalendarTimezone(self.cal.timezone.clone()) - } - // chrono_tz uses the IANA database - CalendarPropName::TimezoneServiceSet => CalendarProp::TimezoneServiceSet( - "https://www.iana.org/time-zones".to_owned().into(), - ), - CalendarPropName::CalendarTimezoneId => { - CalendarProp::CalendarTimezoneId(self.cal.timezone_id.clone()) - } - CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.cal.order)), - CalendarPropName::SupportedCalendarComponentSet => { - CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet::default()) - } - CalendarPropName::SupportedCalendarData => { - CalendarProp::SupportedCalendarData(SupportedCalendarData::default()) - } - CalendarPropName::Getcontenttype => { - CalendarProp::Getcontenttype("text/calendar;charset=utf-8") - } - CalendarPropName::Transports => CalendarProp::Transports(Default::default()), - CalendarPropName::Topic => CalendarProp::Topic(self.cal.push_topic.to_owned()), - CalendarPropName::MaxResourceSize => CalendarProp::MaxResourceSize(10000000), - CalendarPropName::SupportedReportSet => { - CalendarProp::SupportedReportSet(SupportedReportSet::default()) - } - CalendarPropName::SyncToken => CalendarProp::SyncToken(self.get_synctoken()), - CalendarPropName::Getctag => CalendarProp::Getctag(self.get_synctoken()), - CalendarPropName::Source => { - CalendarProp::Source(self.cal.subscription_url.to_owned().map(HrefElement::from)) + CalendarPropWrapperName::DavPush(prop) => { + CalendarPropWrapper::DavPush(::get_prop(self, prop)?) } }) } @@ -167,52 +180,57 @@ impl Resource for CalendarResource { return Err(rustical_dav::Error::PropReadOnly); } match prop { - CalendarProp::Displayname(displayname) => { - self.cal.displayname = displayname; - Ok(()) - } - CalendarProp::CalendarColor(color) => { - self.cal.color = color; - Ok(()) - } - CalendarProp::CalendarDescription(description) => { - self.cal.description = description; - Ok(()) - } - CalendarProp::CalendarTimezone(timezone) => { - // TODO: Ensure that timezone-id is also updated - self.cal.timezone = timezone; - Ok(()) - } - CalendarProp::TimezoneServiceSet(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::CalendarTimezoneId(timezone_id) => { - if let Some(tzid) = &timezone_id { - // Validate timezone id - chrono_tz::Tz::from_str(tzid).map_err(|_| { - rustical_dav::Error::BadRequest(format!("Invalid timezone-id: {}", tzid)) - })?; - // TODO: Ensure that timezone is also updated (For now hope that clients play nice) + CalendarPropWrapper::Calendar(prop) => match prop { + CalendarProp::Displayname(displayname) => { + self.cal.displayname = displayname; + Ok(()) } - self.cal.timezone_id = timezone_id; - Ok(()) + CalendarProp::CalendarColor(color) => { + self.cal.color = color; + Ok(()) + } + CalendarProp::CalendarDescription(description) => { + self.cal.description = description; + Ok(()) + } + CalendarProp::CalendarTimezone(timezone) => { + // TODO: Ensure that timezone-id is also updated + self.cal.timezone = timezone; + Ok(()) + } + CalendarProp::TimezoneServiceSet(_) => Err(rustical_dav::Error::PropReadOnly), + CalendarProp::CalendarTimezoneId(timezone_id) => { + if let Some(tzid) = &timezone_id { + // Validate timezone id + chrono_tz::Tz::from_str(tzid).map_err(|_| { + rustical_dav::Error::BadRequest(format!( + "Invalid timezone-id: {}", + tzid + )) + })?; + // TODO: Ensure that timezone is also updated (For now hope that clients play nice) + } + self.cal.timezone_id = timezone_id; + Ok(()) + } + CalendarProp::CalendarOrder(order) => { + self.cal.order = order.unwrap_or_default(); + Ok(()) + } + CalendarProp::SupportedCalendarComponentSet(_) => { + Err(rustical_dav::Error::PropReadOnly) + } + CalendarProp::SupportedCalendarData(_) => Err(rustical_dav::Error::PropReadOnly), + CalendarProp::Getcontenttype(_) => Err(rustical_dav::Error::PropReadOnly), + CalendarProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly), + CalendarProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly), + // Converting between a calendar subscription calendar and a normal one would be weird + CalendarProp::Source(_) => Err(rustical_dav::Error::PropReadOnly), + }, + CalendarPropWrapper::SyncToken(prop) => { + ::set_prop(self, prop) } - CalendarProp::CalendarOrder(order) => { - self.cal.order = order.unwrap_or_default(); - Ok(()) - } - CalendarProp::SupportedCalendarComponentSet(_) => { - Err(rustical_dav::Error::PropReadOnly) - } - CalendarProp::SupportedCalendarData(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::Getcontenttype(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::Transports(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::Topic(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly), - CalendarProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly), - // Converting between a calendar subscription calendar and a normal one would be weird - CalendarProp::Source(_) => Err(rustical_dav::Error::PropReadOnly), + CalendarPropWrapper::DavPush(prop) => ::set_prop(self, prop), } } @@ -221,44 +239,48 @@ impl Resource for CalendarResource { return Err(rustical_dav::Error::PropReadOnly); } match prop { - CalendarPropName::Displayname => { - self.cal.displayname = None; - Ok(()) + CalendarPropWrapperName::Calendar(prop) => match prop { + CalendarPropName::Displayname => { + self.cal.displayname = None; + Ok(()) + } + CalendarPropName::CalendarColor => { + self.cal.color = None; + Ok(()) + } + CalendarPropName::CalendarDescription => { + self.cal.description = None; + Ok(()) + } + CalendarPropName::CalendarTimezone => { + self.cal.timezone = None; + Ok(()) + } + CalendarPropName::TimezoneServiceSet => Err(rustical_dav::Error::PropReadOnly), + CalendarPropName::CalendarTimezoneId => { + self.cal.timezone_id = None; + Ok(()) + } + CalendarPropName::CalendarOrder => { + self.cal.order = 0; + Ok(()) + } + CalendarPropName::SupportedCalendarComponentSet => { + Err(rustical_dav::Error::PropReadOnly) + } + CalendarPropName::SupportedCalendarData => Err(rustical_dav::Error::PropReadOnly), + CalendarPropName::Getcontenttype => Err(rustical_dav::Error::PropReadOnly), + CalendarPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly), + CalendarPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly), + // Converting a calendar subscription calendar into a normal one would be weird + CalendarPropName::Source => Err(rustical_dav::Error::PropReadOnly), + }, + CalendarPropWrapperName::SyncToken(prop) => { + ::remove_prop(self, prop) } - CalendarPropName::CalendarColor => { - self.cal.color = None; - Ok(()) + CalendarPropWrapperName::DavPush(prop) => { + ::remove_prop(self, prop) } - CalendarPropName::CalendarDescription => { - self.cal.description = None; - Ok(()) - } - CalendarPropName::CalendarTimezone => { - self.cal.timezone = None; - Ok(()) - } - CalendarPropName::TimezoneServiceSet => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::CalendarTimezoneId => { - self.cal.timezone_id = None; - Ok(()) - } - CalendarPropName::CalendarOrder => { - self.cal.order = 0; - Ok(()) - } - CalendarPropName::SupportedCalendarComponentSet => { - Err(rustical_dav::Error::PropReadOnly) - } - CalendarPropName::SupportedCalendarData => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::Getcontenttype => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::Transports => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::Topic => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::SyncToken => Err(rustical_dav::Error::PropReadOnly), - CalendarPropName::Getctag => Err(rustical_dav::Error::PropReadOnly), - // Converting a calendar subscription calendar into a normal one would be weird - CalendarPropName::Source => Err(rustical_dav::Error::PropReadOnly), } } diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index 0173925..4a6829c 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -10,8 +10,10 @@ use actix_web::http::Method; use actix_web::web; use async_trait::async_trait; use derive_more::derive::{From, Into}; +use rustical_dav::extensions::{ + DavPushExtension, DavPushExtensionProp, SyncTokenExtension, SyncTokenExtensionProp, +}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::push::Transports; use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; @@ -44,13 +46,6 @@ pub enum AddressbookProp { #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] Getcontenttype(&'static str), - // WebDav Push - #[xml(skip_deserializing)] - #[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] - Transports(Transports), - #[xml(ns = "rustical_dav::namespace::NS_DAVPUSH")] - Topic(String), - // CardDAV (RFC 6352) #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] AddressbookDescription(Option), @@ -60,22 +55,34 @@ pub enum AddressbookProp { SupportedReportSet(SupportedReportSet), #[xml(ns = "rustical_dav::namespace::NS_DAV")] MaxResourceSize(i64), +} - // Collection Synchronization (RFC 6578) - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - SyncToken(String), - - // https://github.com/apple/ccs-calendarserver/blob/master/doc/Extensions/caldav-ctag.txt - #[xml(ns = "rustical_dav::namespace::NS_CALENDARSERVER")] - Getctag(String), +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)] +#[xml(unit_variants_ident = "AddressbookPropWrapperName", untagged)] +pub enum AddressbookPropWrapper { + Addressbook(AddressbookProp), + SyncToken(SyncTokenExtensionProp), + DavPush(DavPushExtensionProp), } #[derive(Clone, Debug, From, Into)] pub struct AddressbookResource(Addressbook); +impl SyncTokenExtension for AddressbookResource { + fn get_synctoken(&self) -> String { + self.0.format_synctoken() + } +} + +impl DavPushExtension for AddressbookResource { + fn get_topic(&self) -> String { + self.0.push_topic.to_owned() + } +} + impl Resource for AddressbookResource { - type PropName = AddressbookPropName; - type Prop = AddressbookProp; + type PropName = AddressbookPropWrapperName; + type Prop = AddressbookPropWrapper; type Error = Error; type PrincipalResource = PrincipalResource; @@ -93,68 +100,85 @@ impl Resource for AddressbookResource { prop: &Self::PropName, ) -> Result { Ok(match prop { - AddressbookPropName::Displayname => { - AddressbookProp::Displayname(self.0.displayname.clone()) + AddressbookPropWrapperName::Addressbook(prop) => { + AddressbookPropWrapper::Addressbook(match prop { + AddressbookPropName::Displayname => { + AddressbookProp::Displayname(self.0.displayname.clone()) + } + AddressbookPropName::Getcontenttype => { + AddressbookProp::Getcontenttype("text/vcard;charset=utf-8") + } + AddressbookPropName::MaxResourceSize => { + AddressbookProp::MaxResourceSize(10000000) + } + AddressbookPropName::SupportedReportSet => { + AddressbookProp::SupportedReportSet(SupportedReportSet::default()) + } + AddressbookPropName::AddressbookDescription => { + AddressbookProp::AddressbookDescription(self.0.description.to_owned()) + } + AddressbookPropName::SupportedAddressData => { + AddressbookProp::SupportedAddressData(SupportedAddressData::default()) + } + }) } - AddressbookPropName::Getcontenttype => { - AddressbookProp::Getcontenttype("text/vcard;charset=utf-8") + + AddressbookPropWrapperName::SyncToken(prop) => AddressbookPropWrapper::SyncToken( + ::get_prop(self, prop)?, + ), + AddressbookPropWrapperName::DavPush(prop) => { + AddressbookPropWrapper::DavPush(::get_prop(self, prop)?) } - AddressbookPropName::Transports => AddressbookProp::Transports(Default::default()), - AddressbookPropName::Topic => AddressbookProp::Topic(self.0.push_topic.to_owned()), - AddressbookPropName::MaxResourceSize => AddressbookProp::MaxResourceSize(10000000), - AddressbookPropName::SupportedReportSet => { - AddressbookProp::SupportedReportSet(SupportedReportSet::default()) - } - AddressbookPropName::AddressbookDescription => { - AddressbookProp::AddressbookDescription(self.0.description.to_owned()) - } - AddressbookPropName::SupportedAddressData => { - AddressbookProp::SupportedAddressData(SupportedAddressData::default()) - } - AddressbookPropName::SyncToken => AddressbookProp::SyncToken(self.0.format_synctoken()), - AddressbookPropName::Getctag => AddressbookProp::Getctag(self.0.format_synctoken()), }) } fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> { match prop { - AddressbookProp::Displayname(displayname) => { - self.0.displayname = displayname; - Ok(()) + AddressbookPropWrapper::Addressbook(prop) => match prop { + AddressbookProp::Displayname(displayname) => { + self.0.displayname = displayname; + Ok(()) + } + AddressbookProp::AddressbookDescription(description) => { + self.0.description = description; + Ok(()) + } + AddressbookProp::Getcontenttype(_) => Err(rustical_dav::Error::PropReadOnly), + AddressbookProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly), + AddressbookProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly), + AddressbookProp::SupportedAddressData(_) => Err(rustical_dav::Error::PropReadOnly), + }, + AddressbookPropWrapper::SyncToken(prop) => { + ::set_prop(self, prop) } - AddressbookProp::AddressbookDescription(description) => { - self.0.description = description; - Ok(()) + AddressbookPropWrapper::DavPush(prop) => { + ::set_prop(self, prop) } - AddressbookProp::Getcontenttype(_) => Err(rustical_dav::Error::PropReadOnly), - AddressbookProp::Transports(_) => Err(rustical_dav::Error::PropReadOnly), - AddressbookProp::Topic(_) => Err(rustical_dav::Error::PropReadOnly), - AddressbookProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly), - AddressbookProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly), - AddressbookProp::SupportedAddressData(_) => Err(rustical_dav::Error::PropReadOnly), - AddressbookProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly), - AddressbookProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly), } } fn remove_prop(&mut self, prop: &Self::PropName) -> Result<(), rustical_dav::Error> { match prop { - AddressbookPropName::Displayname => { - self.0.displayname = None; - Ok(()) + AddressbookPropWrapperName::Addressbook(prop) => match prop { + AddressbookPropName::Displayname => { + self.0.displayname = None; + Ok(()) + } + AddressbookPropName::AddressbookDescription => { + self.0.description = None; + Ok(()) + } + AddressbookPropName::Getcontenttype => Err(rustical_dav::Error::PropReadOnly), + AddressbookPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly), + AddressbookPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly), + AddressbookPropName::SupportedAddressData => Err(rustical_dav::Error::PropReadOnly), + }, + AddressbookPropWrapperName::SyncToken(prop) => { + ::remove_prop(self, prop) } - AddressbookPropName::AddressbookDescription => { - self.0.description = None; - Ok(()) + AddressbookPropWrapperName::DavPush(prop) => { + ::remove_prop(self, prop) } - AddressbookPropName::Getcontenttype => Err(rustical_dav::Error::PropReadOnly), - AddressbookPropName::Transports => Err(rustical_dav::Error::PropReadOnly), - AddressbookPropName::Topic => Err(rustical_dav::Error::PropReadOnly), - AddressbookPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly), - AddressbookPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly), - AddressbookPropName::SupportedAddressData => Err(rustical_dav::Error::PropReadOnly), - AddressbookPropName::SyncToken => Err(rustical_dav::Error::PropReadOnly), - AddressbookPropName::Getctag => Err(rustical_dav::Error::PropReadOnly), } } diff --git a/crates/dav/src/extensions/davpush.rs b/crates/dav/src/extensions/davpush.rs new file mode 100644 index 0000000..60b467c --- /dev/null +++ b/crates/dav/src/extensions/davpush.rs @@ -0,0 +1,37 @@ +use crate::push::Transports; +use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)] +#[xml(unit_variants_ident = "DavPushExtensionPropName")] +pub enum DavPushExtensionProp { + // WebDav Push + #[xml(skip_deserializing)] + #[xml(ns = "crate::namespace::NS_DAVPUSH")] + Transports(Transports), + #[xml(ns = "crate::namespace::NS_DAVPUSH")] + Topic(String), +} + +pub trait DavPushExtension { + fn get_topic(&self) -> String; + + fn get_prop( + &self, + prop: &DavPushExtensionPropName, + ) -> Result { + Ok(match &prop { + DavPushExtensionPropName::Transports => { + DavPushExtensionProp::Transports(Default::default()) + } + DavPushExtensionPropName::Topic => DavPushExtensionProp::Topic(self.get_topic()), + }) + } + + fn set_prop(&self, _prop: DavPushExtensionProp) -> Result<(), crate::Error> { + Err(crate::Error::PropReadOnly) + } + + fn remove_prop(&self, _prop: &DavPushExtensionPropName) -> Result<(), crate::Error> { + Err(crate::Error::PropReadOnly) + } +} diff --git a/crates/dav/src/extensions/mod.rs b/crates/dav/src/extensions/mod.rs new file mode 100644 index 0000000..3813280 --- /dev/null +++ b/crates/dav/src/extensions/mod.rs @@ -0,0 +1,5 @@ +mod davpush; +mod synctoken; + +pub use davpush::*; +pub use synctoken::*; diff --git a/crates/dav/src/extensions/synctoken.rs b/crates/dav/src/extensions/synctoken.rs new file mode 100644 index 0000000..2a4531e --- /dev/null +++ b/crates/dav/src/extensions/synctoken.rs @@ -0,0 +1,39 @@ +use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumUnitVariants, EnumVariants)] +#[xml(unit_variants_ident = "SyncTokenExtensionPropName")] +pub enum SyncTokenExtensionProp { + // Collection Synchronization (RFC 6578) + #[xml(ns = "crate::namespace::NS_DAV")] + SyncToken(String), + + // CalendarServer + #[xml(ns = "crate::namespace::NS_CALENDARSERVER")] + Getctag(String), +} + +pub trait SyncTokenExtension { + fn get_synctoken(&self) -> String; + + fn get_prop( + &self, + prop: &SyncTokenExtensionPropName, + ) -> Result { + Ok(match &prop { + SyncTokenExtensionPropName::SyncToken => { + SyncTokenExtensionProp::SyncToken(self.get_synctoken()) + } + SyncTokenExtensionPropName::Getctag => { + SyncTokenExtensionProp::Getctag(self.get_synctoken()) + } + }) + } + + fn set_prop(&self, _prop: SyncTokenExtensionProp) -> Result<(), crate::Error> { + Err(crate::Error::PropReadOnly) + } + + fn remove_prop(&self, _prop: &SyncTokenExtensionPropName) -> Result<(), crate::Error> { + Err(crate::Error::PropReadOnly) + } +} diff --git a/crates/dav/src/lib.rs b/crates/dav/src/lib.rs index 16b2dac..56207c9 100644 --- a/crates/dav/src/lib.rs +++ b/crates/dav/src/lib.rs @@ -1,5 +1,6 @@ pub mod depth_header; pub mod error; +pub mod extensions; pub mod namespace; pub mod privileges; pub mod push;