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