save progress: Move from serde::Serialize to XmlSerialize

This commit is contained in:
Lennart
2024-12-28 12:47:33 +01:00
parent 759bb1f1be
commit c16a5214bc
16 changed files with 230 additions and 200 deletions

View File

@@ -1,25 +1,30 @@
use rustical_xml::XmlSerialize;
use serde::Serialize; use serde::Serialize;
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct SupportedCalendarComponent { pub struct SupportedCalendarComponent {
#[serde(rename = "@name")] #[serde(rename = "@name")]
#[xml(ty = "attr")]
pub name: String, pub name: String,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct SupportedCalendarComponentSet { pub struct SupportedCalendarComponentSet {
#[serde(rename = "C:comp")] #[serde(rename = "C:comp")]
#[xml(flatten)]
pub comp: Vec<SupportedCalendarComponent>, pub comp: Vec<SupportedCalendarComponent>,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct CalendarData { pub struct CalendarData {
#[serde(rename = "@content-type")] #[serde(rename = "@content-type")]
#[xml(ty = "attr")]
content_type: String, content_type: String,
#[serde(rename = "@version")] #[serde(rename = "@version")]
#[xml(ty = "attr")]
version: String, 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")] #[serde(rename_all = "kebab-case")]
pub struct SupportedCalendarData { pub struct SupportedCalendarData {
#[serde(rename = "C:calendar-data", alias = "calendar-data")] #[serde(rename = "C:calendar-data", alias = "calendar-data")]
calendar_data: CalendarData, calendar_data: CalendarData,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum ReportMethod { pub enum ReportMethod {
CalendarQuery, CalendarQuery,
@@ -47,14 +52,15 @@ pub enum ReportMethod {
SyncCollection, SyncCollection,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct ReportWrapper { pub struct ReportWrapper {
#[serde(rename = "$value")] #[serde(rename = "$value")]
#[xml(ty = "untagged")]
report: ReportMethod, report: ReportMethod,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct SupportedReportWrapper { pub struct SupportedReportWrapper {
report: ReportWrapper, report: ReportWrapper,
@@ -69,9 +75,10 @@ impl From<ReportMethod> for SupportedReportWrapper {
} }
// RFC 3253 section-3.1.5 // RFC 3253 section-3.1.5
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct SupportedReportSet { pub struct SupportedReportSet {
#[xml(flatten)]
supported_report: Vec<SupportedReportWrapper>, supported_report: Vec<SupportedReportWrapper>,
} }
@@ -87,27 +94,29 @@ impl Default for SupportedReportSet {
} }
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum Transport { pub enum Transport {
#[serde(rename = "P:web-push")] #[serde(rename = "P:web-push")]
WebPush, WebPush,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct TransportWrapper { pub struct TransportWrapper {
#[serde(rename = "$value")] #[serde(rename = "$value")]
#[xml(ty = "untagged")]
transport: Transport, transport: Transport,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Transports { pub struct Transports {
// NOTE: Here we implement an older version of the spec since the new property name is not reflected // NOTE: Here we implement an older version of the spec since the new property name is not reflected
// in DAVx5 yet // in DAVx5 yet
// https://github.com/bitfireAT/webdav-push/commit/461259a2f2174454b2b00033419b11fac52b79e3 // https://github.com/bitfireAT/webdav-push/commit/461259a2f2174454b2b00033419b11fac52b79e3
#[serde(rename = "P:transport")] #[serde(rename = "P:transport")]
#[xml(flatten)]
transports: Vec<TransportWrapper>, transports: Vec<TransportWrapper>,
} }

View File

@@ -19,8 +19,7 @@ use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::HrefElement; use rustical_dav::xml::HrefElement;
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::{Calendar, CalendarStore}; use rustical_store::{Calendar, CalendarStore};
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
@@ -32,13 +31,12 @@ pub struct CalendarResourceService<C: CalendarStore + ?Sized> {
calendar_id: String, calendar_id: String,
} }
#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)]
#[strum_discriminants( #[strum_discriminants(
name(CalendarPropName), name(CalendarPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, VariantNames, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
#[serde(rename_all = "kebab-case")]
pub enum CalendarProp { pub enum CalendarProp {
// WebDAV (RFC 2518) // WebDAV (RFC 2518)
Displayname(Option<String>), Displayname(Option<String>),
@@ -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 // NOTE: Here we implement an older version of the spec since the new property name is not reflected
// in DAVx5 yet // in DAVx5 yet
// https://github.com/bitfireAT/webdav-push/commit/461259a2f2174454b2b00033419b11fac52b79e3 // https://github.com/bitfireAT/webdav-push/commit/461259a2f2174454b2b00033419b11fac52b79e3
#[serde(skip_deserializing)]
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
#[serde(rename = "P:push-transports", alias = "push-transports")] // #[serde(rename = "P:push-transports", alias = "push-transports")]
Transports(Transports), Transports(Transports),
Topic(String), Topic(String),
// CalDAV (RFC 4791) // CalDAV (RFC 4791)
#[serde(rename = "IC:calendar-color", alias = "calendar-color")] // #[serde(rename = "IC:calendar-color", alias = "calendar-color")]
CalendarColor(Option<String>), CalendarColor(Option<String>),
#[serde(rename = "C:calendar-description", alias = "calendar-description")] // #[serde(rename = "C:calendar-description", alias = "calendar-description")]
CalendarDescription(Option<String>), CalendarDescription(Option<String>),
#[serde(rename = "C:calendar-timezone", alias = "calendar-timezone")] // #[serde(rename = "C:calendar-timezone", alias = "calendar-timezone")]
CalendarTimezone(Option<String>), CalendarTimezone(Option<String>),
#[serde(rename = "IC:calendar-order", alias = "calendar-order")] // #[serde(rename = "IC:calendar-order", alias = "calendar-order")]
CalendarOrder(Option<i64>), CalendarOrder(Option<i64>),
#[serde( // #[serde(rename = "C:supported-calendar-component-set")]
rename = "C:supported-calendar-component-set",
alias = "supported-calendar-component-set"
)]
// TODO: Re-add // TODO: Re-add
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
SupportedCalendarComponentSet(SupportedCalendarComponentSet), SupportedCalendarComponentSet(SupportedCalendarComponentSet),
#[serde( // #[serde(rename = "C:supported-calendar-data")]
rename = "C:supported-calendar-data",
alias = "supported-calendar-data"
)]
#[serde(skip_deserializing)]
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
SupportedCalendarData(SupportedCalendarData), SupportedCalendarData(SupportedCalendarData),
MaxResourceSize(i64), MaxResourceSize(i64),
#[serde(skip_deserializing)]
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
SupportedReportSet(SupportedReportSet), SupportedReportSet(SupportedReportSet),
@@ -86,9 +75,9 @@ pub enum CalendarProp {
SyncToken(String), SyncToken(String),
// CalendarServer // CalendarServer
#[serde(rename = "CS:getctag", alias = "getctag")] // #[serde(rename = "CS:getctag", alias = "getctag")]
Getctag(String), Getctag(String),
#[serde(rename = "CS:source", alias = "source")] // #[serde(rename = "CS:source", alias = "source")]
Source(Option<HrefElement>), Source(Option<HrefElement>),
} }

View File

@@ -8,8 +8,8 @@ use rustical_dav::{
resource::{Resource, ResourceService}, resource::{Resource, ResourceService},
}; };
use rustical_store::{auth::User, CalendarObject, CalendarStore}; use rustical_store::{auth::User, CalendarObject, CalendarStore};
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames};
@@ -20,20 +20,19 @@ pub struct CalendarObjectResourceService<C: CalendarStore + ?Sized> {
object_id: String, object_id: String,
} }
#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)]
#[strum_discriminants( #[strum_discriminants(
name(CalendarObjectPropName), name(CalendarObjectPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, VariantNames, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
#[serde(rename_all = "kebab-case")]
pub enum CalendarObjectProp { pub enum CalendarObjectProp {
// WebDAV (RFC 2518) // WebDAV (RFC 2518)
Getetag(String), Getetag(String),
Getcontenttype(String), Getcontenttype(String),
// CalDAV (RFC 4791) // CalDAV (RFC 4791)
#[serde(rename = "C:calendar-data")] // #[serde(rename = "C:calendar-data")]
CalendarData(String), CalendarData(String),
} }

View File

@@ -9,8 +9,7 @@ use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::HrefElement; use rustical_dav::xml::HrefElement;
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::CalendarStore; use rustical_store::CalendarStore;
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames};
@@ -24,23 +23,21 @@ pub struct PrincipalResource {
principal: String, principal: String,
} }
#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)]
#[strum_discriminants( #[strum_discriminants(
name(PrincipalPropName), name(PrincipalPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, VariantNames, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
#[serde(rename_all = "kebab-case")]
pub enum PrincipalProp { pub enum PrincipalProp {
// WebDAV Access Control (RFC 3744) // WebDAV Access Control (RFC 3744)
#[serde(rename = "principal-URL")]
#[strum_discriminants(strum(serialize = "principal-URL"))] #[strum_discriminants(strum(serialize = "principal-URL"))]
PrincipalUrl(HrefElement), PrincipalUrl(HrefElement),
// CalDAV (RFC 4791) // CalDAV (RFC 4791)
#[serde(rename = "C:calendar-home-set")] // #[serde(rename = "C:calendar-home-set")]
CalendarHomeSet(HrefElement), CalendarHomeSet(HrefElement),
#[serde(rename = "C:calendar-user-address-set")] // #[serde(rename = "C:calendar-user-address-set")]
CalendarUserAddressSet(HrefElement), CalendarUserAddressSet(HrefElement),
} }

View File

@@ -7,8 +7,8 @@ use rustical_dav::{
resource::{Resource, ResourceService}, resource::{Resource, ResourceService},
}; };
use rustical_store::{auth::User, AddressObject, AddressbookStore}; use rustical_store::{auth::User, AddressObject, AddressbookStore};
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames};
@@ -21,20 +21,19 @@ pub struct AddressObjectResourceService<AS: AddressbookStore + ?Sized> {
object_id: String, object_id: String,
} }
#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)]
#[strum_discriminants( #[strum_discriminants(
name(AddressObjectPropName), name(AddressObjectPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, VariantNames, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
#[serde(rename_all = "kebab-case")]
pub enum AddressObjectProp { pub enum AddressObjectProp {
// WebDAV (RFC 2518) // WebDAV (RFC 2518)
Getetag(String), Getetag(String),
Getcontenttype(String), Getcontenttype(String),
// CalDAV (RFC 4791) // CalDAV (RFC 4791)
#[serde(rename = "CARD:address-data")] #[xml(ns = b"urn:ietf:params:xml:ns:carddav")]
AddressData(String), AddressData(String),
} }

View File

@@ -1,18 +1,17 @@
use serde::Serialize; use rustical_xml::XmlSerialize;
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct AddressDataType { pub struct AddressDataType {
#[serde(rename = "@content-type")] #[xml(ty = "attr")]
pub content_type: String, pub content_type: String,
#[serde(rename = "@version")] #[xml(ty = "attr")]
pub version: String, pub version: String,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct SupportedAddressData { 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<AddressDataType>, address_data_type: Vec<AddressDataType>,
} }
@@ -33,22 +32,19 @@ impl Default for SupportedAddressData {
} }
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum ReportMethod { pub enum ReportMethod {
AddressbookMultiget, AddressbookMultiget,
SyncCollection, SyncCollection,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct ReportWrapper { pub struct ReportWrapper {
#[serde(rename = "$value")] #[xml(ty = "untagged")]
report: ReportMethod, report: ReportMethod,
} }
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct SupportedReportWrapper { pub struct SupportedReportWrapper {
report: ReportWrapper, report: ReportWrapper,
} }
@@ -62,9 +58,9 @@ impl From<ReportMethod> for SupportedReportWrapper {
} }
// RFC 3253 section-3.1.5 // RFC 3253 section-3.1.5
#[derive(Debug, Clone, Serialize, PartialEq)] #[derive(Debug, Clone, XmlSerialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct SupportedReportSet { pub struct SupportedReportSet {
#[xml(flatten)]
supported_report: Vec<SupportedReportWrapper>, supported_report: Vec<SupportedReportWrapper>,
} }

View File

@@ -14,8 +14,7 @@ use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::resource::{Resource, ResourceService};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::{Addressbook, AddressbookStore}; use rustical_store::{Addressbook, AddressbookStore};
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames};
@@ -26,8 +25,7 @@ pub struct AddressbookResourceService<AS: AddressbookStore + ?Sized> {
addressbook_id: String, addressbook_id: String,
} }
#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)]
#[serde(rename_all = "kebab-case")]
#[strum_discriminants( #[strum_discriminants(
name(AddressbookPropName), name(AddressbookPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, VariantNames, IntoStaticStr),
@@ -39,19 +37,11 @@ pub enum AddressbookProp {
Getcontenttype(String), Getcontenttype(String),
// CardDAV (RFC 6352) // CardDAV (RFC 6352)
#[serde( #[xml(ns = b"urn:ietf:params:xml:ns:carddav")]
rename = "CARD:addressbook-description",
alias = "addressbook-description"
)]
AddressbookDescription(Option<String>), AddressbookDescription(Option<String>),
#[serde(
rename = "CARD:supported-address-data",
alias = "supported-address-data"
)]
#[serde(skip_deserializing)]
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
#[xml(ns = b"urn:ietf:params:xml:ns:carddav")]
SupportedAddressData(SupportedAddressData), SupportedAddressData(SupportedAddressData),
#[serde(skip_deserializing)]
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
SupportedReportSet(SupportedReportSet), SupportedReportSet(SupportedReportSet),
MaxResourceSize(i64), MaxResourceSize(i64),

View File

@@ -9,8 +9,7 @@ use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::HrefElement; use rustical_dav::xml::HrefElement;
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::AddressbookStore; use rustical_store::AddressbookStore;
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames};
@@ -24,23 +23,22 @@ pub struct PrincipalResource {
principal: String, principal: String,
} }
#[derive(XmlDeserialize, Serialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)]
#[strum_discriminants( #[strum_discriminants(
name(PrincipalPropName), name(PrincipalPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, VariantNames, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
#[serde(rename_all = "kebab-case")]
pub enum PrincipalProp { pub enum PrincipalProp {
// WebDAV Access Control (RFC 3744) // WebDAV Access Control (RFC 3744)
#[serde(rename = "principal-URL")] #[xml(rename = b"principal-URL")]
#[strum_discriminants(strum(serialize = "principal-URL"))] #[strum_discriminants(strum(serialize = "principal-URL"))]
PrincipalUrl(HrefElement), PrincipalUrl(HrefElement),
// CardDAV (RFC 6352) // CardDAV (RFC 6352)
#[serde(rename = "CARD:addressbook-home-set")] #[xml(ns = b"urn:ietf:params:xml:ns:carddav")]
AddressbookHomeSet(HrefElement), AddressbookHomeSet(HrefElement),
#[serde(rename = "CARD:principal-address")] #[xml(ns = b"urn:ietf:params:xml:ns:carddav")]
PrincipalAddress(Option<HrefElement>), PrincipalAddress(Option<HrefElement>),
} }

View File

@@ -24,6 +24,9 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
XmlSerializationError(#[from] quick_xml::SeError), XmlSerializationError(#[from] quick_xml::SeError),
#[error(transparent)]
IOError(#[from] std::io::Error),
} }
impl actix_web::error::ResponseError for 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::XmlDeserializationError(_) => StatusCode::BAD_REQUEST,
Self::XmlSerializationError(_) => StatusCode::BAD_REQUEST, Self::XmlSerializationError(_) => StatusCode::BAD_REQUEST,
Error::PropReadOnly => StatusCode::CONFLICT, Error::PropReadOnly => StatusCode::CONFLICT,
Self::IOError(_) => StatusCode::INTERNAL_SERVER_ERROR,
} }
} }

