From c16a5214bc044a43fae20402b1c69b2c2ec87bd8 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:47:33 +0100 Subject: [PATCH] save progress: Move from serde::Serialize to XmlSerialize --- crates/caldav/src/calendar/prop.rs | 31 ++++--- crates/caldav/src/calendar/resource.rs | 33 +++---- crates/caldav/src/calendar_object/resource.rs | 9 +- crates/caldav/src/principal/mod.rs | 11 +-- crates/carddav/src/address_object/resource.rs | 9 +- crates/carddav/src/addressbook/prop.rs | 30 +++--- crates/carddav/src/addressbook/resource.rs | 18 +--- crates/carddav/src/principal/mod.rs | 12 +-- crates/dav/src/error.rs | 4 + crates/dav/src/privileges.rs | 49 +++++----- crates/dav/src/resource/mod.rs | 20 ++-- crates/dav/src/resources/root.rs | 4 +- crates/dav/src/xml/mod.rs | 5 +- crates/dav/src/xml/multistatus.rs | 91 ++++++++++--------- crates/dav/src/xml/resourcetype.rs | 66 +++++++++++--- crates/dav/src/xml/tag_list.rs | 38 +++++--- 16 files changed, 230 insertions(+), 200 deletions(-) diff --git a/crates/caldav/src/calendar/prop.rs b/crates/caldav/src/calendar/prop.rs index bee6c41..00dad9d 100644 --- a/crates/caldav/src/calendar/prop.rs +++ b/crates/caldav/src/calendar/prop.rs @@ -1,25 +1,30 @@ +use rustical_xml::XmlSerialize; use serde::Serialize; -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct SupportedCalendarComponent { #[serde(rename = "@name")] + #[xml(ty = "attr")] pub name: String, } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct SupportedCalendarComponentSet { #[serde(rename = "C:comp")] + #[xml(flatten)] pub comp: Vec, } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct CalendarData { #[serde(rename = "@content-type")] + #[xml(ty = "attr")] content_type: String, #[serde(rename = "@version")] + #[xml(ty = "attr")] version: String, } @@ -32,14 +37,14 @@ impl Default for CalendarData { } } -#[derive(Debug, Clone, Serialize, Default, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, Default, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct SupportedCalendarData { #[serde(rename = "C:calendar-data", alias = "calendar-data")] calendar_data: CalendarData, } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum ReportMethod { CalendarQuery, @@ -47,14 +52,15 @@ pub enum ReportMethod { SyncCollection, } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct ReportWrapper { #[serde(rename = "$value")] + #[xml(ty = "untagged")] report: ReportMethod, } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct SupportedReportWrapper { report: ReportWrapper, @@ -69,9 +75,10 @@ impl From for SupportedReportWrapper { } // RFC 3253 section-3.1.5 -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct SupportedReportSet { + #[xml(flatten)] supported_report: Vec, } @@ -87,27 +94,29 @@ impl Default for SupportedReportSet { } } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum Transport { #[serde(rename = "P:web-push")] WebPush, } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct TransportWrapper { #[serde(rename = "$value")] + #[xml(ty = "untagged")] transport: Transport, } -#[derive(Debug, Clone, Serialize, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)] #[serde(rename_all = "kebab-case")] pub struct Transports { // NOTE: Here we implement an older version of the spec since the new property name is not reflected // in DAVx5 yet // https://github.com/bitfireAT/webdav-push/commit/461259a2f2174454b2b00033419b11fac52b79e3 #[serde(rename = "P:transport")] + #[xml(flatten)] transports: Vec, } diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 6f9dee8..4184c46 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -19,8 +19,7 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::auth::User; use rustical_store::{Calendar, CalendarStore}; -use rustical_xml::XmlDeserialize; -use serde::Serialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; use sha2::{Digest, Sha256}; use std::str::FromStr; use std::sync::Arc; @@ -32,13 +31,12 @@ pub struct CalendarResourceService { calendar_id: String, } -#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(CalendarPropName), derive(EnumString, VariantNames, IntoStaticStr), strum(serialize_all = "kebab-case") )] -#[serde(rename_all = "kebab-case")] pub enum CalendarProp { // WebDAV (RFC 2518) Displayname(Option), @@ -48,37 +46,28 @@ pub enum CalendarProp { // NOTE: Here we implement an older version of the spec since the new property name is not reflected // 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")] + // #[serde(rename = "P:push-transports", alias = "push-transports")] Transports(Transports), Topic(String), // CalDAV (RFC 4791) - #[serde(rename = "IC:calendar-color", alias = "calendar-color")] + // #[serde(rename = "IC:calendar-color", alias = "calendar-color")] CalendarColor(Option), - #[serde(rename = "C:calendar-description", alias = "calendar-description")] + // #[serde(rename = "C:calendar-description", alias = "calendar-description")] CalendarDescription(Option), - #[serde(rename = "C:calendar-timezone", alias = "calendar-timezone")] + // #[serde(rename = "C:calendar-timezone", alias = "calendar-timezone")] CalendarTimezone(Option), - #[serde(rename = "IC:calendar-order", alias = "calendar-order")] + // #[serde(rename = "IC:calendar-order", alias = "calendar-order")] CalendarOrder(Option), - #[serde( - rename = "C:supported-calendar-component-set", - alias = "supported-calendar-component-set" - )] + // #[serde(rename = "C: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)] + // #[serde(rename = "C:supported-calendar-data")] #[xml(skip_deserializing)] SupportedCalendarData(SupportedCalendarData), MaxResourceSize(i64), - #[serde(skip_deserializing)] #[xml(skip_deserializing)] SupportedReportSet(SupportedReportSet), @@ -86,9 +75,9 @@ pub enum CalendarProp { SyncToken(String), // CalendarServer - #[serde(rename = "CS:getctag", alias = "getctag")] + // #[serde(rename = "CS:getctag", alias = "getctag")] Getctag(String), - #[serde(rename = "CS:source", alias = "source")] + // #[serde(rename = "CS:source", alias = "source")] Source(Option), } diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 933e4ba..bbe7f20 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -8,8 +8,8 @@ use rustical_dav::{ resource::{Resource, ResourceService}, }; use rustical_store::{auth::User, CalendarObject, CalendarStore}; -use rustical_xml::XmlDeserialize; -use serde::{Deserialize, Serialize}; +use rustical_xml::{XmlDeserialize, XmlSerialize}; +use serde::Deserialize; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; @@ -20,20 +20,19 @@ pub struct CalendarObjectResourceService { object_id: String, } -#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(CalendarObjectPropName), derive(EnumString, VariantNames, IntoStaticStr), strum(serialize_all = "kebab-case") )] -#[serde(rename_all = "kebab-case")] pub enum CalendarObjectProp { // WebDAV (RFC 2518) Getetag(String), Getcontenttype(String), // CalDAV (RFC 4791) - #[serde(rename = "C:calendar-data")] + // #[serde(rename = "C:calendar-data")] CalendarData(String), } diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 014fc2d..3b333ad 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -9,8 +9,7 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::auth::User; use rustical_store::CalendarStore; -use rustical_xml::XmlDeserialize; -use serde::Serialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; @@ -24,23 +23,21 @@ pub struct PrincipalResource { principal: String, } -#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(PrincipalPropName), derive(EnumString, VariantNames, IntoStaticStr), strum(serialize_all = "kebab-case") )] -#[serde(rename_all = "kebab-case")] pub enum PrincipalProp { // WebDAV Access Control (RFC 3744) - #[serde(rename = "principal-URL")] #[strum_discriminants(strum(serialize = "principal-URL"))] PrincipalUrl(HrefElement), // CalDAV (RFC 4791) - #[serde(rename = "C:calendar-home-set")] + // #[serde(rename = "C:calendar-home-set")] CalendarHomeSet(HrefElement), - #[serde(rename = "C:calendar-user-address-set")] + // #[serde(rename = "C:calendar-user-address-set")] CalendarUserAddressSet(HrefElement), } diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index ad95b91..1cf0ed6 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -7,8 +7,8 @@ use rustical_dav::{ resource::{Resource, ResourceService}, }; use rustical_store::{auth::User, AddressObject, AddressbookStore}; -use rustical_xml::XmlDeserialize; -use serde::{Deserialize, Serialize}; +use rustical_xml::{XmlDeserialize, XmlSerialize}; +use serde::Deserialize; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; @@ -21,20 +21,19 @@ pub struct AddressObjectResourceService { object_id: String, } -#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(AddressObjectPropName), derive(EnumString, VariantNames, IntoStaticStr), strum(serialize_all = "kebab-case") )] -#[serde(rename_all = "kebab-case")] pub enum AddressObjectProp { // WebDAV (RFC 2518) Getetag(String), Getcontenttype(String), // CalDAV (RFC 4791) - #[serde(rename = "CARD:address-data")] + #[xml(ns = b"urn:ietf:params:xml:ns:carddav")] AddressData(String), } diff --git a/crates/carddav/src/addressbook/prop.rs b/crates/carddav/src/addressbook/prop.rs index cd6ff86..6cfcbe7 100644 --- a/crates/carddav/src/addressbook/prop.rs +++ b/crates/carddav/src/addressbook/prop.rs @@ -1,18 +1,17 @@ -use serde::Serialize; +use rustical_xml::XmlSerialize; -#[derive(Debug, Clone, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, XmlSerialize, PartialEq)] pub struct AddressDataType { - #[serde(rename = "@content-type")] + #[xml(ty = "attr")] pub content_type: String, - #[serde(rename = "@version")] + #[xml(ty = "attr")] pub version: String, } -#[derive(Debug, Clone, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, XmlSerialize, PartialEq)] pub struct SupportedAddressData { - #[serde(rename = "CARD:address-data-type", alias = "address-data-type")] + // #[serde(rename = "CARD:address-data-type", alias = "address-data-type")] + #[xml(flatten)] address_data_type: Vec, } @@ -33,22 +32,19 @@ impl Default for SupportedAddressData { } } -#[derive(Debug, Clone, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, XmlSerialize, PartialEq)] pub enum ReportMethod { AddressbookMultiget, SyncCollection, } -#[derive(Debug, Clone, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, XmlSerialize, PartialEq)] pub struct ReportWrapper { - #[serde(rename = "$value")] + #[xml(ty = "untagged")] report: ReportMethod, } -#[derive(Debug, Clone, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, XmlSerialize, PartialEq)] pub struct SupportedReportWrapper { report: ReportWrapper, } @@ -62,9 +58,9 @@ impl From for SupportedReportWrapper { } // RFC 3253 section-3.1.5 -#[derive(Debug, Clone, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Clone, XmlSerialize, PartialEq)] pub struct SupportedReportSet { + #[xml(flatten)] supported_report: Vec, } diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index be50ddd..dfe153b 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -14,8 +14,7 @@ use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{Resource, ResourceService}; use rustical_store::auth::User; use rustical_store::{Addressbook, AddressbookStore}; -use rustical_xml::XmlDeserialize; -use serde::Serialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::str::FromStr; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; @@ -26,8 +25,7 @@ pub struct AddressbookResourceService { addressbook_id: String, } -#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] -#[serde(rename_all = "kebab-case")] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(AddressbookPropName), derive(EnumString, VariantNames, IntoStaticStr), @@ -39,19 +37,11 @@ pub enum AddressbookProp { Getcontenttype(String), // CardDAV (RFC 6352) - #[serde( - rename = "CARD:addressbook-description", - alias = "addressbook-description" - )] + #[xml(ns = b"urn:ietf:params:xml:ns:carddav")] AddressbookDescription(Option), - #[serde( - rename = "CARD:supported-address-data", - alias = "supported-address-data" - )] - #[serde(skip_deserializing)] #[xml(skip_deserializing)] + #[xml(ns = b"urn:ietf:params:xml:ns:carddav")] SupportedAddressData(SupportedAddressData), - #[serde(skip_deserializing)] #[xml(skip_deserializing)] SupportedReportSet(SupportedReportSet), MaxResourceSize(i64), diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index f0fef53..6843368 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -9,8 +9,7 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::auth::User; use rustical_store::AddressbookStore; -use rustical_xml::XmlDeserialize; -use serde::Serialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::sync::Arc; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; @@ -24,23 +23,22 @@ pub struct PrincipalResource { principal: String, } -#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[strum_discriminants( name(PrincipalPropName), derive(EnumString, VariantNames, IntoStaticStr), strum(serialize_all = "kebab-case") )] -#[serde(rename_all = "kebab-case")] pub enum PrincipalProp { // WebDAV Access Control (RFC 3744) - #[serde(rename = "principal-URL")] + #[xml(rename = b"principal-URL")] #[strum_discriminants(strum(serialize = "principal-URL"))] PrincipalUrl(HrefElement), // CardDAV (RFC 6352) - #[serde(rename = "CARD:addressbook-home-set")] + #[xml(ns = b"urn:ietf:params:xml:ns:carddav")] AddressbookHomeSet(HrefElement), - #[serde(rename = "CARD:principal-address")] + #[xml(ns = b"urn:ietf:params:xml:ns:carddav")] PrincipalAddress(Option), } diff --git a/crates/dav/src/error.rs b/crates/dav/src/error.rs index 23b5ae0..5536185 100644 --- a/crates/dav/src/error.rs +++ b/crates/dav/src/error.rs @@ -24,6 +24,9 @@ pub enum Error { #[error(transparent)] XmlSerializationError(#[from] quick_xml::SeError), + + #[error(transparent)] + IOError(#[from] std::io::Error), } impl actix_web::error::ResponseError for Error { @@ -36,6 +39,7 @@ impl actix_web::error::ResponseError for Error { Self::XmlDeserializationError(_) => StatusCode::BAD_REQUEST, Self::XmlSerializationError(_) => StatusCode::BAD_REQUEST, Error::PropReadOnly => StatusCode::CONFLICT, + Self::IOError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/crates/dav/src/privileges.rs b/crates/dav/src/privileges.rs index 6006c07..039bda6 100644 --- a/crates/dav/src/privileges.rs +++ b/crates/dav/src/privileges.rs @@ -1,8 +1,7 @@ -use rustical_xml::XmlDeserialize; -use serde::Serialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::collections::HashSet; -#[derive(Debug, Clone, Serialize, XmlDeserialize, Eq, Hash, PartialEq)] +#[derive(Debug, Clone, XmlSerialize, XmlDeserialize, Eq, Hash, PartialEq)] pub enum UserPrivilege { Read, Write, @@ -14,37 +13,33 @@ pub enum UserPrivilege { All, } -impl Serialize for UserPrivilegeSet { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - #[derive(Serialize)] - #[serde(rename_all = "kebab-case")] - pub struct UserPrivilegeWrapper<'a> { - #[serde(rename = "$value")] - privilege: &'a UserPrivilege, - } - #[derive(Serialize)] - #[serde(rename_all = "kebab-case")] - pub struct FakeUserPrivilegeSet<'a> { - #[serde(rename = "privilege")] - privileges: Vec>, +impl XmlSerialize for UserPrivilegeSet { + fn serialize( + &self, + ns: Option<&[u8]>, + tag: Option<&[u8]>, + writer: &mut quick_xml::Writer, + ) -> std::io::Result<()> { + #[derive(XmlSerialize)] + pub struct FakeUserPrivilegeSet { + #[xml(rename = b"privilege", flatten)] + privileges: Vec, } + FakeUserPrivilegeSet { - privileges: self - .privileges - .iter() - .map(|privilege| UserPrivilegeWrapper { privilege }) - .collect(), + privileges: self.privileges.iter().cloned().collect(), } - .serialize(serializer) + .serialize(ns, tag, writer) + } + + #[allow(refining_impl_trait)] + fn attributes<'a>(&self) -> Option>> { + None } } -#[derive(Debug, Clone, XmlDeserialize, Default, PartialEq)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct UserPrivilegeSet { - #[xml(flatten)] privileges: HashSet, } diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index 9c4c6d3..f07f459 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -10,28 +10,22 @@ use actix_web::{http::StatusCode, ResponseError}; use itertools::Itertools; pub use resource_service::ResourceService; use rustical_store::auth::User; -use rustical_xml::XmlDeserialize; -use serde::Serialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::str::FromStr; use strum::{EnumString, VariantNames}; mod methods; mod resource_service; -pub trait ResourceProp: Serialize + XmlDeserialize {} -impl ResourceProp for T {} +pub trait ResourceProp: XmlSerialize + XmlDeserialize {} +impl ResourceProp for T {} pub trait ResourcePropName: FromStr + VariantNames {} impl ResourcePropName for T {} -pub trait ResourceType: Serialize + XmlDeserialize {} -impl ResourceType for T {} - -#[derive(XmlDeserialize, Serialize, PartialEq)] -#[serde(rename_all = "kebab-case")] +#[derive(XmlDeserialize, XmlSerialize, PartialEq)] pub enum CommonPropertiesProp { // WebDAV (RFC 2518) - #[serde(skip_deserializing)] #[xml(skip_deserializing)] Resourcetype(Resourcetype), @@ -39,15 +33,15 @@ pub enum CommonPropertiesProp { CurrentUserPrincipal(HrefElement), // WebDAV Access Control Protocol (RFC 3477) + #[xml(skip_deserializing)] CurrentUserPrivilegeSet(UserPrivilegeSet), Owner(Option), } -#[derive(Serialize)] +#[derive(XmlSerialize)] +#[xml(untagged)] pub enum EitherProp { - #[serde(untagged)] Left(Left), - #[serde(untagged)] Right(Right), } diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index ec369ed..cc221e5 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -4,7 +4,7 @@ use actix_web::dev::ResourceMap; use actix_web::HttpRequest; use async_trait::async_trait; use rustical_store::auth::User; -use rustical_xml::XmlDeserialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; use serde::Serialize; use std::any::type_name; use std::marker::PhantomData; @@ -23,7 +23,7 @@ impl Default for RootResource { #[strum(serialize_all = "kebab-case")] pub enum RootResourcePropName {} -#[derive(XmlDeserialize, Serialize, Clone, PartialEq)] +#[derive(XmlDeserialize, XmlSerialize, Serialize, Clone, PartialEq)] pub enum RootResourceProp {} impl From for RootResourcePropName { diff --git a/crates/dav/src/xml/mod.rs b/crates/dav/src/xml/mod.rs index df9f865..b8f61c7 100644 --- a/crates/dav/src/xml/mod.rs +++ b/crates/dav/src/xml/mod.rs @@ -6,11 +6,10 @@ use derive_more::derive::From; pub use multistatus::MultistatusElement; pub use propfind::{PropElement, PropfindElement, PropfindType, Propname}; pub use resourcetype::Resourcetype; -use rustical_xml::XmlDeserialize; -use serde::Serialize; +use rustical_xml::{XmlDeserialize, XmlSerialize}; pub use tag_list::TagList; -#[derive(XmlDeserialize, Debug, Clone, Serialize, From, PartialEq)] +#[derive(XmlDeserialize, XmlSerialize, Debug, Clone, From, PartialEq)] pub struct HrefElement { pub href: String, } diff --git a/crates/dav/src/xml/multistatus.rs b/crates/dav/src/xml/multistatus.rs index b78df9a..2ad00d3 100644 --- a/crates/dav/src/xml/multistatus.rs +++ b/crates/dav/src/xml/multistatus.rs @@ -4,33 +4,37 @@ use actix_web::{ http::{header::ContentType, StatusCode}, HttpRequest, HttpResponse, Responder, ResponseError, }; -use serde::{Serialize, Serializer}; +use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot}; // Intermediate struct because of a serde limitation, see following article: // https://stackoverflow.com/questions/78444158/unsupportedcannot-serialize-enum-newtype-variant-exampledata -#[derive(Serialize)] -pub struct PropTagWrapper { - #[serde(rename = "$value")] +#[derive(XmlSerialize)] +pub struct PropTagWrapper { + #[xml(flatten, ty = "untagged")] pub prop: Vec, } // RFC 2518 // -#[derive(Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct PropstatElement { +#[derive(XmlSerialize)] +pub struct PropstatElement { pub prop: PropType, - #[serde(serialize_with = "serialize_status")] + #[xml(serialize_with = "xml_serialize_status")] pub status: StatusCode, } -fn serialize_status(status: &StatusCode, serializer: S) -> Result { - format!("HTTP/1.1 {}", status).serialize(serializer) +fn xml_serialize_status( + status: &StatusCode, + ns: Option<&[u8]>, + tag: Option<&[u8]>, + writer: &mut quick_xml::Writer, +) -> std::io::Result<()> { + XmlSerialize::serialize(&format!("HTTP/1.1 {}", status), ns, tag, writer) } -#[derive(Serialize)] -#[serde(untagged)] -pub enum PropstatWrapper { +#[derive(XmlSerialize)] +#[xml(untagged)] +pub enum PropstatWrapper { Normal(PropstatElement>), TagList(PropstatElement), } @@ -38,26 +42,30 @@ pub enum PropstatWrapper { // RFC 2518 // -#[derive(Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct ResponseElement { +#[derive(XmlSerialize)] +pub struct ResponseElement { pub href: String, - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(serialize_with = "serialize_optional_status")] + #[xml(serialize_with = "xml_serialize_optional_status")] pub status: Option, + #[xml(flatten)] pub propstat: Vec>, } -fn serialize_optional_status( - status_option: &Option, - serializer: S, -) -> Result { - status_option - .map(|status| format!("HTTP/1.1 {}", status)) - .serialize(serializer) +fn xml_serialize_optional_status( + val: &Option, + ns: Option<&[u8]>, + tag: Option<&[u8]>, + writer: &mut quick_xml::Writer, +) -> std::io::Result<()> { + XmlSerialize::serialize( + &val.map(|status| format!("HTTP/1.1 {}", status)), + ns, + tag, + writer, + ) } -impl Default for ResponseElement { +impl Default for ResponseElement { fn default() -> Self { Self { href: String::new(), @@ -70,30 +78,24 @@ impl Default for ResponseElement { // RFC 2518 // // Extended by sync-token as specified in RFC 6578 -#[derive(Serialize)] -#[serde(rename = "multistatus", rename_all = "kebab-case")] -pub struct MultistatusElement { - #[serde(rename = "response")] +#[derive(XmlSerialize, XmlRootTag)] +#[xml(root = b"multistatus")] +pub struct MultistatusElement { + #[xml(rename = b"response", flatten)] pub responses: Vec>, - #[serde(rename = "response")] + #[xml(rename = b"response", flatten)] pub member_responses: Vec>, - #[serde(rename = "@xmlns")] + // TODO: napespaces pub ns_dav: &'static str, - #[serde(rename = "@xmlns:P")] pub ns_davpush: &'static str, - #[serde(rename = "@xmlns:C")] pub ns_caldav: &'static str, - #[serde(rename = "@xmlns:IC")] pub ns_ical: &'static str, - #[serde(rename = "@xmlns:CS")] pub ns_calendarserver: &'static str, - #[serde(rename = "@xmlns:CARD")] pub ns_carddav: &'static str, - #[serde(skip_serializing_if = "Option::is_none")] pub sync_token: Option, } -impl Default for MultistatusElement { +impl Default for MultistatusElement { fn default() -> Self { Self { responses: vec![], @@ -109,19 +111,18 @@ impl Default for MultistatusElement { } } -impl Responder for MultistatusElement { +impl Responder for MultistatusElement { type Body = BoxBody; fn respond_to(self, _req: &HttpRequest) -> HttpResponse { - let mut output = "\n".to_owned(); - let mut ser = quick_xml::se::Serializer::new(&mut output); - ser.indent(' ', 4); - if let Err(err) = self.serialize(ser) { + let mut output: Vec<_> = b"\n".into(); + let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4); + if let Err(err) = self.serialize_root(&mut writer) { return crate::Error::from(err).error_response(); } HttpResponse::MultiStatus() .content_type(ContentType::xml()) - .body(output) + .body(String::from_utf8(output).unwrap()) } } diff --git a/crates/dav/src/xml/resourcetype.rs b/crates/dav/src/xml/resourcetype.rs index ab9d60c..05573f0 100644 --- a/crates/dav/src/xml/resourcetype.rs +++ b/crates/dav/src/xml/resourcetype.rs @@ -1,18 +1,62 @@ -use serde::ser::SerializeMap; -use serde::Serialize; +use quick_xml::events::attributes::Attribute; +use quick_xml::events::{BytesEnd, BytesStart, Event}; +use rustical_xml::XmlSerialize; #[derive(Debug, Clone, PartialEq)] pub struct Resourcetype(pub &'static [&'static str]); -impl Serialize for Resourcetype { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut map = serializer.serialize_map(Some(self.0.len()))?; - for entry in self.0 { - map.serialize_entry(entry, &())?; +impl XmlSerialize for Resourcetype { + fn serialize( + &self, + ns: Option<&[u8]>, + tag: Option<&[u8]>, + writer: &mut quick_xml::Writer, + ) -> std::io::Result<()> { + let tag_str = tag.map(String::from_utf8_lossy); + if let Some(tag) = &tag_str { + writer.write_event(Event::Start(BytesStart::new(tag.to_owned())))?; } - map.end() + + for &ty in self.0 { + writer.write_event(Event::Empty(BytesStart::new(ty)))?; + } + + if let Some(tag) = &tag_str { + writer.write_event(Event::End(BytesEnd::new(tag.to_owned())))?; + } + Ok(()) + } + + #[allow(refining_impl_trait)] + fn attributes<'a>(&self) -> Option>> { + None + } +} + +#[cfg(test)] +mod tests { + use super::Resourcetype; + use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot}; + + #[derive(XmlSerialize, XmlRootTag)] + #[xml(root = b"document")] + struct Document { + resourcetype: Resourcetype, + } + + #[test] + fn test_serialize_resourcetype() { + let mut buf = Vec::new(); + let mut writer = quick_xml::Writer::new(&mut buf); + Document { + resourcetype: Resourcetype(&["collection", "hello"]), + } + .serialize_root(&mut writer) + .unwrap(); + let out = String::from_utf8(buf).unwrap(); + assert_eq!( + out, + "" + ) } } diff --git a/crates/dav/src/xml/tag_list.rs b/crates/dav/src/xml/tag_list.rs index ecfd06a..f48dd4a 100644 --- a/crates/dav/src/xml/tag_list.rs +++ b/crates/dav/src/xml/tag_list.rs @@ -1,20 +1,36 @@ use derive_more::derive::From; -use serde::ser::SerializeMap; -use serde::Serialize; +use rustical_xml::XmlSerialize; #[derive(Clone, Debug, PartialEq, From)] pub struct TagList(Vec); -impl Serialize for TagList { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut map = serializer.serialize_map(Some(self.0.len()))?; - for name in &self.0 { - map.serialize_entry(&name, &())?; +impl XmlSerialize for TagList { + fn serialize( + &self, + ns: Option<&[u8]>, + tag: Option<&[u8]>, + writer: &mut quick_xml::Writer, + ) -> std::io::Result<()> { + #[derive(Debug, XmlSerialize, PartialEq)] + struct Inner { + #[xml(ty = "untagged", flatten)] + tags: Vec, } - map.end() + + #[derive(Debug, XmlSerialize, PartialEq)] + struct Tag { + #[xml(ty = "tag_name")] + name: String, + } + Inner { + tags: self.0.iter().map(|t| Tag { name: t.to_owned() }).collect(), + } + .serialize(ns, tag, writer) + } + + #[allow(refining_impl_trait)] + fn attributes<'a>(&self) -> Option>> { + None } }