Add namespaces to propnames

This commit is contained in:
Lennart
2025-01-18 18:56:37 +01:00
parent 461c67a72b
commit ea9f5a711d
12 changed files with 118 additions and 76 deletions

View File

@@ -16,16 +16,17 @@ use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::{Calendar, CalendarStore, SubscriptionStore}; use rustical_store::{Calendar, CalendarStore, SubscriptionStore};
use rustical_xml::EnumVariants;
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{XmlDeserialize, XmlSerialize};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr};
#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)]
#[strum_discriminants( #[strum_discriminants(
name(CalendarPropName), name(CalendarPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
pub enum CalendarProp { pub enum CalendarProp {
@@ -89,6 +90,12 @@ impl From<CalendarResource> for Calendar {
} }
} }
impl CalendarResource {
fn get_synctoken(&self) -> String {
self.cal.format_synctoken()
}
}
impl Resource for CalendarResource { impl Resource for CalendarResource {
type PropName = CalendarPropName; type PropName = CalendarPropName;
type Prop = CalendarProp; type Prop = CalendarProp;
@@ -98,13 +105,16 @@ impl Resource for CalendarResource {
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
if self.cal.subscription_url.is_none() { if self.cal.subscription_url.is_none() {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),
ResourcetypeInner(rustical_dav::namespace::NS_CALDAV, "calendar"), ResourcetypeInner(Some(rustical_dav::namespace::NS_CALDAV), "calendar"),
]) ])
} else { } else {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),
ResourcetypeInner(rustical_dav::namespace::NS_CALENDARSERVER, "subscribed"), ResourcetypeInner(
Some(rustical_dav::namespace::NS_CALENDARSERVER),
"subscribed",
),
]) ])
} }
} }
@@ -149,8 +159,8 @@ impl Resource for CalendarResource {
CalendarPropName::SupportedReportSet => { CalendarPropName::SupportedReportSet => {
CalendarProp::SupportedReportSet(SupportedReportSet::default()) CalendarProp::SupportedReportSet(SupportedReportSet::default())
} }
CalendarPropName::SyncToken => CalendarProp::SyncToken(self.cal.format_synctoken()), CalendarPropName::SyncToken => CalendarProp::SyncToken(self.get_synctoken()),
CalendarPropName::Getctag => CalendarProp::Getctag(self.cal.format_synctoken()), CalendarPropName::Getctag => CalendarProp::Getctag(self.get_synctoken()),
CalendarPropName::Source => { CalendarPropName::Source => {
CalendarProp::Source(self.cal.subscription_url.to_owned().map(HrefElement::from)) CalendarProp::Source(self.cal.subscription_url.to_owned().map(HrefElement::from))
} }

View File

@@ -9,10 +9,10 @@ use rustical_dav::{
xml::Resourcetype, xml::Resourcetype,
}; };
use rustical_store::{auth::User, CalendarObject, CalendarStore}; use rustical_store::{auth::User, CalendarObject, CalendarStore};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize};
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr};
pub struct CalendarObjectResourceService<C: CalendarStore> { pub struct CalendarObjectResourceService<C: CalendarStore> {
cal_store: Arc<C>, cal_store: Arc<C>,
@@ -24,10 +24,10 @@ impl<C: CalendarStore> CalendarObjectResourceService<C> {
} }
} }
#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)]
#[strum_discriminants( #[strum_discriminants(
name(CalendarObjectPropName), name(CalendarObjectPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
pub enum CalendarObjectProp { pub enum CalendarObjectProp {

View File

@@ -8,25 +8,25 @@ use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; 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::{EnumVariants, XmlDeserialize, XmlSerialize};
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr};
#[derive(Clone)] #[derive(Clone)]
pub struct CalendarSetResource { pub struct CalendarSetResource {
pub(crate) principal: String, pub(crate) principal: String,
} }
#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)]
#[strum_discriminants( #[strum_discriminants(
name(PrincipalPropName), name(PrincipalPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
pub enum PrincipalProp { pub enum PrincipalProp {
// WebDAV Access Control (RFC 3744) // WebDAV Access Control (RFC 3744)
#[strum_discriminants(strum(serialize = "principal-URL"))] #[strum_discriminants(strum(serialize = "principal-URL"))]
#[xml(ns = "rustical_dav::namespace::NS_DAV")] #[xml(ns = "rustical_dav::namespace::NS_DAV", rename = b"principal-URL")]
PrincipalUrl(HrefElement), PrincipalUrl(HrefElement),
} }
@@ -38,7 +38,7 @@ impl Resource for CalendarSetResource {
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ResourcetypeInner( Resourcetype(&[ResourcetypeInner(
rustical_dav::namespace::NS_DAV, Some(rustical_dav::namespace::NS_DAV),
"collection", "collection",
)]) )])
} }

