diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 77598d9..90c38f0 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -4,6 +4,7 @@ mod de; mod ser; use std::collections::BTreeMap; +use std::fmt; use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; use quote::ToTokens; @@ -456,6 +457,18 @@ impl ToTokens for Namespace { } } +impl fmt::Debug for Namespace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Path(arg0) => f + .debug_tuple("Path") + .field(&arg0.into_token_stream().to_string()) + .finish(), + Self::Literal(arg0) => f.debug_tuple("Literal").field(arg0).finish(), + } + } +} + fn discard_lifetimes(ty: &mut syn::Type) { match ty { syn::Type::Path(ty) => discard_path_lifetimes(ty), diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index 6e11e13..b6ea3c9 100644 --- a/instant-xml-macros/src/ser.rs +++ b/instant-xml-macros/src/ser.rs @@ -1,5 +1,8 @@ use proc_macro2::TokenStream; use quote::quote; +use syn::spanned::Spanned; + +use crate::Namespace; use super::{discard_lifetimes, ContainerMeta, FieldMeta}; @@ -85,11 +88,46 @@ fn process_named_field( ) { let name = field.ident.as_ref().unwrap().to_string(); let field_value = field.ident.as_ref().unwrap(); - let field_meta = FieldMeta::from_field(field); + + let default_ns = &meta.ns.uri; if field_meta.attribute { + let (ns, error) = match &field_meta.ns.uri { + Some(Namespace::Path(path)) => match path.get_ident() { + Some(prefix) => match &meta.ns.prefixes.get(&prefix.to_string()) { + Some(ns) => (quote!(#ns), quote!()), + None => ( + quote!(""), + syn::Error::new( + field_meta.ns.uri.span(), + &format!("unknown prefix `{prefix}` (prefix must be defined on the field's type)"), + ) + .into_compile_error(), + ), + }, + None => ( + quote!(""), + syn::Error::new( + field_meta.ns.uri.span(), + "attribute namespace must be a prefix identifier", + ) + .into_compile_error(), + ), + }, + Some(Namespace::Literal(_)) => ( + quote!(""), + syn::Error::new( + field_meta.ns.uri.span(), + "attribute namespace must be a prefix identifier", + ) + .into_compile_error(), + ), + None => (quote!(#default_ns), quote!()), + }; + attributes.extend(quote!( - serializer.write_attr(#name, &self.#field_value)?; + #error + serializer.write_attr(#name, #ns, &self.#field_value)?; )); return; } diff --git a/instant-xml/src/ser.rs b/instant-xml/src/ser.rs index fcd7231..daf00ea 100644 --- a/instant-xml/src/ser.rs +++ b/instant-xml/src/ser.rs @@ -63,12 +63,24 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> { Ok(prefix) } - pub fn write_attr(&mut self, name: &str, value: &V) -> Result<(), Error> { + pub fn write_attr( + &mut self, + name: &str, + ns: &str, + value: &V, + ) -> Result<(), Error> { if self.state != State::Attribute { return Err(Error::UnexpectedState); } - self.output.write_fmt(format_args!(" {}=\"", name))?; + match ns == self.default_ns { + true => self.output.write_fmt(format_args!(" {name}=\""))?, + false => { + let prefix = self.prefixes.get(ns).ok_or(dbg!(Error::UnexpectedState))?; + self.output.write_fmt(format_args!(" {prefix}:{name}=\""))?; + } + } + self.state = State::Scalar; value.serialize(self)?; self.state = State::Attribute; diff --git a/instant-xml/tests/ser-attr-ns.rs b/instant-xml/tests/ser-attr-ns.rs new file mode 100644 index 0000000..603bb3f --- /dev/null +++ b/instant-xml/tests/ser-attr-ns.rs @@ -0,0 +1,18 @@ +use similar_asserts::assert_eq; + +use instant_xml::{to_string, ToXml}; + +#[derive(ToXml)] +#[xml(ns(bar = "BAR"))] +struct NoPrefixAttrNs { + #[xml(attribute, ns(bar))] + flag: bool, +} + +#[test] +fn no_prefix_attr_ns() { + assert_eq!( + to_string(&NoPrefixAttrNs { flag: true }).unwrap(), + "" + ); +}