From ea9f5a711d45a4d1ef8b5b28fb063f65cd0473e9 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:56:37 +0100 Subject: [PATCH] Add namespaces to propnames --- crates/caldav/src/calendar/resource.rs | 28 +++++++++----- crates/caldav/src/calendar_object/resource.rs | 8 ++-- crates/caldav/src/calendar_set/mod.rs | 12 +++--- crates/caldav/src/principal/mod.rs | 14 +++---- crates/carddav/src/address_object/resource.rs | 8 ++-- crates/carddav/src/addressbook/resource.rs | 15 ++++---- crates/carddav/src/principal/mod.rs | 12 +++--- crates/dav/src/resource/methods/proppatch.rs | 24 ++++++++---- crates/dav/src/resource/mod.rs | 37 +++++++++++++------ crates/dav/src/resources/root.rs | 13 ++++--- crates/dav/src/xml/resourcetype.rs | 6 +-- crates/dav/src/xml/tag_list.rs | 17 +++++++-- 12 files changed, 118 insertions(+), 76 deletions(-) diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 6699d2e..12311e5 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -16,16 +16,17 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; use rustical_store::{Calendar, CalendarStore, SubscriptionStore}; +use rustical_xml::EnumVariants; use rustical_xml::{XmlDeserialize, XmlSerialize}; use std::marker::PhantomData; use std::str::FromStr; 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( name(CalendarPropName), - derive(EnumString, VariantNames, IntoStaticStr), + derive(EnumString, IntoStaticStr), strum(serialize_all = "kebab-case") )] pub enum CalendarProp { @@ -89,6 +90,12 @@ impl From for Calendar { } } +impl CalendarResource { + fn get_synctoken(&self) -> String { + self.cal.format_synctoken() + } +} + impl Resource for CalendarResource { type PropName = CalendarPropName; type Prop = CalendarProp; @@ -98,13 +105,16 @@ impl Resource for CalendarResource { fn get_resourcetype(&self) -> Resourcetype { if self.cal.subscription_url.is_none() { Resourcetype(&[ - ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), - ResourcetypeInner(rustical_dav::namespace::NS_CALDAV, "calendar"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_CALDAV), "calendar"), ]) } else { Resourcetype(&[ - ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), - ResourcetypeInner(rustical_dav::namespace::NS_CALENDARSERVER, "subscribed"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), + ResourcetypeInner( + Some(rustical_dav::namespace::NS_CALENDARSERVER), + "subscribed", + ), ]) } } @@ -149,8 +159,8 @@ impl Resource for CalendarResource { CalendarPropName::SupportedReportSet => { CalendarProp::SupportedReportSet(SupportedReportSet::default()) } - CalendarPropName::SyncToken => CalendarProp::SyncToken(self.cal.format_synctoken()), - CalendarPropName::Getctag => CalendarProp::Getctag(self.cal.format_synctoken()), + CalendarPropName::SyncToken => CalendarProp::SyncToken(self.get_synctoken()), + CalendarPropName::Getctag => CalendarProp::Getctag(self.get_synctoken()), CalendarPropName::Source => { CalendarProp::Source(self.cal.subscription_url.to_owned().map(HrefElement::from)) } diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 1200370..6b77e64 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -9,10 +9,10 @@ use rustical_dav::{ xml::Resourcetype, }; use rustical_store::{auth::User, CalendarObject, CalendarStore}; -use rustical_xml::{XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; use serde::Deserialize; use std::sync::Arc; -use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; +use strum::{EnumDiscriminants, EnumString, IntoStaticStr}; pub struct CalendarObjectResourceService { cal_store: Arc, @@ -24,10 +24,10 @@ impl CalendarObjectResourceService { } } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)] #[strum_discriminants( name(CalendarObjectPropName), - derive(EnumString, VariantNames, IntoStaticStr), + derive(EnumString, IntoStaticStr), strum(serialize_all = "kebab-case") )] pub enum CalendarObjectProp { diff --git a/crates/caldav/src/calendar_set/mod.rs b/crates/caldav/src/calendar_set/mod.rs index 01c71ef..6ebe467 100644 --- a/crates/caldav/src/calendar_set/mod.rs +++ b/crates/caldav/src/calendar_set/mod.rs @@ -8,25 +8,25 @@ use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; use rustical_store::CalendarStore; -use rustical_xml::{XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; use std::sync::Arc; -use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; +use strum::{EnumDiscriminants, EnumString, IntoStaticStr}; #[derive(Clone)] pub struct CalendarSetResource { pub(crate) principal: String, } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)] #[strum_discriminants( name(PrincipalPropName), - derive(EnumString, VariantNames, IntoStaticStr), + derive(EnumString, IntoStaticStr), strum(serialize_all = "kebab-case") )] pub enum PrincipalProp { // WebDAV Access Control (RFC 3744) #[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), } @@ -38,7 +38,7 @@ impl Resource for CalendarSetResource { fn get_resourcetype(&self) -> Resourcetype { Resourcetype(&[ResourcetypeInner( - rustical_dav::namespace::NS_DAV, + Some(rustical_dav::namespace::NS_DAV), "collection", )]) } diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 1bbafa7..b28040d 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -6,8 +6,8 @@ use rustical_dav::privileges::UserPrivilegeSet; use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; -use rustical_xml::{XmlDeserialize, XmlSerialize}; -use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; +use strum::{EnumDiscriminants, EnumString, IntoStaticStr}; #[derive(Clone)] pub struct PrincipalResource { @@ -18,10 +18,10 @@ pub struct PrincipalResource { #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] Vec); -#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)] #[strum_discriminants( name(PrincipalPropName), - derive(EnumString, VariantNames, IntoStaticStr), + derive(EnumString, IntoStaticStr), strum(serialize_all = "kebab-case") )] pub enum PrincipalProp { @@ -36,7 +36,7 @@ pub enum PrincipalProp { // WebDAV Access Control (RFC 3744) #[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), // CalDAV (RFC 4791) @@ -64,8 +64,8 @@ impl Resource for PrincipalResource { fn get_resourcetype(&self) -> Resourcetype { Resourcetype(&[ - ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), - ResourcetypeInner(rustical_dav::namespace::NS_DAV, "principal"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "principal"), ]) } diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index d586f73..aba63c6 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -8,10 +8,10 @@ use rustical_dav::{ xml::Resourcetype, }; use rustical_store::{auth::User, AddressObject, AddressbookStore}; -use rustical_xml::{XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; use serde::Deserialize; use std::sync::Arc; -use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; +use strum::{EnumDiscriminants, EnumString, IntoStaticStr}; use super::methods::{get_object, put_object}; @@ -20,10 +20,10 @@ pub struct AddressObjectResourceService { addr_store: Arc, } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)] #[strum_discriminants( name(AddressObjectPropName), - derive(EnumString, VariantNames, IntoStaticStr), + derive(EnumString, IntoStaticStr), strum(serialize_all = "kebab-case") )] pub enum AddressObjectProp { diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index 4f4fd2a..de2d3bd 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -16,14 +16,13 @@ use rustical_dav::resource::{Resource, ResourceService}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; use rustical_store::{Addressbook, AddressbookStore, SubscriptionStore}; -use rustical_xml::{XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; use std::marker::PhantomData; use std::str::FromStr; use std::sync::Arc; -use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; +use strum::{EnumDiscriminants, EnumString, IntoStaticStr}; -pub struct AddressbookResourceService -{ +pub struct AddressbookResourceService { addr_store: Arc, __phantom_sub: PhantomData, } @@ -37,10 +36,10 @@ impl AddressbookResourceService } } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)] #[strum_discriminants( name(AddressbookPropName), - derive(EnumString, VariantNames, IntoStaticStr), + derive(EnumString, IntoStaticStr), strum(serialize_all = "kebab-case") )] pub enum AddressbookProp { @@ -87,8 +86,8 @@ impl Resource for AddressbookResource { fn get_resourcetype(&self) -> Resourcetype { Resourcetype(&[ - ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), - ResourcetypeInner(rustical_dav::namespace::NS_CARDDAV, "addressbook"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_CARDDAV), "addressbook"), ]) } diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index 9d038e2..ebfb275 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -7,9 +7,9 @@ use rustical_dav::resource::{NamedRoute, Resource, ResourceService}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; use rustical_store::AddressbookStore; -use rustical_xml::{XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; use std::sync::Arc; -use strum::{EnumDiscriminants, EnumString, IntoStaticStr, VariantNames}; +use strum::{EnumDiscriminants, EnumString, IntoStaticStr}; pub struct PrincipalResourceService { addr_store: Arc, @@ -26,10 +26,10 @@ pub struct PrincipalResource { principal: String, } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumDiscriminants, Clone, EnumVariants)] #[strum_discriminants( name(PrincipalPropName), - derive(EnumString, VariantNames, IntoStaticStr), + derive(EnumString, IntoStaticStr), strum(serialize_all = "kebab-case") )] pub enum PrincipalProp { @@ -69,8 +69,8 @@ impl Resource for PrincipalResource { fn get_resourcetype(&self) -> Resourcetype { Resourcetype(&[ - ResourcetypeInner(rustical_dav::namespace::NS_DAV, "collection"), - ResourcetypeInner(rustical_dav::namespace::NS_DAV, "principal"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "collection"), + ResourcetypeInner(Some(rustical_dav::namespace::NS_DAV), "principal"), ]) } diff --git a/crates/dav/src/resource/methods/proppatch.rs b/crates/dav/src/resource/methods/proppatch.rs index 453046d..8613228 100644 --- a/crates/dav/src/resource/methods/proppatch.rs +++ b/crates/dav/src/resource/methods/proppatch.rs @@ -8,6 +8,7 @@ use crate::Error; use actix_web::http::StatusCode; use actix_web::web::Data; use actix_web::{web::Path, HttpRequest}; +use itertools::Itertools; use rustical_store::auth::User; use rustical_xml::Unparsed; use rustical_xml::XmlDeserialize; @@ -98,20 +99,27 @@ pub(crate) async fn route_proppatch( let propname: ::PropName = prop.clone().into(); let propname: &str = propname.into(); match resource.set_prop(prop) { - Ok(()) => props_ok.push(propname.to_owned()), - Err(Error::PropReadOnly) => props_conflict.push(propname.to_owned()), + Ok(()) => props_ok.push((None, propname.to_owned())), + Err(Error::PropReadOnly) => { + props_conflict.push((None, propname.to_owned())) + } Err(err) => return Err(err.into()), }; } SetPropertyPropWrapper::Invalid(invalid) => { let propname = invalid.tag_name(); - if ::list_props().contains(&propname.as_str()) { + if ::list_props() + .into_iter() + .map(|(_ns, tag)| tag) + .collect_vec() + .contains(&propname.as_str()) + { // This happens in following cases: // - read-only properties with #[serde(skip_deserializing)] // - internal properties - props_conflict.push(propname) + props_conflict.push((None, propname)) } else { - props_not_found.push(propname); + props_not_found.push((None, propname)); } } } @@ -120,12 +128,12 @@ pub(crate) async fn route_proppatch( let propname = remove_el.prop.0 .0; match <::PropName as FromStr>::from_str(&propname) { Ok(prop) => match resource.remove_prop(&prop) { - Ok(()) => props_ok.push(propname), - Err(Error::PropReadOnly) => props_conflict.push(propname), + Ok(()) => props_ok.push((None, propname)), + Err(Error::PropReadOnly) => props_conflict.push((None, propname)), Err(err) => return Err(err.into()), }, // I guess removing a nonexisting property should be successful :) - Err(_) => props_ok.push(propname), + Err(_) => props_ok.push((None, propname)), }; } } diff --git a/crates/dav/src/resource/mod.rs b/crates/dav/src/resource/mod.rs index bf9de61..7311381 100644 --- a/crates/dav/src/resource/mod.rs +++ b/crates/dav/src/resource/mod.rs @@ -6,11 +6,12 @@ use crate::Error; use actix_web::dev::ResourceMap; use actix_web::{http::StatusCode, ResponseError}; use itertools::Itertools; +use quick_xml::name::Namespace; pub use resource_service::ResourceService; use rustical_store::auth::User; -use rustical_xml::{XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; use std::str::FromStr; -use strum::{EnumString, VariantNames}; +use strum::EnumString; mod methods; mod resource_service; @@ -20,10 +21,10 @@ pub use resource_service::*; pub trait ResourceProp: XmlSerialize + XmlDeserialize {} impl ResourceProp for T {} -pub trait ResourcePropName: FromStr + VariantNames {} -impl ResourcePropName for T {} +pub trait ResourcePropName: FromStr {} +impl ResourcePropName for T {} -#[derive(XmlDeserialize, XmlSerialize, PartialEq)] +#[derive(XmlDeserialize, XmlSerialize, PartialEq, EnumVariants)] pub enum CommonPropertiesProp { // WebDAV (RFC 2518) #[xml(skip_deserializing)] @@ -49,7 +50,7 @@ pub enum EitherProp { Right(Right), } -#[derive(EnumString, VariantNames, Clone)] +#[derive(EnumString, Clone)] #[strum(serialize_all = "kebab-case")] pub enum CommonPropertiesPropName { Resourcetype, @@ -60,14 +61,18 @@ pub enum CommonPropertiesPropName { pub trait Resource: Clone + 'static { type PropName: ResourcePropName + From + Into<&'static str>; - type Prop: ResourceProp + PartialEq + Clone; + type Prop: ResourceProp + PartialEq + Clone + EnumVariants; type Error: ResponseError + From; type PrincipalResource: Resource + NamedRoute; fn get_resourcetype(&self) -> Resourcetype; - fn list_props() -> Vec<&'static str> { - [Self::PropName::VARIANTS, CommonPropertiesPropName::VARIANTS].concat() + fn list_props() -> Vec<(Option>, &'static str)> { + [ + Self::Prop::variant_names(), + CommonPropertiesProp::variant_names(), + ] + .concat() } 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(), ); } + let props = Self::list_props() .into_iter() - .map(str::to_string) + .map(|(ns, tag)| (ns.to_owned(), tag.to_string())) .collect_vec(); return Ok(ResponseElement { @@ -159,7 +165,10 @@ pub trait Resource: Clone + 'static { 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![]; @@ -195,7 +204,11 @@ pub trait Resource: Clone + 'static { if !invalid_props.is_empty() { propstats.push(PropstatWrapper::TagList(PropstatElement { status: StatusCode::NOT_FOUND, - prop: invalid_props.into(), + prop: invalid_props + .into_iter() + .map(|tag| (None, tag)) + .collect_vec() + .into(), })); } Ok(ResponseElement { diff --git a/crates/dav/src/resources/root.rs b/crates/dav/src/resources/root.rs index 24da4d6..f88f41e 100644 --- a/crates/dav/src/resources/root.rs +++ b/crates/dav/src/resources/root.rs @@ -4,10 +4,10 @@ use crate::xml::{Resourcetype, ResourcetypeInner}; use actix_web::dev::ResourceMap; use async_trait::async_trait; use rustical_store::auth::User; -use rustical_xml::{XmlDeserialize, XmlSerialize}; +use rustical_xml::{EnumVariants, XmlDeserialize, XmlSerialize}; use serde::Serialize; use std::marker::PhantomData; -use strum::{EnumString, IntoStaticStr, VariantNames}; +use strum::{EnumString, IntoStaticStr}; #[derive(Clone)] pub struct RootResource(PhantomData); @@ -18,11 +18,11 @@ impl Default for RootResource { } } -#[derive(EnumString, VariantNames, Clone, IntoStaticStr)] +#[derive(EnumString, Clone, IntoStaticStr)] #[strum(serialize_all = "kebab-case")] pub enum RootResourcePropName {} -#[derive(XmlDeserialize, XmlSerialize, Serialize, Clone, PartialEq)] +#[derive(XmlDeserialize, XmlSerialize, Serialize, Clone, PartialEq, EnumVariants)] pub enum RootResourceProp {} impl From for RootResourcePropName { @@ -38,7 +38,10 @@ impl Resource for RootResource { type PrincipalResource = PR; fn get_resourcetype(&self) -> Resourcetype { - Resourcetype(&[ResourcetypeInner(crate::namespace::NS_DAV, "collection")]) + Resourcetype(&[ResourcetypeInner( + Some(crate::namespace::NS_DAV), + "collection", + )]) } fn get_prop( diff --git a/crates/dav/src/xml/resourcetype.rs b/crates/dav/src/xml/resourcetype.rs index 1f54e54..fe106b5 100644 --- a/crates/dav/src/xml/resourcetype.rs +++ b/crates/dav/src/xml/resourcetype.rs @@ -5,7 +5,7 @@ pub struct Resourcetype(#[xml(flatten, ty = "untagged")] pub &'static [Resourcet #[derive(Debug, Clone, PartialEq, XmlSerialize)] pub struct ResourcetypeInner( - #[xml(ty = "namespace")] pub quick_xml::name::Namespace<'static>, + #[xml(ty = "namespace")] pub Option>, #[xml(ty = "tag_name")] pub &'static str, ); @@ -27,8 +27,8 @@ mod tests { let mut writer = quick_xml::Writer::new(&mut buf); Document { resourcetype: Resourcetype(&[ - ResourcetypeInner(crate::namespace::NS_DAV, "displayname"), - ResourcetypeInner(crate::namespace::NS_CALENDARSERVER, "calendar-color"), + ResourcetypeInner(Some(crate::namespace::NS_DAV), "displayname"), + ResourcetypeInner(Some(crate::namespace::NS_CALENDARSERVER), "calendar-color"), ]), } .serialize_root(&mut writer) diff --git a/crates/dav/src/xml/tag_list.rs b/crates/dav/src/xml/tag_list.rs index 59962bc..c3e7ae8 100644 --- a/crates/dav/src/xml/tag_list.rs +++ b/crates/dav/src/xml/tag_list.rs @@ -4,7 +4,7 @@ use rustical_xml::XmlSerialize; use std::collections::HashMap; #[derive(Clone, Debug, PartialEq, From)] -pub struct TagList(Vec); +pub struct TagList(Vec<(Option>, String)>); impl XmlSerialize for TagList { fn serialize( @@ -18,9 +18,18 @@ impl XmlSerialize for TagList { struct Inner(#[xml(ty = "untagged", flatten)] Vec); #[derive(Debug, XmlSerialize, PartialEq)] - struct Tag(#[xml(ty = "tag_name")] String); - Inner(self.0.iter().map(|t| Tag(t.to_owned())).collect()) - .serialize(ns, tag, namespaces, writer) + struct Tag( + #[xml(ty = "namespace")] Option>, + #[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)]