View File

@@ -6,8 +6,8 @@ use rustical_dav::privileges::UserPrivilegeSet;
use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize};
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr};
#[derive(Clone)] #[derive(Clone)]
pub struct PrincipalResource { pub struct PrincipalResource {
@@ -18,10 +18,10 @@ pub struct PrincipalResource {
#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)]
pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] Vec<HrefElement>); pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] Vec<HrefElement>);
#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)]
#[strum_discriminants( #[strum_discriminants(
name(PrincipalPropName), name(PrincipalPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
pub enum PrincipalProp { pub enum PrincipalProp {
@@ -36,7 +36,7 @@ pub enum PrincipalProp {
// WebDAV Access Control (RFC 3744) // WebDAV Access Control (RFC 3744)
#[strum_discriminants(strum(serialize = "principal-URL"))] #[strum_discriminants(strum(serialize = "principal-URL"))]
#[xml(ns = "rustical_dav::namespace::NS_DAV")] #[xml(ns = "rustical_dav::namespace::NS_DAV", rename = b"principal-URL")]
PrincipalUrl(HrefElement), PrincipalUrl(HrefElement),
// CalDAV (RFC 4791) // CalDAV (RFC 4791)
@@ -64,8 +64,8 @@ impl Resource for PrincipalResource {
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "principal"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "principal"),
]) ])
} }

View File

@@ -8,10 +8,10 @@ use rustical_dav::{
xml::Resourcetype, xml::Resourcetype,
}; };
use rustical_store::{auth::User, AddressObject, AddressbookStore}; use rustical_store::{auth::User, AddressObject, AddressbookStore};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize};
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr};
use super::methods::{get_object, put_object}; use super::methods::{get_object, put_object};
@@ -20,10 +20,10 @@ pub struct AddressObjectResourceService<AS: AddressbookStore> {
addr_store: Arc<AS>, addr_store: Arc<AS>,
} }
#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)]
#[strum_discriminants( #[strum_discriminants(
name(AddressObjectPropName), name(AddressObjectPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
pub enum AddressObjectProp { pub enum AddressObjectProp {

View File

@@ -16,14 +16,13 @@ use rustical_dav::resource::{Resource, ResourceService};
use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_store::{Addressbook, AddressbookStore, SubscriptionStore}; use rustical_store::{Addressbook, AddressbookStore, SubscriptionStore};
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr};
pub struct AddressbookResourceService<AS: AddressbookStore, S: SubscriptionStore> pub struct AddressbookResourceService<AS: AddressbookStore, S: SubscriptionStore> {
{
addr_store: Arc<AS>, addr_store: Arc<AS>,
__phantom_sub: PhantomData<S>, __phantom_sub: PhantomData<S>,
} }
@@ -37,10 +36,10 @@ impl<A: AddressbookStore, S: SubscriptionStore> AddressbookResourceService<A, S>
} }
} }
#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)]
#[strum_discriminants( #[strum_discriminants(
name(AddressbookPropName), name(AddressbookPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
pub enum AddressbookProp { pub enum AddressbookProp {
@@ -87,8 +86,8 @@ impl Resource for AddressbookResource {
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),
ResourcetypeInner(rustical_dav::namespace::NS_CARDDAV, "addressbook"), ResourcetypeInner(Some(rustical_dav::namespace::NS_CARDDAV), "addressbook"),
]) ])
} }

View File

