rustical_dav/xml/
multistatus.rs

1use crate::xml::TagList;
2use headers::{CacheControl, ContentType, HeaderMapExt};
3use http::StatusCode;
4use quick_xml::name::Namespace;
5use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
6use std::collections::HashMap;
7
8#[derive(XmlSerialize)]
9pub struct PropTagWrapper<T: XmlSerialize>(#[xml(flatten, ty = "untagged")] pub Vec<T>);
10
11// RFC 2518
12// <!ELEMENT propstat (prop, status, responsedescription?) >
13#[derive(XmlSerialize, Debug)]
14pub struct PropstatElement<PropType: XmlSerialize> {
15    #[xml(ns = "crate::namespace::NS_DAV")]
16    pub prop: PropType,
17    #[xml(serialize_with = "xml_serialize_status")]
18    #[xml(ns = "crate::namespace::NS_DAV")]
19    pub status: StatusCode,
20}
21
22#[allow(clippy::trivially_copy_pass_by_ref)]
23fn xml_serialize_status(
24    status: &StatusCode,
25    ns: Option<Namespace>,
26    tag: Option<&str>,
27    namespaces: &HashMap<Namespace, &str>,
28    writer: &mut quick_xml::Writer<&mut Vec<u8>>,
29) -> std::io::Result<()> {
30    XmlSerialize::serialize(&format!("HTTP/1.1 {status}"), ns, tag, namespaces, writer)
31}
32
33#[derive(XmlSerialize)]
34#[xml(untagged)]
35pub enum PropstatWrapper<T: XmlSerialize> {
36    Normal(PropstatElement<PropTagWrapper<T>>),
37    TagList(PropstatElement<TagList>),
38}
39
40// RFC 2518
41// <!ELEMENT response (href, ((href*, status)|(propstat+)),
42// responsedescription?) >
43#[derive(XmlSerialize, XmlRootTag)]
44#[xml(ns = "crate::namespace::NS_DAV", root = "response")]
45#[xml(ns_prefix(
46    crate::namespace::NS_DAV = "",
47    crate::namespace::NS_CARDDAV = "CARD",
48    crate::namespace::NS_CALDAV = "CAL",
49    crate::namespace::NS_CALENDARSERVER = "CS",
50    crate::namespace::NS_DAVPUSH = "PUSH"
51))]
52pub struct ResponseElement<PropstatType: XmlSerialize> {
53    pub href: String,
54    #[xml(serialize_with = "xml_serialize_optional_status")]
55    pub status: Option<StatusCode>,
56    #[xml(flatten)]
57    pub propstat: Vec<PropstatWrapper<PropstatType>>,
58}
59
60#[allow(clippy::trivially_copy_pass_by_ref, clippy::ref_option)]
61fn xml_serialize_optional_status(
62    val: &Option<StatusCode>,
63    ns: Option<Namespace>,
64    tag: Option<&str>,
65    namespaces: &HashMap<Namespace, &str>,
66    writer: &mut quick_xml::Writer<&mut Vec<u8>>,
67) -> std::io::Result<()> {
68    XmlSerialize::serialize(
69        &val.map(|status| format!("HTTP/1.1 {status}")),
70        ns,
71        tag,
72        namespaces,
73        writer,
74    )
75}
76
77impl<PT: XmlSerialize> Default for ResponseElement<PT> {
78    fn default() -> Self {
79        Self {
80            href: String::new(),
81            status: None,
82            propstat: vec![],
83        }
84    }
85}
86
87// RFC 2518
88// <!ELEMENT multistatus (response+, responsedescription?) >
89// Extended by sync-token as specified in RFC 6578
90#[derive(XmlSerialize, XmlRootTag)]
91#[xml(root = "multistatus", ns = "crate::namespace::NS_DAV")]
92#[xml(ns_prefix(
93    crate::namespace::NS_DAV = "",
94    crate::namespace::NS_CARDDAV = "CARD",
95    crate::namespace::NS_CALDAV = "CAL",
96    crate::namespace::NS_CALENDARSERVER = "CS",
97    crate::namespace::NS_DAVPUSH = "PUSH"
98))]
99pub struct MultistatusElement<PropType: XmlSerialize, MemberPropType: XmlSerialize> {
100    #[xml(rename = "response", flatten)]
101    pub responses: Vec<ResponseElement<PropType>>,
102    #[xml(rename = "response", flatten)]
103    pub member_responses: Vec<ResponseElement<MemberPropType>>,
104    pub sync_token: Option<String>,
105}
106
107impl<T1: XmlSerialize, T2: XmlSerialize> Default for MultistatusElement<T1, T2> {
108    fn default() -> Self {
109        Self {
110            responses: vec![],
111            member_responses: vec![],
112            sync_token: None,
113        }
114    }
115}
116
117impl<T1: XmlSerialize, T2: XmlSerialize> axum::response::IntoResponse
118    for MultistatusElement<T1, T2>
119{
120    fn into_response(self) -> axum::response::Response {
121        use axum::body::Body;
122
123        let output = match self.serialize_to_string() {
124            Ok(out) => out,
125            Err(err) => return crate::Error::from(err).into_response(),
126        };
127
128        let mut resp = axum::response::Response::builder().status(StatusCode::MULTI_STATUS);
129        let hdrs = resp.headers_mut().unwrap();
130        hdrs.typed_insert(ContentType::xml());
131        hdrs.typed_insert(CacheControl::new().with_no_cache());
132        resp.body(Body::from(output)).unwrap()
133    }
134}