Group some DAV properties in extensions

This commit is contained in:
Lennart
2025-01-18 22:23:56 +01:00
parent 8d7574290c
commit 1dda9dea8d
6 changed files with 326 additions and 198 deletions

View File

@@ -10,8 +10,10 @@ use actix_web::http::Method;
use actix_web::web; use actix_web::web;
use async_trait::async_trait; use async_trait::async_trait;
use derive_more::derive::{From, Into}; use derive_more::derive::{From, Into};
use rustical_dav::extensions::{
DavPushExtension, DavPushExtensionProp, SyncTokenExtension, SyncTokenExtensionProp,
};
use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::push::Transports;
use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
@@ -31,13 +33,6 @@ pub enum CalendarProp {
#[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)]
Getcontenttype(&'static str), 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) // CalDAV (RFC 4791)
#[xml(ns = "rustical_dav::namespace::NS_ICAL")] #[xml(ns = "rustical_dav::namespace::NS_ICAL")]
CalendarColor(Option<String>), CalendarColor(Option<String>),
@@ -61,18 +56,18 @@ pub enum CalendarProp {
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
SupportedReportSet(SupportedReportSet), 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")] #[xml(ns = "rustical_dav::namespace::NS_CALENDARSERVER")]
Source(Option<HrefElement>), Source(Option<HrefElement>),
} }
#[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)] #[derive(Clone, Debug, From, Into)]
pub struct CalendarResource { pub struct CalendarResource {
pub cal: Calendar, pub cal: Calendar,
@@ -85,15 +80,21 @@ impl From<CalendarResource> for Calendar {
} }
} }
impl CalendarResource { impl SyncTokenExtension for CalendarResource {
fn get_synctoken(&self) -> String { fn get_synctoken(&self) -> String {
self.cal.format_synctoken() self.cal.format_synctoken()
} }
} }
impl DavPushExtension for CalendarResource {
fn get_topic(&self) -> String {
self.cal.push_topic.to_owned()
}
}
impl Resource for CalendarResource { impl Resource for CalendarResource {
type PropName = CalendarPropName; type PropName = CalendarPropWrapperName;
type Prop = CalendarProp; type Prop = CalendarPropWrapper;
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
@@ -121,43 +122,55 @@ impl Resource for CalendarResource {
prop: &Self::PropName, prop: &Self::PropName,
) -> Result<Self::Prop, Self::Error> { ) -> Result<Self::Prop, Self::Error> {
Ok(match prop { Ok(match prop {
CalendarPropName::Displayname => { CalendarPropWrapperName::Calendar(prop) => CalendarPropWrapper::Calendar(match prop {
CalendarProp::Displayname(self.cal.displayname.clone()) 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(<Self as SyncTokenExtension>::get_prop(self, prop)?)
} }
CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.cal.color.clone()), CalendarPropWrapperName::DavPush(prop) => {
CalendarPropName::CalendarDescription => { CalendarPropWrapper::DavPush(<Self as DavPushExtension>::get_prop(self, prop)?)
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))
} }
}) })
} }
@@ -167,52 +180,57 @@ impl Resource for CalendarResource {
return Err(rustical_dav::Error::PropReadOnly); return Err(rustical_dav::Error::PropReadOnly);
} }
match prop { match prop {
CalendarProp::Displayname(displayname) => { CalendarPropWrapper::Calendar(prop) => match prop {
self.cal.displayname = displayname; CalendarProp::Displayname(displayname) => {
Ok(()) 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)
} }
self.cal.timezone_id = timezone_id; CalendarProp::CalendarColor(color) => {
Ok(()) 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) => {
<Self as SyncTokenExtension>::set_prop(self, prop)
} }
CalendarProp::CalendarOrder(order) => { CalendarPropWrapper::DavPush(prop) => <Self as DavPushExtension>::set_prop(self, prop),
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),
} }
} }
@@ -221,44 +239,48 @@ impl Resource for CalendarResource {
return Err(rustical_dav::Error::PropReadOnly); return Err(rustical_dav::Error::PropReadOnly);
} }
match prop { match prop {
CalendarPropName::Displayname => { CalendarPropWrapperName::Calendar(prop) => match prop {
self.cal.displayname = None; CalendarPropName::Displayname => {
Ok(()) 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) => {
<Self as SyncTokenExtension>::remove_prop(self, prop)
} }
CalendarPropName::CalendarColor => { CalendarPropWrapperName::DavPush(prop) => {
self.cal.color = None; <Self as DavPushExtension>::remove_prop(self, prop)
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::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),
} }
} }