View File

@@ -1,8 +1,7 @@
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize;
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Debug, Clone, Serialize, XmlDeserialize, Eq, Hash, PartialEq)] #[derive(Debug, Clone, XmlSerialize, XmlDeserialize, Eq, Hash, PartialEq)]
pub enum UserPrivilege { pub enum UserPrivilege {
Read, Read,
Write, Write,
@@ -14,37 +13,33 @@ pub enum UserPrivilege {
All, All,
} }
impl Serialize for UserPrivilegeSet { impl XmlSerialize for UserPrivilegeSet {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<W: std::io::Write>(
where &self,
S: serde::Serializer, ns: Option<&[u8]>,
{ tag: Option<&[u8]>,
#[derive(Serialize)] writer: &mut quick_xml::Writer<W>,
#[serde(rename_all = "kebab-case")] ) -> std::io::Result<()> {
pub struct UserPrivilegeWrapper<'a> { #[derive(XmlSerialize)]
#[serde(rename = "$value")] pub struct FakeUserPrivilegeSet {
privilege: &'a UserPrivilege, #[xml(rename = b"privilege", flatten)]
} privileges: Vec<UserPrivilege>,
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct FakeUserPrivilegeSet<'a> {
#[serde(rename = "privilege")]
privileges: Vec<UserPrivilegeWrapper<'a>>,
} }
FakeUserPrivilegeSet { FakeUserPrivilegeSet {
privileges: self privileges: self.privileges.iter().cloned().collect(),
.privileges
.iter()
.map(|privilege| UserPrivilegeWrapper { privilege })
.collect(),
} }
.serialize(serializer) .serialize(ns, tag, writer)
}
#[allow(refining_impl_trait)]
fn attributes<'a>(&self) -> Option<Vec<quick_xml::events::attributes::Attribute<'a>>> {
None
} }
} }
#[derive(Debug, Clone, XmlDeserialize, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
pub struct UserPrivilegeSet { pub struct UserPrivilegeSet {
#[xml(flatten)]
privileges: HashSet<UserPrivilege>, privileges: HashSet<UserPrivilege>,
} }

