rustical_dav/xml/
multistatus.rs

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