resourcetype: Proper namespace handling

This commit is contained in:
Lennart
2025-01-04 14:58:33 +01:00
parent e7f51f040b
commit f406b7dbb2
11 changed files with 126 additions and 62 deletions

View File

@@ -13,7 +13,7 @@ use async_trait::async_trait;
use derive_more::derive::{From, Into}; use derive_more::derive::{From, Into};
use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::HrefElement; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::{Calendar, CalendarStore}; use rustical_store::{Calendar, CalendarStore};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
@@ -93,11 +93,33 @@ impl Resource for CalendarResource {
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> &'static [&'static str] { fn get_resourcetype(&self) -> Resourcetype {
if self.0.subscription_url.is_none() { if self.0.subscription_url.is_none() {
&["collection", "C:calendar"] Resourcetype {
inner: &[
ResourcetypeInner {
ns: rustical_dav::namespace::NS_DAV,
name: "collection",
},
ResourcetypeInner {
ns: rustical_dav::namespace::NS_CALDAV,
name: "calendar",
},
],
}
} else { } else {
&["collection", "CS:subscribed"] Resourcetype {
inner: &[
ResourcetypeInner {
ns: rustical_dav::namespace::NS_DAV,
name: "collection",
},
ResourcetypeInner {
ns: rustical_dav::namespace::NS_CALENDARSERVER,
name: "subscribed",
},
],
}
} }
} }

View File