View File

@@ -10,28 +10,22 @@ use actix_web::{http::StatusCode, ResponseError};
use itertools::Itertools; use itertools::Itertools;
pub use resource_service::ResourceService; pub use resource_service::ResourceService;
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize;
use std::str::FromStr; use std::str::FromStr;
use strum::{EnumString, VariantNames}; use strum::{EnumString, VariantNames};
mod methods; mod methods;
mod resource_service; mod resource_service;
pub trait ResourceProp: Serialize + XmlDeserialize {} pub trait ResourceProp: XmlSerialize + XmlDeserialize {}
impl<T: Serialize + XmlDeserialize> ResourceProp for T {} impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {}
pub trait ResourcePropName: FromStr + VariantNames {} pub trait ResourcePropName: FromStr + VariantNames {}
impl<T: FromStr + VariantNames> ResourcePropName for T {} impl<T: FromStr + VariantNames> ResourcePropName for T {}
pub trait ResourceType: Serialize + XmlDeserialize {} #[derive(XmlDeserialize, XmlSerialize, PartialEq)]
impl<T: Serialize + XmlDeserialize> ResourceType for T {}
#[derive(XmlDeserialize, Serialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum CommonPropertiesProp { pub enum CommonPropertiesProp {
// WebDAV (RFC 2518) // WebDAV (RFC 2518)
#[serde(skip_deserializing)]
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
Resourcetype(Resourcetype), Resourcetype(Resourcetype),
@@ -39,15 +33,15 @@ pub enum CommonPropertiesProp {
CurrentUserPrincipal(HrefElement), CurrentUserPrincipal(HrefElement),
// WebDAV Access Control Protocol (RFC 3477) // WebDAV Access Control Protocol (RFC 3477)
#[xml(skip_deserializing)]
CurrentUserPrivilegeSet(UserPrivilegeSet), CurrentUserPrivilegeSet(UserPrivilegeSet),
Owner(Option<HrefElement>), Owner(Option<HrefElement>),
} }
#[derive(Serialize)] #[derive(XmlSerialize)]
#[xml(untagged)]
pub enum EitherProp<Left: ResourceProp, Right: ResourceProp> { pub enum EitherProp<Left: ResourceProp, Right: ResourceProp> {
#[serde(untagged)]
Left(Left), Left(Left),
#[serde(untagged)]
Right(Right), Right(Right),
} }

