dav: Fix proppatch supporting multiple properties in <set> and <remove> elements

This commit is contained in:
Lennart
2025-07-18 20:59:37 +02:00
parent 34839aa2ed
commit 16f9ce6f38

View File

@@ -26,21 +26,21 @@ enum SetPropertyPropWrapper<T: XmlDeserialize> {
// We are <prop> // We are <prop>
#[derive(XmlDeserialize, Clone, Debug)] #[derive(XmlDeserialize, Clone, Debug)]
struct SetPropertyPropWrapperWrapper<T: XmlDeserialize>( struct SetPropertyPropWrapperWrapper<T: XmlDeserialize>(
#[xml(ty = "untagged")] SetPropertyPropWrapper<T>, #[xml(ty = "untagged", flatten)] Vec<SetPropertyPropWrapper<T>>,
); );
// We are <set> // We are <set>
#[derive(XmlDeserialize, Clone, Debug)] #[derive(XmlDeserialize, Clone, Debug)]
struct SetPropertyElement<T: XmlDeserialize> { struct SetPropertyElement<T: XmlDeserialize> {
#[xml(ns = "crate::namespace::NS_DAV")] #[xml(ns = "crate::namespace::NS_DAV")]
prop: T, prop: SetPropertyPropWrapperWrapper<T>,
} }
#[derive(XmlDeserialize, Clone, Debug)] #[derive(XmlDeserialize, Clone, Debug)]
struct TagName(#[xml(ty = "tag_name")] String); struct TagName(#[xml(ty = "tag_name")] String);
#[derive(XmlDeserialize, Clone, Debug)] #[derive(XmlDeserialize, Clone, Debug)]
struct PropertyElement(#[xml(ty = "untagged")] TagName); struct PropertyElement(#[xml(ty = "untagged", flatten)] Vec<TagName>);
#[derive(XmlDeserialize, Clone, Debug)] #[derive(XmlDeserialize, Clone, Debug)]
struct RemovePropertyElement { struct RemovePropertyElement {
@@ -81,9 +81,8 @@ pub(crate) async fn route_proppatch<R: ResourceService>(
let href = path.to_owned(); let href = path.to_owned();
// Extract operations // Extract operations
let PropertyupdateElement::<SetPropertyPropWrapperWrapper<<R::Resource as Resource>::Prop>>( let PropertyupdateElement::<<R::Resource as Resource>::Prop>(operations) =
operations, XmlDocument::parse_str(body).map_err(Error::XmlError)?;
) = XmlDocument::parse_str(body).map_err(Error::XmlError)?;
let mut resource = resource_service let mut resource = resource_service
.get_resource(path_components, false) .get_resource(path_components, false)
@@ -100,59 +99,63 @@ pub(crate) async fn route_proppatch<R: ResourceService>(
for operation in operations.into_iter() { for operation in operations.into_iter() {
match operation { match operation {
Operation::Set(SetPropertyElement { Operation::Set(SetPropertyElement {
prop: SetPropertyPropWrapperWrapper(property), prop: SetPropertyPropWrapperWrapper(properties),
}) => { }) => {
match property { for property in properties {
SetPropertyPropWrapper::Valid(prop) => { match property {
let propname: <<R::Resource as Resource>::Prop as PropName>::Names = SetPropertyPropWrapper::Valid(prop) => {
prop.clone().into(); let propname: <<R::Resource as Resource>::Prop as PropName>::Names =
let (ns, propname): (Option<Namespace>, &str) = propname.into(); prop.clone().into();
match resource.set_prop(prop) { let (ns, propname): (Option<Namespace>, &str) = propname.into();
Ok(()) => { match resource.set_prop(prop) {
props_ok.push((ns.map(NamespaceOwned::from), propname.to_owned())) Ok(()) => props_ok
} .push((ns.map(NamespaceOwned::from), propname.to_owned())),
Err(Error::PropReadOnly) => props_conflict Err(Error::PropReadOnly) => props_conflict
.push((ns.map(NamespaceOwned::from), propname.to_owned())), .push((ns.map(NamespaceOwned::from), propname.to_owned())),
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
} }
SetPropertyPropWrapper::Invalid(invalid) => { SetPropertyPropWrapper::Invalid(invalid) => {
let propname = invalid.tag_name(); let propname = invalid.tag_name();
if let Some(full_propname) = <R::Resource as Resource>::list_props() if let Some(full_propname) = <R::Resource as Resource>::list_props()
.into_iter() .into_iter()
.find_map(|(ns, tag)| { .find_map(|(ns, tag)| {
if tag == propname.as_str() { if tag == propname.as_str() {
Some((ns.map(NamespaceOwned::from), tag.to_owned())) Some((ns.map(NamespaceOwned::from), tag.to_owned()))
} else { } else {
None None
} }
}) })
{ {
// This happens in following cases: // This happens in following cases:
// - read-only properties with #[serde(skip_deserializing)] // - read-only properties with #[serde(skip_deserializing)]
// - internal properties // - internal properties
props_conflict.push(full_propname) props_conflict.push(full_propname)
} else { } else {
props_not_found.push((None, propname)); props_not_found.push((None, propname));
}
} }
} }
} }
} }
Operation::Remove(remove_el) => { Operation::Remove(remove_el) => {
let propname = remove_el.prop.0.0; for tagname in remove_el.prop.0 {
match <<R::Resource as Resource>::Prop as PropName>::Names::from_str(&propname) { let propname = tagname.0;
Ok(prop) => match resource.remove_prop(&prop) { match <<R::Resource as Resource>::Prop as PropName>::Names::from_str(&propname)
Ok(()) => props_ok.push((None, propname)), {
Err(Error::PropReadOnly) => props_conflict.push({ Ok(prop) => match resource.remove_prop(&prop) {
let (ns, tag) = prop.into(); Ok(()) => props_ok.push((None, propname)),
(ns.map(NamespaceOwned::from), tag.to_owned()) Err(Error::PropReadOnly) => props_conflict.push({
}), let (ns, tag) = prop.into();
Err(err) => return Err(err.into()), (ns.map(NamespaceOwned::from), tag.to_owned())
}, }),
// I guess removing a nonexisting property should be successful :) Err(err) => return Err(err.into()),
Err(_) => props_ok.push((None, propname)), },
}; // I guess removing a nonexisting property should be successful :)
Err(_) => props_ok.push((None, propname)),
};
}
} }
} }
} }