diff --git a/crates/caldav/src/calendar/prop.rs b/crates/caldav/src/calendar/prop.rs index 0485c13..bee6c41 100644 --- a/crates/caldav/src/calendar/prop.rs +++ b/crates/caldav/src/calendar/prop.rs @@ -1,20 +1,20 @@ -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Clone, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct SupportedCalendarComponent { #[serde(rename = "@name")] pub name: String, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Clone, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct SupportedCalendarComponentSet { #[serde(rename = "C:comp")] pub comp: Vec, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Clone, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct CalendarData { #[serde(rename = "@content-type")] diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 11ceb6c..880aec8 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -19,7 +19,8 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::auth::User; use rustical_store::{Calendar, CalendarStore}; -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use serde::Serialize; use sha2::{Digest, Sha256}; use std::str::FromStr; use std::sync::Arc; @@ -31,7 +32,7 @@ pub struct CalendarResourceService { calendar_id: String, } -#[derive(Default, Deserialize, Serialize, PartialEq, EnumDiscriminants)] +#[derive(Default, XmlDeserialize, Serialize, PartialEq, EnumDiscriminants)] #[strum_discriminants( name(CalendarPropName), derive(EnumString, VariantNames), @@ -48,6 +49,7 @@ pub enum CalendarProp { // in DAVx5 yet // https://github.com/bitfireAT/webdav-push/commit/461259a2f2174454b2b00033419b11fac52b79e3 #[serde(skip_deserializing)] + #[xml(skip_deserializing)] #[serde(rename = "P:push-transports", alias = "push-transports")] Transports(Transports), Topic(String), @@ -65,15 +67,19 @@ pub enum CalendarProp { rename = "C:supported-calendar-component-set", alias = "supported-calendar-component-set" )] + // TODO: Re-add + #[xml(skip_deserializing)] SupportedCalendarComponentSet(SupportedCalendarComponentSet), #[serde( rename = "C:supported-calendar-data", alias = "supported-calendar-data" )] #[serde(skip_deserializing)] + #[xml(skip_deserializing)] SupportedCalendarData(SupportedCalendarData), MaxResourceSize(i64), #[serde(skip_deserializing)] + #[xml(skip_deserializing)] SupportedReportSet(SupportedReportSet), // Collection Synchronization (RFC 6578) diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index b5cfc9f..eba2d27 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -8,6 +8,7 @@ use rustical_dav::{ resource::{Resource, ResourceService}, }; use rustical_store::{auth::User, CalendarObject, CalendarStore}; +use rustical_xml::XmlDeserialize; use serde::{Deserialize, Serialize}; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, VariantNames}; @@ -19,7 +20,7 @@ pub struct CalendarObjectResourceService { object_id: String, } -#[derive(Default, Deserialize, Serialize, PartialEq, EnumDiscriminants)] +#[derive(Default, XmlDeserialize, Serialize, PartialEq, EnumDiscriminants)] #[strum_discriminants( name(CalendarObjectPropName), derive(EnumString, VariantNames), @@ -36,6 +37,7 @@ pub enum CalendarObjectProp { CalendarData(String), #[serde(other)] + #[xml(other)] #[strum_discriminants(strum(disabled))] #[default] Invalid, diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 9a906db..5de2be4 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -9,7 +9,8 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::auth::User; use rustical_store::CalendarStore; -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use serde::Serialize; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, VariantNames}; @@ -23,7 +24,7 @@ pub struct PrincipalResource { principal: String, } -#[derive(Default, Deserialize, Serialize, PartialEq, EnumDiscriminants)] +#[derive(Default, XmlDeserialize, Serialize, PartialEq, EnumDiscriminants)] #[strum_discriminants( name(PrincipalPropName), derive(EnumString, VariantNames), @@ -43,6 +44,7 @@ pub enum PrincipalProp { CalendarUserAddressSet(HrefElement), #[serde(other)] + #[xml(other)] #[strum_discriminants(strum(disabled))] #[default] Invalid, diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index 136eff2..7050167 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -7,6 +7,7 @@ use rustical_dav::{ resource::{Resource, ResourceService}, }; use rustical_store::{auth::User, AddressObject, AddressbookStore}; +use rustical_xml::XmlDeserialize; use serde::{Deserialize, Serialize}; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, VariantNames}; @@ -20,7 +21,7 @@ pub struct AddressObjectResourceService { object_id: String, } -#[derive(Default, Deserialize, Serialize, PartialEq, EnumDiscriminants)] +#[derive(Default, XmlDeserialize, Serialize, PartialEq, EnumDiscriminants)] #[strum_discriminants( name(AddressObjectPropName), derive(EnumString, VariantNames), @@ -37,6 +38,7 @@ pub enum AddressObjectProp { AddressData(String), #[serde(other)] + #[xml(other)] #[strum_discriminants(strum(disabled))] #[default] Invalid, diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index 1a42735..a7a15b3 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -14,7 +14,8 @@ use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{Resource, ResourceService}; use rustical_store::auth::User; use rustical_store::{Addressbook, AddressbookStore}; -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use serde::Serialize; use std::str::FromStr; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, VariantNames}; @@ -25,7 +26,7 @@ pub struct AddressbookResourceService { addressbook_id: String, } -#[derive(Default, Deserialize, Serialize, PartialEq, EnumDiscriminants)] +#[derive(Default, XmlDeserialize, Serialize, PartialEq, EnumDiscriminants)] #[serde(rename_all = "kebab-case")] #[strum_discriminants( name(AddressbookPropName), @@ -48,8 +49,10 @@ pub enum AddressbookProp { alias = "supported-address-data" )] #[serde(skip_deserializing)] + #[xml(skip_deserializing)] SupportedAddressData(SupportedAddressData), #[serde(skip_deserializing)] + #[xml(skip_deserializing)] SupportedReportSet(SupportedReportSet), MaxResourceSize(i64), @@ -60,6 +63,7 @@ pub enum AddressbookProp { Getctag(String), #[serde(other)] + #[xml(other)] #[strum_discriminants(strum(disabled))] #[default] Invalid, diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index c9a8250..9db6f14 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -9,7 +9,8 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::auth::User; use rustical_store::AddressbookStore; -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use serde::Serialize; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, VariantNames}; @@ -23,7 +24,7 @@ pub struct PrincipalResource { principal: String, } -#[derive(Default, Deserialize, Serialize, PartialEq, EnumDiscriminants)] +#[derive(Default, XmlDeserialize, Serialize, PartialEq, EnumDiscriminants)] #[strum_discriminants( name(PrincipalPropName), derive(EnumString, VariantNames), @@ -43,6 +44,7 @@ pub enum PrincipalProp { PrincipalAddress(Option), #[serde(other)] + #[xml(other)] #[strum_discriminants(strum(disabled))] #[default] Invalid, diff --git a/crates/dav/src/error.rs b/crates/dav/src/error.rs index e8abcb4..23b5ae0 100644 --- a/crates/dav/src/error.rs +++ b/crates/dav/src/error.rs @@ -20,10 +20,7 @@ pub enum Error { PropReadOnly, #[error(transparent)] - NewXmlDeserializationError(#[from] rustical_xml::XmlDeError), - - #[error(transparent)] - XmlDeserializationError(#[from] quick_xml::DeError), + XmlDeserializationError(#[from] rustical_xml::XmlDeError), #[error(transparent)] XmlSerializationError(#[from] quick_xml::SeError), @@ -36,7 +33,6 @@ impl actix_web::error::ResponseError for Error { Self::NotFound => StatusCode::NOT_FOUND, Self::BadRequest(_) => StatusCode::BAD_REQUEST, Self::Unauthorized => StatusCode::UNAUTHORIZED, - Self::NewXmlDeserializationError(_) => StatusCode::BAD_REQUEST, Self::XmlDeserializationError(_) => StatusCode::BAD_REQUEST, Self::XmlSerializationError(_) => StatusCode::BAD_REQUEST, Error::PropReadOnly => StatusCode::CONFLICT, diff --git a/crates/dav/src/privileges.rs b/crates/dav/src/privileges.rs index c1a0ea5..6006c07 100644 --- a/crates/dav/src/privileges.rs +++ b/crates/dav/src/privileges.rs @@ -1,8 +1,8 @@ -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use serde::Serialize; use std::collections::HashSet; -#[derive(Debug, Clone, Serialize, Deserialize, Eq, Hash, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, Serialize, XmlDeserialize, Eq, Hash, PartialEq)] pub enum UserPrivilege { Read, Write, @@ -42,10 +42,9 @@ impl Serialize for UserPrivilegeSet { } } -// TODO: implement Deserialize once we need it -#[derive(Debug, Clone, Deserialize, Default, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, XmlDeserialize, Default, PartialEq)] pub struct UserPrivilegeSet { + #[xml(flatten)] privileges: HashSet, } diff --git a/crates/dav/src/resource/methods/propfind.rs b/crates/dav/src/resource/methods/propfind.rs index 8a8dd73..91e70d1 100644 --- a/crates/dav/src/resource/methods/propfind.rs +++ b/crates/dav/src/resource/methods/propfind.rs @@ -42,7 +42,7 @@ pub(crate) async fn route_propfind( // A request body is optional. If empty we MUST return all props let propfind: PropfindElement = if !body.is_empty() { - PropfindElement::parse_str(&body).map_err(Error::NewXmlDeserializationError)? + PropfindElement::parse_str(&body).map_err(Error::XmlDeserializationError)? } else { PropfindElement { prop: PropfindType::Allprop, diff --git a/crates/dav/src/resource/methods/proppatch.rs b/crates/dav/src/resource/methods/proppatch.rs index d60e0f3..ab5de26 100644 --- a/crates/dav/src/resource/methods/proppatch.rs +++ b/crates/dav/src/resource/methods/proppatch.rs @@ -5,47 +5,53 @@ use crate::resource::ResourceService; use crate::xml::multistatus::{PropstatElement, PropstatWrapper, ResponseElement}; use crate::xml::MultistatusElement; use crate::xml::TagList; -use crate::xml::TagName; use crate::Error; use actix_web::http::StatusCode; use actix_web::{web::Path, HttpRequest}; use rustical_store::auth::User; -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use rustical_xml::XmlDocument; +use rustical_xml::XmlRootTag; use std::str::FromStr; use tracing::instrument; use tracing_actix_web::RootSpan; -// https://docs.rs/quick-xml/latest/quick_xml/de/index.html#normal-enum-variant -#[derive(Deserialize, Serialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -struct PropertyElement { - #[serde(rename = "$value")] +#[derive(XmlDeserialize, Clone, Debug)] +struct SetPropertyElement { prop: T, } -#[derive(Deserialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -struct SetPropertyElement { - prop: PropertyElement, +#[derive(XmlDeserialize, Clone, Debug)] +struct TagName { + #[xml(ty = "tag_name")] + name: String, } -#[derive(Deserialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] +#[derive(XmlDeserialize, Clone, Debug)] +struct PropertyElement { + #[xml(ty = "untagged")] + property: TagName, +} + +#[derive(XmlDeserialize, Clone, Debug)] struct RemovePropertyElement { - prop: PropertyElement, + prop: PropertyElement, } -#[derive(Deserialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -enum Operation { - Set(SetPropertyElement), +#[derive(XmlDeserialize, Clone, Debug)] +enum Operation { + Set(SetPropertyElement), Remove(RemovePropertyElement), } -#[derive(Deserialize, Clone, Debug)] -#[serde(rename_all = "kebab-case")] -struct PropertyupdateElement { - #[serde(rename = "$value", default = "Vec::new")] +#[derive(XmlDeserialize, XmlRootTag, Clone, Debug)] +#[xml(root = b"propertyupdate")] +struct PropertyupdateElement { + // #[xml(flatten)] + // set: Vec, + // #[xml(flatten)] + // remove: Vec, + #[xml(ty = "untagged", flatten)] operations: Vec>, } @@ -63,18 +69,18 @@ pub(crate) async fn route_proppatch( // Extract operations let PropertyupdateElement::<::Prop> { operations } = - quick_xml::de::from_str(&body).map_err(Error::XmlDeserializationError)?; + XmlDocument::parse_str(&body).map_err(Error::XmlDeserializationError)?; // Extract all set property names without verification // Weird workaround because quick_xml doesn't allow untagged enums - let propnames: Vec = quick_xml::de::from_str::>(&body) + let propnames: Vec = PropertyupdateElement::::parse_str(&body) .map_err(Error::XmlDeserializationError)? .operations .into_iter() .map(|op_el| match op_el { - Operation::Set(set_el) => set_el.prop.prop.into(), + Operation::Set(set_el) => set_el.prop.name, // If we can't remove a nonexisting property then that's no big deal - Operation::Remove(remove_el) => remove_el.prop.prop.into(), + Operation::Remove(remove_el) => remove_el.prop.property.name, }) .collect(); @@ -90,9 +96,7 @@ pub(crate) async fn route_proppatch( for (operation, propname) in operations.into_iter().zip(propnames) { match operation { - Operation::Set(SetPropertyElement { - prop: PropertyElement { prop }, - }) => { + Operation::Set(SetPropertyElement { prop }) => { if prop.invalid_property() { if ::list_props().contains(&propname.as_str()) { // This happens in following cases: diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index 79e48ca..47b4307 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -11,7 +11,8 @@ pub use invalid_property::InvalidProperty; use itertools::Itertools; pub use resource_service::ResourceService; use rustical_store::auth::User; -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use serde::Serialize; use std::str::FromStr; use strum::{EnumString, VariantNames}; @@ -19,20 +20,21 @@ mod invalid_property; mod methods; mod resource_service; -pub trait ResourceProp: InvalidProperty + Serialize + for<'de> Deserialize<'de> {} -impl Deserialize<'de>> ResourceProp for T {} +pub trait ResourceProp: InvalidProperty + Serialize + XmlDeserialize {} +impl ResourceProp for T {} pub trait ResourcePropName: FromStr + VariantNames {} impl ResourcePropName for T {} -pub trait ResourceType: Serialize + for<'de> Deserialize<'de> {} -impl Deserialize<'de>> ResourceType for T {} +pub trait ResourceType: Serialize + XmlDeserialize {} +impl ResourceType for T {} -#[derive(Deserialize, Serialize, PartialEq, Default)] +#[derive(XmlDeserialize, Serialize, PartialEq, Default)] #[serde(rename_all = "kebab-case")] pub enum CommonPropertiesProp { // WebDAV (RFC 2518) #[serde(skip_deserializing)] + #[xml(skip_deserializing)] Resourcetype(Resourcetype), // WebDAV Current Principal Extension (RFC 5397) @@ -40,7 +42,7 @@ pub enum CommonPropertiesProp { // WebDAV Access Control Protocol (RFC 3477) CurrentUserPrivilegeSet(UserPrivilegeSet), - Owner(Option), + Owner(HrefElement), #[serde(other)] #[default] @@ -97,11 +99,18 @@ pub trait Resource: Clone + 'static { CommonPropertiesProp::CurrentUserPrivilegeSet(self.get_user_privileges(user)?) } CommonPropertiesPropName::Owner => { - CommonPropertiesProp::Owner(self.get_owner().map(|owner| { + // TODO: Reintroduce optional owner field + let owner = self.get_owner().unwrap_or(&user.id); + CommonPropertiesProp::Owner( Self::PrincipalResource::get_url(rmap, [owner]) .unwrap() - .into() - })) + .into(), + ) + // CommonPropertiesProp::Owner(self.get_owner().map(|owner| { + // Self::PrincipalResource::get_url(rmap, [owner]) + // .unwrap() + // .into() + // })) } }) } diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index 9bcaf61..5be0853 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -4,7 +4,8 @@ use actix_web::dev::ResourceMap; use actix_web::HttpRequest; use async_trait::async_trait; use rustical_store::auth::User; -use serde::{Deserialize, Serialize}; +use rustical_xml::XmlDeserialize; +use serde::Serialize; use std::any::type_name; use std::marker::PhantomData; use strum::{EnumString, VariantNames}; @@ -22,7 +23,7 @@ impl Default for RootResource { #[strum(serialize_all = "kebab-case")] pub enum RootResourcePropName {} -#[derive(Deserialize, Serialize, Default, Clone, PartialEq)] +#[derive(XmlDeserialize, Serialize, Default, Clone, PartialEq)] pub enum RootResourceProp { #[serde(other)] #[default] diff --git a/crates/dav/src/xml/mod.rs b/crates/dav/src/xml/mod.rs index 88d28aa..df9f865 100644 --- a/crates/dav/src/xml/mod.rs +++ b/crates/dav/src/xml/mod.rs @@ -2,20 +2,15 @@ pub mod multistatus; mod propfind; mod resourcetype; pub mod tag_list; -pub mod tag_name; - -pub use propfind::{PropElement, PropfindElement, PropfindType, Propname}; - use derive_more::derive::From; pub use multistatus::MultistatusElement; -pub use tag_list::TagList; -pub use tag_name::TagName; - +pub use propfind::{PropElement, PropfindElement, PropfindType, Propname}; pub use resourcetype::Resourcetype; +use rustical_xml::XmlDeserialize; +use serde::Serialize; +pub use tag_list::TagList; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize, Serialize, From, PartialEq)] +#[derive(XmlDeserialize, Debug, Clone, Serialize, From, PartialEq)] pub struct HrefElement { pub href: String, } diff --git a/crates/dav/src/xml/tag_name.rs b/crates/dav/src/xml/tag_name.rs deleted file mode 100644 index 43df209..0000000 --- a/crates/dav/src/xml/tag_name.rs +++ /dev/null @@ -1,44 +0,0 @@ -use serde::{ - de::{VariantAccess, Visitor}, - Deserialize, -}; - -#[derive(Debug, Clone, PartialEq)] -pub struct TagName(pub String); - -impl From for String { - fn from(value: TagName) -> Self { - value.0 - } -} - -impl From for TagName { - fn from(value: String) -> Self { - Self(value) - } -} - -impl<'de> Deserialize<'de> for TagName { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct __Visitor; - - impl<'de> Visitor<'de> for __Visitor { - type Value = TagName; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("tagname") - } - fn visit_enum(self, data: A) -> Result - where - A: serde::de::EnumAccess<'de>, - { - let (name, variant): (String, _) = data.variant()?; - VariantAccess::unit_variant(variant)?; - Ok(TagName(name)) - } - } - deserializer.deserialize_enum("doesn't matter", &[], __Visitor) - } -}