@@ -7,9 +7,9 @@ use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; 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::{EnumVariants, XmlDeserialize, XmlSerialize};
use std::sync::Arc; use std::sync::Arc;
use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; use strum::{EnumDiscriminants, EnumString, IntoStaticStr};
pub struct PrincipalResourceService<A: AddressbookStore> { pub struct PrincipalResourceService<A: AddressbookStore> {
addr_store: Arc<A>, addr_store: Arc<A>,
@@ -26,10 +26,10 @@ pub struct PrincipalResource {
principal: String, principal: String,
} }
#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)]
#[strum_discriminants( #[strum_discriminants(
name(PrincipalPropName), name(PrincipalPropName),
derive(EnumString, VariantNames, IntoStaticStr), derive(EnumString, IntoStaticStr),
strum(serialize_all = "kebab-case") strum(serialize_all = "kebab-case")
)] )]
pub enum PrincipalProp { pub enum PrincipalProp {
@@ -69,8 +69,8 @@ impl Resource for PrincipalResource {
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ Resourcetype(&[
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"),
ResourcetypeInner(rustical_dav::namespace::NS_DAV, "principal"), ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "principal"),
]) ])
} }

View File

@@ -8,6 +8,7 @@ use crate::Error;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::web::Data; use actix_web::web::Data;
use actix_web::{web::Path, HttpRequest}; use actix_web::{web::Path, HttpRequest};
use itertools::Itertools;
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_xml::Unparsed; use rustical_xml::Unparsed;
use rustical_xml::XmlDeserialize; use rustical_xml::XmlDeserialize;
@@ -98,20 +99,27 @@ pub(crate) async fn route_proppatch<R: ResourceService>(
let propname: <R::Resource as Resource>::PropName = prop.clone().into(); let propname: <R::Resource as Resource>::PropName = prop.clone().into();
let propname: &str = propname.into(); let propname: &str = propname.into();
match resource.set_prop(prop) { match resource.set_prop(prop) {
Ok(()) => props_ok.push(propname.to_owned()), Ok(()) => props_ok.push((None, propname.to_owned())),
Err(Error::PropReadOnly) => props_conflict.push(propname.to_owned()), Err(Error::PropReadOnly) => {
props_conflict.push((None, 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 <R::Resource as Resource>::list_props().contains(&propname.as_str()) { if <R::Resource as Resource>::list_props()
.into_iter()
.map(|(_ns, tag)| tag)
.collect_vec()
.contains(&propname.as_str())
{
// 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(propname) props_conflict.push((None, propname))
} else { } else {
props_not_found.push(propname); props_not_found.push((None, propname));
} }
} }
} }
@@ -120,12 +128,12 @@ pub(crate) async fn route_proppatch<R: ResourceService>(
let propname = remove_el.prop.0 .0; let propname = remove_el.prop.0 .0;
match <<R::Resource as Resource>::PropName as FromStr>::from_str(&propname) { match <<R::Resource as Resource>::PropName as FromStr>::from_str(&propname) {
Ok(prop) => match resource.remove_prop(&prop) { Ok(prop) => match resource.remove_prop(&prop) {
Ok(()) => props_ok.push(propname), Ok(()) => props_ok.push((None, propname)),
Err(Error::PropReadOnly) => props_conflict.push(propname), Err(Error::PropReadOnly) => props_conflict.push((None, propname)),
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}, },
// I guess removing a nonexisting property should be successful :) // I guess removing a nonexisting property should be successful :)
Err(_) => props_ok.push(propname), Err(_) => props_ok.push((None, propname)),
}; };
} }
} }

View File

