Skip to main content

nextcloud_dav/propfind/
property.rs

1//! The properties this crate knows how to read back.
2
3/// XML namespace a WebDAV property is defined in.
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
5pub enum DavNamespace {
6    /// `DAV:` — the WebDAV core namespace, prefix `d`.
7    Dav,
8    /// `http://owncloud.org/ns` — the legacy ownCloud namespace, prefix `oc`.
9    OwnCloud,
10    /// `http://nextcloud.org/ns` — the Nextcloud namespace, prefix `nc`.
11    Nextcloud,
12}
13
14impl DavNamespace {
15    /// Prefix used for this namespace in request bodies.
16    pub const fn prefix(self) -> &'static str {
17        match self {
18            Self::Dav => "d",
19            Self::OwnCloud => "oc",
20            Self::Nextcloud => "nc",
21        }
22    }
23
24    /// Namespace URI as declared on the propfind element.
25    pub const fn uri(self) -> &'static str {
26        match self {
27            Self::Dav => "DAV:",
28            Self::OwnCloud => "http://owncloud.org/ns",
29            Self::Nextcloud => "http://nextcloud.org/ns",
30        }
31    }
32}
33
34/// A property a propfind request may ask for.
35///
36/// The set is deliberately the one [`DavResource`](crate::DavResource) can
37/// represent: asking for a property nothing decodes would only cost bytes.
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub enum DavProperty {
40    /// `d:getetag` — entity tag of the current content.
41    Etag,
42    /// `d:getcontentlength` — size in bytes.
43    ContentLength,
44    /// `d:getcontenttype` — MIME type.
45    ContentType,
46    /// `d:resourcetype` — collection marker.
47    ResourceType,
48    /// `d:getlastmodified` — modification time, as an HTTP date.
49    LastModified,
50    /// `d:displayname` — user-visible name.
51    DisplayName,
52    /// `oc:fileid` — Nextcloud's stable file id.
53    FileId,
54    /// `oc:permissions` — permission letters for this resource.
55    Permissions,
56    /// `nc:has-preview` — whether a preview can be rendered.
57    HasPreview,
58}
59
60impl DavProperty {
61    /// Namespace the property is defined in.
62    pub const fn namespace(self) -> DavNamespace {
63        match self {
64            Self::Etag
65            | Self::ContentLength
66            | Self::ContentType
67            | Self::ResourceType
68            | Self::LastModified
69            | Self::DisplayName => DavNamespace::Dav,
70            Self::FileId | Self::Permissions => DavNamespace::OwnCloud,
71            Self::HasPreview => DavNamespace::Nextcloud,
72        }
73    }
74
75    /// Local part of the property name, without prefix.
76    pub const fn local_name(self) -> &'static str {
77        match self {
78            Self::Etag => "getetag",
79            Self::ContentLength => "getcontentlength",
80            Self::ContentType => "getcontenttype",
81            Self::ResourceType => "resourcetype",
82            Self::LastModified => "getlastmodified",
83            Self::DisplayName => "displayname",
84            Self::FileId => "fileid",
85            Self::Permissions => "permissions",
86            Self::HasPreview => "has-preview",
87        }
88    }
89
90    /// Qualified name used in both request and response XML.
91    ///
92    /// Spelled out rather than assembled from
93    /// [`namespace`](Self::namespace) and [`local_name`](Self::local_name)
94    /// because it has to stay a `&'static str`; the unit tests assert the
95    /// three stay consistent.
96    pub const fn qualified_name(self) -> &'static str {
97        match self {
98            Self::Etag => "d:getetag",
99            Self::ContentLength => "d:getcontentlength",
100            Self::ContentType => "d:getcontenttype",
101            Self::ResourceType => "d:resourcetype",
102            Self::LastModified => "d:getlastmodified",
103            Self::DisplayName => "d:displayname",
104            Self::FileId => "oc:fileid",
105            Self::Permissions => "oc:permissions",
106            Self::HasPreview => "nc:has-preview",
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::{DavNamespace, DavProperty};
114
115    const ALL: [DavProperty; 9] = [
116        DavProperty::Etag,
117        DavProperty::ContentLength,
118        DavProperty::ContentType,
119        DavProperty::ResourceType,
120        DavProperty::LastModified,
121        DavProperty::DisplayName,
122        DavProperty::FileId,
123        DavProperty::Permissions,
124        DavProperty::HasPreview,
125    ];
126
127    #[test]
128    fn every_property_renders_its_namespaced_tag() {
129        let expected = [
130            "d:getetag",
131            "d:getcontentlength",
132            "d:getcontenttype",
133            "d:resourcetype",
134            "d:getlastmodified",
135            "d:displayname",
136            "oc:fileid",
137            "oc:permissions",
138            "nc:has-preview",
139        ];
140        for (property, tag) in ALL.iter().zip(expected) {
141            assert_eq!(property.qualified_name(), tag);
142            let prefix = property.namespace().prefix();
143            assert_eq!(
144                property.qualified_name(),
145                format!("{prefix}:{}", property.local_name())
146            );
147        }
148    }
149
150    #[test]
151    fn namespaces_carry_the_prefix_and_uri_they_are_declared_with() {
152        assert_eq!(DavNamespace::Dav.prefix(), "d");
153        assert_eq!(DavNamespace::Dav.uri(), "DAV:");
154        assert_eq!(DavNamespace::OwnCloud.uri(), "http://owncloud.org/ns");
155        assert_eq!(DavNamespace::Nextcloud.uri(), "http://nextcloud.org/ns");
156    }
157
158    #[test]
159    fn request_tags_match_the_response_tags() {
160        assert_eq!(DavProperty::HasPreview.qualified_name(), "nc:has-preview");
161        assert_eq!(DavProperty::FileId.qualified_name(), "oc:fileid");
162    }
163}