View File

@@ -4,7 +4,7 @@ use actix_web::dev::ResourceMap;
use actix_web::HttpRequest; use actix_web::HttpRequest;
use async_trait::async_trait; use async_trait::async_trait;
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize; use serde::Serialize;
use std::any::type_name; use std::any::type_name;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -23,7 +23,7 @@ impl<PR: Resource> Default for RootResource<PR> {
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]
pub enum RootResourcePropName {} pub enum RootResourcePropName {}
#[derive(XmlDeserialize, Serialize, Clone, PartialEq)] #[derive(XmlDeserialize, XmlSerialize, Serialize, Clone, PartialEq)]
pub enum RootResourceProp {} pub enum RootResourceProp {}
impl From<RootResourceProp> for RootResourcePropName { impl From<RootResourceProp> for RootResourcePropName {

View File

@@ -6,11 +6,10 @@ use derive_more::derive::From;
pub use multistatus::MultistatusElement; pub use multistatus::MultistatusElement;
pub use propfind::{PropElement, PropfindElement, PropfindType, Propname}; pub use propfind::{PropElement, PropfindElement, PropfindType, Propname};
pub use resourcetype::Resourcetype; pub use resourcetype::Resourcetype;
use rustical_xml::XmlDeserialize; use rustical_xml::{XmlDeserialize, XmlSerialize};
use serde::Serialize;
pub use tag_list::TagList; pub use tag_list::TagList;
#[derive(XmlDeserialize, Debug, Clone, Serialize, From, PartialEq)] #[derive(XmlDeserialize, XmlSerialize, Debug, Clone, From, PartialEq)]
pub struct HrefElement { pub struct HrefElement {
pub href: String, pub href: String,
} }

View File

@@ -4,33 +4,37 @@ use actix_web::{
http::{header::ContentType, StatusCode}, http::{header::ContentType, StatusCode},
HttpRequest, HttpResponse, Responder, ResponseError, HttpRequest, HttpResponse, Responder, ResponseError,
}; };
use serde::{Serialize, Serializer}; use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
// Intermediate struct because of a serde limitation, see following article: // Intermediate struct because of a serde limitation, see following article:
// https://stackoverflow.com/questions/78444158/unsupportedcannot-serialize-enum-newtype-variant-exampledata // https://stackoverflow.com/questions/78444158/unsupportedcannot-serialize-enum-newtype-variant-exampledata
#[derive(Serialize)] #[derive(XmlSerialize)]
pub struct PropTagWrapper<T: Serialize> { pub struct PropTagWrapper<T: XmlSerialize> {
#[serde(rename = "$value")] #[xml(flatten, ty = "untagged")]
pub prop: Vec<T>, pub prop: Vec<T>,
} }
// RFC 2518 // RFC 2518
// <!ELEMENT propstat (prop, status, responsedescription?) > // <!ELEMENT propstat (prop, status, responsedescription?) >
#[derive(Serialize)] #[derive(XmlSerialize)]
#[serde(rename_all = "kebab-case")] pub struct PropstatElement<PropType: XmlSerialize> {
pub struct PropstatElement<PropType: Serialize> {
pub prop: PropType, pub prop: PropType,
#[serde(serialize_with = "serialize_status")] #[xml(serialize_with = "xml_serialize_status")]
pub status: StatusCode, pub status: StatusCode,
} }
fn serialize_status<S: Serializer>(status: &StatusCode, serializer: S) -> Result<S::Ok, S::Error> { fn xml_serialize_status<W: ::std::io::Write>(
format!("HTTP/1.1 {}", status).serialize(serializer) status: &StatusCode,
ns: Option<&[u8]>,
tag: Option<&[u8]>,
writer: &mut quick_xml::Writer<W>,
) -> std::io::Result<()> {
XmlSerialize::serialize(&format!("HTTP/1.1 {}", status), ns, tag, writer)
} }
#[derive(Serialize)] #[derive(XmlSerialize)]
#[serde(untagged)] #[xml(untagged)]
pub enum PropstatWrapper<T: Serialize> { pub enum PropstatWrapper<T: XmlSerialize> {
Normal(PropstatElement<PropTagWrapper<T>>), Normal(PropstatElement<PropTagWrapper<T>>),
TagList(PropstatElement<TagList>), TagList(PropstatElement<TagList>),
} }
@@ -38,26 +42,30 @@ pub enum PropstatWrapper<T: Serialize> {
// RFC 2518 // RFC 2518
// <!ELEMENT response (href, ((href*, status)|(propstat+)), // <!ELEMENT response (href, ((href*, status)|(propstat+)),
// responsedescription?) > // responsedescription?) >
#[derive(Serialize)] #[derive(XmlSerialize)]
#[serde(rename_all = "kebab-case")] pub struct ResponseElement<PropstatType: XmlSerialize> {
pub struct ResponseElement<PropstatType: Serialize> {
pub href: String, pub href: String,
#[serde(skip_serializing_if = "Option::is_none")] #[xml(serialize_with = "xml_serialize_optional_status")]
#[serde(serialize_with = "serialize_optional_status")]
pub status: Option<StatusCode>, pub status: Option<StatusCode>,
#[xml(flatten)]
pub propstat: Vec<PropstatWrapper<PropstatType>>, pub propstat: Vec<PropstatWrapper<PropstatType>>,
} }
fn serialize_optional_status<S: Serializer>( fn xml_serialize_optional_status<W: ::std::io::Write>(
status_option: &Option<StatusCode>, val: &Option<StatusCode>,
serializer: S, ns: Option<&[u8]>,
) -> Result<S::Ok, S::Error> { tag: Option<&[u8]>,
status_option writer: &mut quick_xml::Writer<W>,
.map(|status| format!("HTTP/1.1 {}", status)) ) -> std::io::Result<()> {
.serialize(serializer) XmlSerialize::serialize(
&val.map(|status| format!("HTTP/1.1 {}", status)),
ns,
tag,
writer,
)
} }
impl<PT: Serialize> Default for ResponseElement<PT> { impl<PT: XmlSerialize> Default for ResponseElement<PT> {
fn default() -> Self { fn default() -> Self {
Self { Self {
href: String::new(), href: String::new(),
@@ -70,30 +78,24 @@ impl<PT: Serialize> Default for ResponseElement<PT> {
// RFC 2518 // RFC 2518
// <!ELEMENT multistatus (response+, responsedescription?) > // <!ELEMENT multistatus (response+, responsedescription?) >
// Extended by sync-token as specified in RFC 6578 // Extended by sync-token as specified in RFC 6578
#[derive(Serialize)] #[derive(XmlSerialize, XmlRootTag)]
#[serde(rename = "multistatus", rename_all = "kebab-case")] #[xml(root = b"multistatus")]
pub struct MultistatusElement<PropType: Serialize, MemberPropType: Serialize> { pub struct MultistatusElement<PropType: XmlSerialize, MemberPropType: XmlSerialize> {
#[serde(rename = "response")] #[xml(rename = b"response", flatten)]
pub responses: Vec<ResponseElement<PropType>>, pub responses: Vec<ResponseElement<PropType>>,
#[serde(rename = "response")] #[xml(rename = b"response", flatten)]
pub member_responses: Vec<ResponseElement<MemberPropType>>, pub member_responses: Vec<ResponseElement<MemberPropType>>,
#[serde(rename = "@xmlns")] // TODO: napespaces
pub ns_dav: &'static str, pub ns_dav: &'static str,
#[serde(rename = "@xmlns:P")]
pub ns_davpush: &'static str, pub ns_davpush: &'static str,
#[serde(rename = "@xmlns:C")]
pub ns_caldav: &'static str, pub ns_caldav: &'static str,
#[serde(rename = "@xmlns:IC")]
pub ns_ical: &'static str, pub ns_ical: &'static str,
#[serde(rename = "@xmlns:CS")]
pub ns_calendarserver: &'static str, pub ns_calendarserver: &'static str,
#[serde(rename = "@xmlns:CARD")]
pub ns_carddav: &'static str, pub ns_carddav: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub sync_token: Option<String>, pub sync_token: Option<String>,
} }
impl<T1: Serialize, T2: Serialize> Default for MultistatusElement<T1, T2> { impl<T1: XmlSerialize, T2: XmlSerialize> Default for MultistatusElement<T1, T2> {
fn default() -> Self { fn default() -> Self {
Self { Self {
responses: vec![], responses: vec![],
@@ -109,19 +111,18 @@ impl<T1: Serialize, T2: Serialize> Default for MultistatusElement<T1, T2> {
} }
} }
impl<T1: Serialize, T2: Serialize> Responder for MultistatusElement<T1, T2> { impl<T1: XmlSerialize, T2: XmlSerialize> Responder for MultistatusElement<T1, T2> {
type Body = BoxBody; type Body = BoxBody;
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> { fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
let mut output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".to_owned(); let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
let mut ser = quick_xml::se::Serializer::new(&mut output); let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
ser.indent(' ', 4); if let Err(err) = self.serialize_root(&mut writer) {
if let Err(err) = self.serialize(ser) {
return crate::Error::from(err).error_response(); return crate::Error::from(err).error_response();
} }
HttpResponse::MultiStatus() HttpResponse::MultiStatus()
.content_type(ContentType::xml()) .content_type(ContentType::xml())
.body(output) .body(String::from_utf8(output).unwrap())
} }
} }

View File

@@ -1,18 +1,62 @@
use serde::ser::SerializeMap; use quick_xml::events::attributes::Attribute;
use serde::Serialize; use quick_xml::events::{BytesEnd, BytesStart, Event};
use rustical_xml::XmlSerialize;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Resourcetype(pub &'static [&'static str]); pub struct Resourcetype(pub &'static [&'static str]);
impl Serialize for Resourcetype { impl XmlSerialize for Resourcetype {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<W: std::io::Write>(
where &self,
S: serde::Serializer, ns: Option<&[u8]>,
{ tag: Option<&[u8]>,
let mut map = serializer.serialize_map(Some(self.0.len()))?; writer: &mut quick_xml::Writer<W>,
for entry in self.0 { ) -> std::io::Result<()> {
map.serialize_entry(entry, &())?; 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<Vec<Attribute<'a>>> {
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,
"<document><resourcetype><collection/><hello/></resourcetype></document>"
)
} }
} }

View File

@@ -1,20 +1,36 @@
use derive_more::derive::From; use derive_more::derive::From;
use serde::ser::SerializeMap; use rustical_xml::XmlSerialize;
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, From)] #[derive(Clone, Debug, PartialEq, From)]
pub struct TagList(Vec<String>); pub struct TagList(Vec<String>);
impl Serialize for TagList { impl XmlSerialize for TagList {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<W: std::io::Write>(
where &self,
S: serde::Serializer, ns: Option<&[u8]>,
{ tag: Option<&[u8]>,
let mut map = serializer.serialize_map(Some(self.0.len()))?; writer: &mut quick_xml::Writer<W>,
for name in &self.0 { ) -> std::io::Result<()> {
map.serialize_entry(&name, &())?; #[derive(Debug, XmlSerialize, PartialEq)]
struct Inner {
#[xml(ty = "untagged", flatten)]
tags: Vec<Tag>,
} }
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<Vec<quick_xml::events::attributes::Attribute<'a>>> {
None
} }
} }