mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 17:12:22 +00:00
save progress: Move from serde::Serialize to XmlSerialize
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user