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

@@ -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,
}
}

View File

@@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<UserPrivilegeWrapper<'a>>,
impl XmlSerialize for UserPrivilegeSet {
fn serialize<W: std::io::Write>(
&self,
ns: Option<&[u8]>,
tag: Option<&[u8]>,
writer: &mut quick_xml::Writer<W>,
) -> std::io::Result<()> {
#[derive(XmlSerialize)]
pub struct FakeUserPrivilegeSet {
#[xml(rename = b"privilege", flatten)]
privileges: Vec<UserPrivilege>,
}
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<Vec<quick_xml::events::attributes::Attribute<'a>>> {
None
}
}
#[derive(Debug, Clone, XmlDeserialize, Default, PartialEq)]
#[derive(Debug, Clone, Default, PartialEq)]
pub struct UserPrivilegeSet {
#[xml(flatten)]
privileges: HashSet<UserPrivilege>,
}

View File

@@ -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<T: Serialize + XmlDeserialize> ResourceProp for T {}
pub trait ResourceProp: XmlSerialize + XmlDeserialize {}
impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {}
pub trait ResourcePropName: FromStr + VariantNames {}
impl<T: FromStr + VariantNames> ResourcePropName for T {}
pub trait ResourceType: Serialize + XmlDeserialize {}
impl<T: Serialize + XmlDeserialize> 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<HrefElement>),
}
#[derive(Serialize)]
#[derive(XmlSerialize)]
#[xml(untagged)]
pub enum EitherProp<Left: ResourceProp, Right: ResourceProp> {
#[serde(untagged)]
Left(Left),
#[serde(untagged)]
Right(Right),
}

View File

@@ -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<PR: Resource> Default for RootResource<PR> {
#[strum(serialize_all = "kebab-case")]
pub enum RootResourcePropName {}
#[derive(XmlDeserialize, Serialize, Clone, PartialEq)]
#[derive(XmlDeserialize, XmlSerialize, Serialize, Clone, PartialEq)]
pub enum RootResourceProp {}
impl From<RootResourceProp> for RootResourcePropName {

View File

@@ -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,
}

View File

@@ -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<T: Serialize> {
#[serde(rename = "$value")]
#[derive(XmlSerialize)]
pub struct PropTagWrapper<T: XmlSerialize> {
#[xml(flatten, ty = "untagged")]
pub prop: Vec<T>,
}
// RFC 2518
// <!ELEMENT propstat (prop, status, responsedescription?) >
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct PropstatElement<PropType: Serialize> {
#[derive(XmlSerialize)]
pub struct PropstatElement<PropType: XmlSerialize> {
pub prop: PropType,
#[serde(serialize_with = "serialize_status")]
#[xml(serialize_with = "xml_serialize_status")]
pub status: StatusCode,
}
fn serialize_status<S: Serializer>(status: &StatusCode, serializer: S) -> Result<S::Ok, S::Error> {
format!("HTTP/1.1 {}", status).serialize(serializer)
fn xml_serialize_status<W: ::std::io::Write>(
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)]
#[serde(untagged)]
pub enum PropstatWrapper<T: Serialize> {
#[derive(XmlSerialize)]
#[xml(untagged)]
pub enum PropstatWrapper<T: XmlSerialize> {
Normal(PropstatElement<PropTagWrapper<T>>),
TagList(PropstatElement<TagList>),
}
@@ -38,26 +42,30 @@ pub enum PropstatWrapper<T: Serialize> {
// RFC 2518
// <!ELEMENT response (href, ((href*, status)|(propstat+)),
// responsedescription?) >
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ResponseElement<PropstatType: Serialize> {
#[derive(XmlSerialize)]
pub struct ResponseElement<PropstatType: XmlSerialize> {
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<StatusCode>,
#[xml(flatten)]
pub propstat: Vec<PropstatWrapper<PropstatType>>,
}
fn serialize_optional_status<S: Serializer>(
status_option: &Option<StatusCode>,
serializer: S,
) -> Result<S::Ok, S::Error> {
status_option
.map(|status| format!("HTTP/1.1 {}", status))
.serialize(serializer)
fn xml_serialize_optional_status<W: ::std::io::Write>(
val: &Option<StatusCode>,
ns: Option<&[u8]>,
tag: Option<&[u8]>,
writer: &mut quick_xml::Writer<W>,
) -> std::io::Result<()> {
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 {
Self {
href: String::new(),
@@ -70,30 +78,24 @@ impl<PT: Serialize> Default for ResponseElement<PT> {
// RFC 2518
// <!ELEMENT multistatus (response+, responsedescription?) >
// Extended by sync-token as specified in RFC 6578
#[derive(Serialize)]
#[serde(rename = "multistatus", rename_all = "kebab-case")]
pub struct MultistatusElement<PropType: Serialize, MemberPropType: Serialize> {
#[serde(rename = "response")]
#[derive(XmlSerialize, XmlRootTag)]
#[xml(root = b"multistatus")]
pub struct MultistatusElement<PropType: XmlSerialize, MemberPropType: XmlSerialize> {
#[xml(rename = b"response", flatten)]
pub responses: Vec<ResponseElement<PropType>>,
#[serde(rename = "response")]
#[xml(rename = b"response", flatten)]
pub member_responses: Vec<ResponseElement<MemberPropType>>,
#[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<String>,
}
impl<T1: Serialize, T2: Serialize> Default for MultistatusElement<T1, T2> {
impl<T1: XmlSerialize, T2: XmlSerialize> Default for MultistatusElement<T1, T2> {
fn default() -> Self {
Self {
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;
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
let mut output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\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"<?xml version=\"1.0\" encoding=\"utf-8\"?>\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())
}
}

View File

@@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<W: std::io::Write>(
&self,
ns: Option<&[u8]>,
tag: Option<&[u8]>,
writer: &mut quick_xml::Writer<W>,
) -> 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<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 serde::ser::SerializeMap;
use serde::Serialize;
use rustical_xml::XmlSerialize;
#[derive(Clone, Debug, PartialEq, From)]
pub struct TagList(Vec<String>);
impl Serialize for TagList {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<W: std::io::Write>(
&self,
ns: Option<&[u8]>,
tag: Option<&[u8]>,
writer: &mut quick_xml::Writer<W>,
) -> std::io::Result<()> {
#[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
}
}