webdav_xml/elements/
prop.rs

1// SPDX-FileCopyrightText: d-k-bo <d-k-bo@mailbox.org>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use bytestring::ByteString;
6
7use crate::{
8    element::{Element, ElementName},
9    properties::{
10        ContentLanguage, ContentLength, ContentType, CreationDate, DisplayName, ETag, LastModified,
11        LockDiscovery, ResourceType, SupportedLock,
12    },
13    value::{Value, ValueMap},
14    Error, DAV_NAMESPACE, DAV_PREFIX,
15};
16
17/// The `prop` XML element as defined in [RFC 4918](http://webdav.org/specs/rfc4918.html#ELEMENT_prop).
18///
19/// This element can contain arbitrary child elements and supports extracting
20/// them using [`Properties::get()`].
21#[derive(Clone, Debug, Default, PartialEq)]
22pub struct Properties(ValueMap);
23
24impl Properties {
25    pub fn new() -> Self {
26        Self::default()
27    }
28    pub fn with<E>(mut self, e: E) -> Self
29    where
30        E: Element + Into<Value>,
31    {
32        self.0.insert::<E>(e.into());
33        self
34    }
35    pub fn with_name<E>(mut self) -> Self
36    where
37        E: Element,
38    {
39        self.0.insert::<E>(Value::Empty);
40        self
41    }
42}
43
44impl Properties {
45    /// Read a specific property from this `prop` element.
46    ///
47    /// Returns
48    /// - `None` if the property doesn't exist
49    /// - `Some(None)` if the property exists and is empty
50    /// - `Some(Some(Ok(_)))` if the property exists and was successfully
51    ///   extracted
52    /// - `Some(Some(Err(_)))` if the property exists and extraction failed
53    pub fn get<'v, P>(&'v self) -> Option<Option<Result<P, Error>>>
54    where
55        P: Element + TryFrom<&'v Value, Error = Error>,
56    {
57        self.0.get_optional()
58    }
59    /// List the names of the properties in this `prop` element.
60    pub fn names(&self) -> impl Iterator<Item = &ElementName<ByteString>> {
61        self.0 .0.keys()
62    }
63}
64
65impl Properties {
66    /// Read the `creationdate` property.
67    ///
68    /// See [`Properties::get()`] for an overview of the possible return values.
69    pub fn creationdate(&self) -> Option<Option<Result<CreationDate, Error>>> {
70        self.get()
71    }
72    /// Read the `displayname` property.
73    ///
74    /// See [`Properties::get()`] for an overview of the possible return values.
75    pub fn displayname(&self) -> Option<Option<Result<DisplayName, Error>>> {
76        self.get()
77    }
78    /// Read the `getcontentlanguage` property.
79    ///
80    /// See [`Properties::get()`] for an overview of the possible return values.
81    pub fn getcontentlanguage(&self) -> Option<Option<Result<ContentLanguage, Error>>> {
82        self.get()
83    }
84    /// Read the `getcontentlength` property.
85    ///
86    /// See [`Properties::get()`] for an overview of the possible return values.
87    pub fn getcontentlength(&self) -> Option<Option<Result<ContentLength, Error>>> {
88        self.get()
89    }
90    /// Read the `getcontenttype` property.
91    ///
92    /// See [`Properties::get()`] for an overview of the possible return values.
93    pub fn getcontenttype(&self) -> Option<Option<Result<ContentType, Error>>> {
94        self.get()
95    }
96    /// Read the `getetag` property.
97    ///
98    /// See [`Properties::get()`] for an overview of the possible return values.
99    pub fn getetag(&self) -> Option<Option<Result<ETag, Error>>> {
100        self.get()
101    }
102    /// Read the `getlastmodified` property.
103    ///
104    /// See [`Properties::get()`] for an overview of the possible return values.
105    pub fn getlastmodified(&self) -> Option<Option<Result<LastModified, Error>>> {
106        self.get()
107    }
108    /// Read the `lockdiscovery` property.
109    ///
110    /// See [`Properties::get()`] for an overview of the possible return values.
111    pub fn lockdiscovery(&self) -> Option<Option<Result<LockDiscovery, Error>>> {
112        self.get()
113    }
114    /// Read the `resourcetype` property.
115    ///
116    /// See [`Properties::get()`] for an overview of the possible return values.
117    pub fn resourcetype(&self) -> Option<Option<Result<ResourceType, Error>>> {
118        self.get()
119    }
120    /// Read the `supportedlock` property.
121    ///
122    /// See [`Properties::get()`] for an overview of the possible return values.
123    pub fn supportedlock(&self) -> Option<Option<Result<SupportedLock, Error>>> {
124        self.get()
125    }
126}
127
128impl Element for Properties {
129    const NAMESPACE: &'static str = DAV_NAMESPACE;
130    const PREFIX: &'static str = DAV_PREFIX;
131    const LOCAL_NAME: &'static str = "prop";
132}
133
134impl TryFrom<&Value> for Properties {
135    type Error = Error;
136
137    fn try_from(value: &Value) -> Result<Self, Self::Error> {
138        value.to_map().cloned().map(Self)
139    }
140}
141
142impl From<Properties> for Value {
143    fn from(Properties(map): Properties) -> Value {
144        Value::Map(map)
145    }
146}
147
148#[cfg(test)]
149#[test]
150fn test_deserialize() -> eyre::Result<()> {
151    use pretty_assertions::assert_eq;
152
153    use crate::FromXml;
154
155    let xml = r#"
156    <d:prop xmlns:d="DAV:">
157        <d:getlastmodified>Mon, 30 Sep 2019 12:13:02 GMT</d:getlastmodified>
158        <d:getcontentlength>0</d:getcontentlength>
159        <d:resourcetype/>
160        <d:getetag>&quot;a28785e285ce0de0738676814705c4e1&quot;</d:getetag>
161        <d:getcontenttype>text/plain</d:getcontenttype>
162    </d:prop>
163    "#;
164
165    let prop = Properties::from_xml(xml)?;
166
167    assert_eq!(
168        prop.getlastmodified().unwrap().unwrap()?,
169        LastModified("Mon, 30 Sep 2019 12:13:02 GMT".parse()?)
170    );
171    assert_eq!(prop.getcontentlength().unwrap().unwrap()?, ContentLength(0));
172    assert!(prop.resourcetype().unwrap().is_none());
173    assert_eq!(
174        prop.getetag().unwrap().unwrap()?,
175        ETag(r#""a28785e285ce0de0738676814705c4e1""#.into())
176    );
177    assert_eq!(
178        prop.getcontenttype().unwrap().unwrap()?,
179        ContentType(mime::TEXT_PLAIN)
180    );
181
182    Ok(())
183}