@@ -6,6 +6,7 @@ use derive_more::derive::{From, Into};
use rustical_dav::{ use rustical_dav::{
privileges::UserPrivilegeSet, privileges::UserPrivilegeSet,
resource::{Resource, ResourceService}, resource::{Resource, ResourceService},
xml::Resourcetype,
}; };
use rustical_store::{auth::User, CalendarObject, CalendarStore}; use rustical_store::{auth::User, CalendarObject, CalendarStore};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
@@ -53,8 +54,8 @@ impl Resource for CalendarObjectResource {
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> &'static [&'static str] { fn get_resourcetype(&self) -> Resourcetype {
&[] Resourcetype { inner: &[] }
} }
fn get_prop( fn get_prop(

View File

@@ -4,7 +4,7 @@ use actix_web::dev::ResourceMap;
use async_trait::async_trait; use async_trait::async_trait;
use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::HrefElement; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::CalendarStore; use rustical_store::CalendarStore;
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
@@ -57,8 +57,19 @@ impl Resource for PrincipalResource {
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> &'static [&'static str] { fn get_resourcetype(&self) -> Resourcetype {
&["collection", "principal"] Resourcetype {
inner: &[
ResourcetypeInner {
ns: rustical_dav::namespace::NS_DAV,
name: "collection",
},
ResourcetypeInner {
ns: rustical_dav::namespace::NS_DAV,
name: "principal",
},
],
}
} }
fn get_prop( fn get_prop(

View File

@@ -5,6 +5,7 @@ use derive_more::derive::{From, Into};
use rustical_dav::{ use rustical_dav::{
privileges::UserPrivilegeSet, privileges::UserPrivilegeSet,
resource::{Resource, ResourceService}, resource::{Resource, ResourceService},
xml::Resourcetype,
}; };
use rustical_store::{auth::User, AddressObject, AddressbookStore}; use rustical_store::{auth::User, AddressObject, AddressbookStore};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
@@ -54,8 +55,8 @@ impl Resource for AddressObjectResource {
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> &'static [&'static str] { fn get_resourcetype(&self) -> Resourcetype {
&[] Resourcetype { inner: &[] }
} }
fn get_prop( fn get_prop(

View File

@@ -11,6 +11,7 @@ use async_trait::async_trait;
use derive_more::derive::{From, Into}; use derive_more::derive::{From, Into};
use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::{Addressbook, AddressbookStore}; use rustical_store::{Addressbook, AddressbookStore};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
@@ -68,9 +69,19 @@ impl Resource for AddressbookResource {
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> &'static [&'static str] { fn get_resourcetype(&self) -> Resourcetype {
// TODO: namespace Resourcetype {
&["collection", "CARD:addressbook"] inner: &[
ResourcetypeInner {
ns: rustical_dav::namespace::NS_DAV,
name: "collection",
},
ResourcetypeInner {
ns: rustical_dav::namespace::NS_CARDDAV,
name: "addressbook",
},
],
}
} }
fn get_prop( fn get_prop(

View File

@@ -4,7 +4,7 @@ use actix_web::dev::ResourceMap;
use async_trait::async_trait; use async_trait::async_trait;
use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::HrefElement; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::AddressbookStore; use rustical_store::AddressbookStore;
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
@@ -58,8 +58,19 @@ impl Resource for PrincipalResource {
type Error = Error; type Error = Error;
type PrincipalResource = PrincipalResource; type PrincipalResource = PrincipalResource;
fn get_resourcetype(&self) -> &'static [&'static str] { fn get_resourcetype(&self) -> Resourcetype {
&["collection", "principal"] Resourcetype {
inner: &[
ResourcetypeInner {
ns: rustical_dav::namespace::NS_DAV,
name: "collection",
},
ResourcetypeInner {
ns: rustical_dav::namespace::NS_DAV,
name: "principal",
},
],
}
} }
fn get_prop( fn get_prop(

View File

@@ -64,7 +64,7 @@ pub trait Resource: Clone + 'static {
type Error: ResponseError + From<crate::Error>; type Error: ResponseError + From<crate::Error>;
type PrincipalResource: Resource; type PrincipalResource: Resource;
fn get_resourcetype(&self) -> &'static [&'static str]; fn get_resourcetype(&self) -> Resourcetype;
fn list_props() -> Vec<&'static str> { fn list_props() -> Vec<&'static str> {
[Self::PropName::VARIANTS, CommonPropertiesPropName::VARIANTS].concat() [Self::PropName::VARIANTS, CommonPropertiesPropName::VARIANTS].concat()
@@ -78,7 +78,7 @@ pub trait Resource: Clone + 'static {
) -> Result<CommonPropertiesProp, Self::Error> { ) -> Result<CommonPropertiesProp, Self::Error> {
Ok(match prop { Ok(match prop {
CommonPropertiesPropName::Resourcetype => { CommonPropertiesPropName::Resourcetype => {
CommonPropertiesProp::Resourcetype(Resourcetype(self.get_resourcetype())) CommonPropertiesProp::Resourcetype(self.get_resourcetype())
} }
CommonPropertiesPropName::CurrentUserPrincipal => { CommonPropertiesPropName::CurrentUserPrincipal => {
CommonPropertiesProp::CurrentUserPrincipal( CommonPropertiesProp::CurrentUserPrincipal(

View File

@@ -1,5 +1,6 @@
use crate::privileges::UserPrivilegeSet; use crate::privileges::UserPrivilegeSet;
use crate::resource::{Resource, ResourceService}; use crate::resource::{Resource, ResourceService};
use crate::xml::{Resourcetype, ResourcetypeInner};
use actix_web::dev::ResourceMap; use actix_web::dev::ResourceMap;
use async_trait::async_trait; use async_trait::async_trait;
use rustical_store::auth::User; use rustical_store::auth::User;
@@ -37,8 +38,13 @@ impl<PR: Resource> Resource for RootResource<PR> {
type Error = PR::Error; type Error = PR::Error;
type PrincipalResource = PR; type PrincipalResource = PR;
fn get_resourcetype(&self) -> &'static [&'static str] { fn get_resourcetype(&self) -> Resourcetype {
&["collection"] Resourcetype {
inner: &[ResourcetypeInner {
ns: crate::namespace::NS_DAV,
name: "collection",
}],
}
} }
fn get_prop( fn get_prop(

View File

@@ -5,7 +5,7 @@ pub mod tag_list;
use derive_more::derive::From; use derive_more::derive::From;
pub use multistatus::MultistatusElement; pub use multistatus::MultistatusElement;
pub use propfind::{PropElement, PropfindElement, PropfindType, Propname}; pub use propfind::{PropElement, PropfindElement, PropfindType, Propname};
pub use resourcetype::Resourcetype; pub use resourcetype::{Resourcetype, ResourcetypeInner};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
pub use tag_list::TagList; pub use tag_list::TagList;

View File

@@ -1,47 +1,25 @@
use std::collections::HashMap;
use quick_xml::events::attributes::Attribute;
use quick_xml::events::{BytesEnd, BytesStart, Event};
use quick_xml::name::Namespace;
use rustical_xml::XmlSerialize; use rustical_xml::XmlSerialize;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, XmlSerialize)]
pub struct Resourcetype(pub &'static [&'static str]); pub struct Resourcetype {
#[xml(flatten, ty = "untagged")]
pub inner: &'static [ResourcetypeInner],
}
impl XmlSerialize for Resourcetype { #[derive(Debug, Clone, PartialEq, XmlSerialize)]
fn serialize<W: std::io::Write>( pub struct ResourcetypeInner {
&self, #[xml(ty = "namespace")]
ns: Option<Namespace>, pub ns: quick_xml::name::Namespace<'static>,
tag: Option<&[u8]>, #[xml(ty = "tag_name")]
namespaces: &HashMap<Namespace, &[u8]>, pub name: &'static str,
writer: &mut quick_xml::Writer<W>,
) -> std::io::Result<()> {
let tag_str = tag.map(String::from_utf8_lossy);
if let Some(tag) = &tag_str {
writer.write_event(Event::Start(BytesStart::new(tag.clone())))?;
}
for &ty in self.0 {
writer.write_event(Event::Empty(BytesStart::new(ty)))?;
}
if let Some(tag) = &tag_str {
writer.write_event(Event::End(BytesEnd::new(tag.clone())))?;
}
Ok(())
}
#[allow(refining_impl_trait)]
fn attributes<'a>(&self) -> Option<Vec<Attribute<'a>>> {
None
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Resourcetype;
use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot}; use rustical_xml::{XmlRootTag, XmlSerialize, XmlSerializeRoot};
use super::{Resourcetype, ResourcetypeInner};
#[derive(XmlSerialize, XmlRootTag)] #[derive(XmlSerialize, XmlRootTag)]
#[xml(root = b"document")] #[xml(root = b"document")]
struct Document { struct Document {
@@ -53,14 +31,25 @@ mod tests {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = quick_xml::Writer::new(&mut buf); let mut writer = quick_xml::Writer::new(&mut buf);
Document { Document {
resourcetype: Resourcetype(&["collection", "hello"]), resourcetype: Resourcetype {
inner: &[
ResourcetypeInner {
ns: crate::namespace::NS_DAV,
name: "displayname",
},
ResourcetypeInner {
ns: crate::namespace::NS_CALENDARSERVER,
name: "calendar-color",
},
],
},
} }
.serialize_root(&mut writer) .serialize_root(&mut writer)
.unwrap(); .unwrap();
let out = String::from_utf8(buf).unwrap(); let out = String::from_utf8(buf).unwrap();
assert_eq!( assert_eq!(
out, out,
"<document><resourcetype><collection/><hello/></resourcetype></document>" "<document><resourcetype><displayname xmlns=\"DAV:\"/><calendar-color xmlns=\"http://calendarserver.org/ns/\"/></resourcetype></document>"
) )
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::attrs::{FieldType, StructAttrs}; use crate::attrs::{FieldType, StructAttrs};
use crate::{field, Field}; use crate::Field;
use core::panic; use core::panic;
use darling::FromDeriveInput; use darling::FromDeriveInput;
use quote::quote; use quote::quote;
@@ -197,7 +197,7 @@ impl NamedStruct {
pub fn impl_se(&self) -> proc_macro2::TokenStream { pub fn impl_se(&self) -> proc_macro2::TokenStream {
let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl();
let ident = &self.ident; let ident = &self.ident;
let tag_writers = self.fields.iter().map(Field::tag_writer); let tag_writers: Vec<_> = self.fields.iter().filter_map(Field::tag_writer).collect();
let untagged_attributes = self let untagged_attributes = self
.fields .fields
@@ -240,7 +240,18 @@ impl NamedStruct {
} }
}); });
let is_empty = tag_writers.len() == 0; let namespace_field = self
.fields
.iter()
.find(|field| field.attrs.xml_ty == FieldType::Namespace)
.map(|field| {
let field_ident = field.field_ident();
quote! {
let ns = Some(self.#field_ident);
}
});
let is_empty = tag_writers.is_empty();
// If we are the root element write the xmlns attributes // If we are the root element write the xmlns attributes
let prefix_attributes = if self.attrs.root.is_some() { let prefix_attributes = if self.attrs.root.is_some() {
@@ -276,6 +287,7 @@ impl NamedStruct {
use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
#tag_name_field; #tag_name_field;
#namespace_field;
let prefix = ns let prefix = ns
.map(|ns| namespaces.get(&ns)) .map(|ns| namespaces.get(&ns))