@@ -6,11 +6,12 @@ use crate::Error;
use actix_web::dev::ResourceMap; use actix_web::dev::ResourceMap;
use actix_web::{http::StatusCode, ResponseError}; use actix_web::{http::StatusCode, ResponseError};
use itertools::Itertools; use itertools::Itertools;
use quick_xml::name::Namespace;
pub use resource_service::ResourceService; pub use resource_service::ResourceService;
use rustical_store::auth::User; use rustical_store::auth::User;
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize};
use std::str::FromStr; use std::str::FromStr;
use strum::{EnumString, VariantNames}; use strum::EnumString;
mod methods; mod methods;
mod resource_service; mod resource_service;
@@ -20,10 +21,10 @@ pub use resource_service::*;
pub trait ResourceProp: XmlSerialize + XmlDeserialize {} pub trait ResourceProp: XmlSerialize + XmlDeserialize {}
impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {} impl<T: XmlSerialize + XmlDeserialize> ResourceProp for T {}
pub trait ResourcePropName: FromStr + VariantNames {} pub trait ResourcePropName: FromStr {}
impl<T: FromStr + VariantNames> ResourcePropName for T {} impl<T: FromStr> ResourcePropName for T {}
#[derive(XmlDeserialize, XmlSerialize, PartialEq)] #[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumVariants)]
pub enum CommonPropertiesProp { pub enum CommonPropertiesProp {
// WebDAV (RFC 2518) // WebDAV (RFC 2518)
#[xml(skip_deserializing)] #[xml(skip_deserializing)]
@@ -49,7 +50,7 @@ pub enum EitherProp<Left: ResourceProp, Right: ResourceProp> {
Right(Right), Right(Right),
} }
#[derive(EnumString, VariantNames, Clone)] #[derive(EnumString, Clone)]
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]
pub enum CommonPropertiesPropName { pub enum CommonPropertiesPropName {
Resourcetype, Resourcetype,
@@ -60,14 +61,18 @@ pub enum CommonPropertiesPropName {
pub trait Resource: Clone + 'static { pub trait Resource: Clone + 'static {
type PropName: ResourcePropName + From<Self::Prop> + Into<&'static str>; type PropName: ResourcePropName + From<Self::Prop> + Into<&'static str>;
type Prop: ResourceProp + PartialEq + Clone; type Prop: ResourceProp + PartialEq + Clone + EnumVariants;
type Error: ResponseError + From<crate::Error>; type Error: ResponseError + From<crate::Error>;
type PrincipalResource: Resource + NamedRoute; type PrincipalResource: Resource + NamedRoute;
fn get_resourcetype(&self) -> Resourcetype; fn get_resourcetype(&self) -> Resourcetype;
fn list_props() -> Vec<&'static str> { fn list_props() -> Vec<(Option<Namespace<'static>>, &'static str)> {
[Self::PropName::VARIANTS, CommonPropertiesPropName::VARIANTS].concat() [
Self::Prop::variant_names(),
CommonPropertiesProp::variant_names(),
]
.concat()
} }
fn get_internal_prop( fn get_internal_prop(
@@ -137,9 +142,10 @@ pub trait Resource: Clone + 'static {
Error::BadRequest("propname MUST be the only queried prop".to_owned()).into(), Error::BadRequest("propname MUST be the only queried prop".to_owned()).into(),
); );
} }
let props = Self::list_props() let props = Self::list_props()
.into_iter() .into_iter()
.map(str::to_string) .map(|(ns, tag)| (ns.to_owned(), tag.to_string()))
.collect_vec(); .collect_vec();
return Ok(ResponseElement { return Ok(ResponseElement {
@@ -159,7 +165,10 @@ pub trait Resource: Clone + 'static {
Error::BadRequest("allprop MUST be the only queried prop".to_owned()).into(), Error::BadRequest("allprop MUST be the only queried prop".to_owned()).into(),
); );
} }
props = Self::list_props(); props = Self::list_props()
.into_iter()
.map(|(_ns, tag)| tag)
.collect();
} }
let mut valid_props = vec![]; let mut valid_props = vec![];
@@ -195,7 +204,11 @@ pub trait Resource: Clone + 'static {
if !invalid_props.is_empty() { if !invalid_props.is_empty() {
propstats.push(PropstatWrapper::TagList(PropstatElement { propstats.push(PropstatWrapper::TagList(PropstatElement {
status: StatusCode::NOT_FOUND, status: StatusCode::NOT_FOUND,
prop: invalid_props.into(), prop: invalid_props
.into_iter()
.map(|tag| (None, tag))
.collect_vec()
.into(),
})); }));
} }
Ok(ResponseElement { Ok(ResponseElement {

View File

@@ -4,10 +4,10 @@ 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;
use rustical_xml::{XmlDeserialize, XmlSerialize}; use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize};
use serde::Serialize; use serde::Serialize;
use std::marker::PhantomData; use std::marker::PhantomData;
use strum::{EnumString, IntoStaticStr, VariantNames}; use strum::{EnumString, IntoStaticStr};
#[derive(Clone)] #[derive(Clone)]
pub struct RootResource<PR: Resource>(PhantomData<PR>); pub struct RootResource<PR: Resource>(PhantomData<PR>);
@@ -18,11 +18,11 @@ impl<PR: Resource> Default for RootResource<PR> {
} }
} }
#[derive(EnumString, VariantNames, Clone, IntoStaticStr)] #[derive(EnumString, Clone, IntoStaticStr)]
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]
pub enum RootResourcePropName {} pub enum RootResourcePropName {}
#[derive(XmlDeserialize, XmlSerialize, Serialize, Clone, PartialEq)] #[derive(XmlDeserialize, XmlSerialize, Serialize, Clone, PartialEq, EnumVariants)]
pub enum RootResourceProp {} pub enum RootResourceProp {}
impl From<RootResourceProp> for RootResourcePropName { impl From<RootResourceProp> for RootResourcePropName {
@@ -38,7 +38,10 @@ impl<PR: Resource + NamedRoute> Resource for RootResource<PR> {
type PrincipalResource = PR; type PrincipalResource = PR;
fn get_resourcetype(&self) -> Resourcetype { fn get_resourcetype(&self) -> Resourcetype {
Resourcetype(&[ResourcetypeInner(crate::namespace::NS_DAV, "collection")]) Resourcetype(&[ResourcetypeInner(
Some(crate::namespace::NS_DAV),
"collection",
)])
} }
fn get_prop( fn get_prop(

View File

@@ -5,7 +5,7 @@ pub struct Resourcetype(#[xml(flatten, ty = "untagged")] pub &'static [Resourcet
#[derive(Debug, Clone, PartialEq, XmlSerialize)] #[derive(Debug, Clone, PartialEq, XmlSerialize)]
pub struct ResourcetypeInner( pub struct ResourcetypeInner(
#[xml(ty = "namespace")] pub quick_xml::name::Namespace<'static>, #[xml(ty = "namespace")] pub Option<quick_xml::name::Namespace<'static>>,
#[xml(ty = "tag_name")] pub &'static str, #[xml(ty = "tag_name")] pub &'static str,
); );
@@ -27,8 +27,8 @@ mod tests {
let mut writer = quick_xml::Writer::new(&mut buf); let mut writer = quick_xml::Writer::new(&mut buf);
Document { Document {
resourcetype: Resourcetype(&[ resourcetype: Resourcetype(&[
ResourcetypeInner(crate::namespace::NS_DAV, "displayname"), ResourcetypeInner(Some(crate::namespace::NS_DAV), "displayname"),
ResourcetypeInner(crate::namespace::NS_CALENDARSERVER, "calendar-color"), ResourcetypeInner(Some(crate::namespace::NS_CALENDARSERVER), "calendar-color"),
]), ]),
} }
.serialize_root(&mut writer) .serialize_root(&mut writer)

View File

@@ -4,7 +4,7 @@ use rustical_xml::XmlSerialize;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Clone, Debug, PartialEq, From)] #[derive(Clone, Debug, PartialEq, From)]
pub struct TagList(Vec<String>); pub struct TagList(Vec<(Option<Namespace<'static>>, String)>);
impl XmlSerialize for TagList { impl XmlSerialize for TagList {
fn serialize<W: std::io::Write>( fn serialize<W: std::io::Write>(
@@ -18,9 +18,18 @@ impl XmlSerialize for TagList {
struct Inner(#[xml(ty = "untagged", flatten)] Vec<Tag>); struct Inner(#[xml(ty = "untagged", flatten)] Vec<Tag>);
#[derive(Debug, XmlSerialize, PartialEq)] #[derive(Debug, XmlSerialize, PartialEq)]
struct Tag(#[xml(ty = "tag_name")] String); struct Tag(
Inner(self.0.iter().map(|t| Tag(t.to_owned())).collect()) #[xml(ty = "namespace")] Option<Namespace<'static>>,
.serialize(ns, tag, namespaces, writer) #[xml(ty = "tag_name")] String,
);
Inner(
self.0
.iter()
.map(|(ns, tag)| Tag(ns.to_owned(), tag.to_owned()))
.collect(),
)
.serialize(ns, tag, namespaces, writer)
} }
#[allow(refining_impl_trait)] #[allow(refining_impl_trait)]