View File

@@ -10,8 +10,10 @@ use actix_web::http::Method;
use actix_web::web; use actix_web::web;
use async_trait::async_trait; use async_trait::async_trait;
use derive_more::derive::{From, Into}; use derive_more::derive::{From, Into};
use rustical_dav::extensions::{
DavPushExtension, DavPushExtensionProp, SyncTokenExtension, SyncTokenExtensionProp,
};
use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::push::Transports;
use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
@@ -44,13 +46,6 @@ pub enum AddressbookProp {
#[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)]
Getcontenttype(&'static str), 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) // CardDAV (RFC 6352)
#[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")]
AddressbookDescription(Option<String>), AddressbookDescription(Option<String>),
@@ -60,22 +55,34 @@ pub enum AddressbookProp {
SupportedReportSet(SupportedReportSet), SupportedReportSet(SupportedReportSet),
#[xml(ns = "rustical_dav::namespace::NS_DAV")] #[xml(ns = "rustical_dav::namespace::NS_DAV")]
MaxResourceSize(i64), MaxResourceSize(i64),
}
// Collection Synchronization (RFC 6578) #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, EnumUnitVariants)]
#[xml(ns = "rustical_dav::namespace::NS_DAV")] #[xml(unit_variants_ident = "AddressbookPropWrapperName", untagged)]
SyncToken(String), pub enum AddressbookPropWrapper {
Addressbook(AddressbookProp),
// https://github.com/apple/ccs-calendarserver/blob/master/doc/Extensions/caldav-ctag.txt SyncToken(SyncTokenExtensionProp),
#[xml(ns = "rustical_dav::namespace::NS_CALENDARSERVER")] DavPush(DavPushExtensionProp),
Getctag(String),
} }
#[derive(Clone, Debug, From, Into)] #[derive(Clone, Debug, From, Into)]
pub struct AddressbookResource(Addressbook); 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 { impl Resource for AddressbookResource {
type PropName = AddressbookPropName; type PropName = AddressbookPropWrapperName;
type Prop = AddressbookProp; type Prop = AddressbookPropWrapper;
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
@@ -93,68 +100,85 @@ impl Resource for AddressbookResource {
prop: &Self::PropName, prop: &Self::PropName,
) -> Result<Self::Prop, Self::Error> { ) -> Result<Self::Prop, Self::Error> {
Ok(match prop { Ok(match prop {
AddressbookPropName::Displayname => { AddressbookPropWrapperName::Addressbook(prop) => {
AddressbookProp::Displayname(self.0.displayname.clone()) 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(
<Self as SyncTokenExtension>::get_prop(self, prop)?,
),
AddressbookPropWrapperName::DavPush(prop) => {
AddressbookPropWrapper::DavPush(<Self as DavPushExtension>::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> { fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> {
match prop { match prop {
AddressbookProp::Displayname(displayname) => { AddressbookPropWrapper::Addressbook(prop) => match prop {
self.0.displayname = displayname; AddressbookProp::Displayname(displayname) => {
Ok(()) 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) => {
<Self as SyncTokenExtension>::set_prop(self, prop)
} }
AddressbookProp::AddressbookDescription(description) => { AddressbookPropWrapper::DavPush(prop) => {
self.0.description = description; <Self as DavPushExtension>::set_prop(self, prop)
Ok(())
} }
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> { fn remove_prop(&mut self, prop: &Self::PropName) -> Result<(), rustical_dav::Error> {
match prop { match prop {
AddressbookPropName::Displayname => { AddressbookPropWrapperName::Addressbook(prop) => match prop {
self.0.displayname = None; AddressbookPropName::Displayname => {
Ok(()) 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) => {
<Self as SyncTokenExtension>::remove_prop(self, prop)
} }
AddressbookPropName::AddressbookDescription => { AddressbookPropWrapperName::DavPush(prop) => {
self.0.description = None; <Self as DavPushExtension>::remove_prop(self, prop)
Ok(())
} }
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),
} }
} }

View File

@@ -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<DavPushExtensionProp, crate::Error> {
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)
}
}

View File

@@ -0,0 +1,5 @@
mod davpush;
mod synctoken;
pub use davpush::*;
pub use synctoken::*;

View File

@@ -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<SyncTokenExtensionProp, crate::Error> {
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)
}
}

View File

@@ -1,5 +1,6 @@
pub mod depth_header; pub mod depth_header;
pub mod error; pub mod error;
pub mod extensions;
pub mod namespace; pub mod namespace;
pub mod privileges; pub mod privileges;
pub mod push; pub mod push;