use crate::xml::XmlWriter;
use super::{DavNamespace, DavProperty};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PropfindBody {
properties: Vec<DavProperty>,
}
impl PropfindBody {
pub fn new(properties: impl IntoIterator<Item = DavProperty>) -> Self {
Self {
properties: properties.into_iter().collect(),
}
}
pub fn all() -> Self {
Self::new([
DavProperty::Etag,
DavProperty::ContentLength,
DavProperty::ContentType,
DavProperty::ResourceType,
DavProperty::LastModified,
DavProperty::DisplayName,
DavProperty::FileId,
DavProperty::Permissions,
DavProperty::HasPreview,
])
}
pub fn properties(&self) -> &[DavProperty] {
&self.properties
}
pub fn is_empty(&self) -> bool {
self.properties.is_empty()
}
pub fn to_xml(&self) -> String {
let mut writer = XmlWriter::new();
writer.prolog();
writer.open_with("d:propfind", &self.namespace_declarations());
writer.open("d:prop");
for property in &self.properties {
writer.empty(property.qualified_name());
}
writer.close("d:prop");
writer.close("d:propfind");
writer.into_string()
}
fn namespace_declarations(&self) -> Vec<(&'static str, &'static str)> {
let mut declarations: Vec<(&'static str, &'static str)> = Vec::new();
for namespace in self.properties.iter().map(|p| p.namespace()) {
if declarations
.iter()
.any(|(key, _)| *key == declaration_key(namespace))
{
continue;
}
declarations.push((declaration_key(namespace), namespace.uri()));
}
if declarations.is_empty() {
let namespace = DavNamespace::Dav;
declarations.push((declaration_key(namespace), namespace.uri()));
}
declarations
}
}
const fn declaration_key(namespace: DavNamespace) -> &'static str {
match namespace {
DavNamespace::Dav => "xmlns:d",
DavNamespace::OwnCloud => "xmlns:oc",
DavNamespace::Nextcloud => "xmlns:nc",
}
}
#[cfg(test)]
mod tests {
use super::PropfindBody;
use crate::propfind::DavProperty;
#[test]
fn every_preset_contains_the_three_namespace_declarations() {
let xml = PropfindBody::all().to_xml();
assert!(xml.contains(r#"xmlns:d="DAV:""#), "{xml}");
assert!(
xml.contains(r#"xmlns:oc="http://owncloud.org/ns""#),
"{xml}"
);
assert!(
xml.contains(r#"xmlns:nc="http://nextcloud.org/ns""#),
"{xml}"
);
}
#[test]
fn preset_asks_for_every_property_once() {
let xml = PropfindBody::all().to_xml();
assert_eq!(xml.matches("<d:getetag/>").count(), 1);
assert!(xml.contains("<oc:fileid/>"));
assert!(xml.contains("<nc:has-preview/>"));
assert_eq!(xml.matches("xmlns:d=").count(), 1);
}
#[test]
fn body_is_well_formed_and_carries_the_requested_properties() {
let xml = PropfindBody::new([DavProperty::Etag, DavProperty::FileId]).to_xml();
assert!(xml.starts_with("<?xml version=\"1.0\" encoding=\"utf-8\"?>"));
assert!(xml.contains("<d:propfind"));
assert!(xml.contains("<d:prop><d:getetag/><oc:fileid/></d:prop>"));
assert!(xml.ends_with("</d:propfind>"));
assert_eq!(xml.matches("<d:prop>").count(), 1);
assert_eq!(xml.matches("</d:propfind>").count(), 1);
assert!(!xml.contains("xmlns:nc"));
}
#[test]
fn core_only_body_still_declares_the_dav_namespace() {
let xml = PropfindBody::new([DavProperty::Etag]).to_xml();
assert!(xml.contains(r#"xmlns:d="DAV:""#));
assert!(!xml.contains("xmlns:oc"));
}
#[test]
fn empty_body_is_reported_and_still_renders_a_document() {
let body = PropfindBody::new([]);
assert!(body.is_empty());
assert!(body.to_xml().contains("<d:prop></d:prop>"));
}
}