Mandate that attribute namespaces are valid prefixes

This commit is contained in:
Dirkjan Ochtman 2022-09-07 17:40:40 +02:00
parent 570cdc81a5
commit b30859929b
4 changed files with 85 additions and 4 deletions

View File

@ -4,6 +4,7 @@ mod de;
mod ser; mod ser;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt;
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
use quote::ToTokens; 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) { fn discard_lifetimes(ty: &mut syn::Type) {
match ty { match ty {
syn::Type::Path(ty) => discard_path_lifetimes(ty), syn::Type::Path(ty) => discard_path_lifetimes(ty),

View File

@ -1,5 +1,8 @@
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use syn::spanned::Spanned;
use crate::Namespace;
use super::{discard_lifetimes, ContainerMeta, FieldMeta}; use super::{discard_lifetimes, ContainerMeta, FieldMeta};
@ -85,11 +88,46 @@ fn process_named_field(
) { ) {
let name = field.ident.as_ref().unwrap().to_string(); let name = field.ident.as_ref().unwrap().to_string();
let field_value = field.ident.as_ref().unwrap(); let field_value = field.ident.as_ref().unwrap();
let field_meta = FieldMeta::from_field(field); let field_meta = FieldMeta::from_field(field);
let default_ns = &meta.ns.uri;
if field_meta.attribute { 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!( attributes.extend(quote!(
serializer.write_attr(#name, &self.#field_value)?; #error
serializer.write_attr(#name, #ns, &self.#field_value)?;
)); ));
return; return;
} }

View File

@ -63,12 +63,24 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
Ok(prefix) Ok(prefix)
} }
pub fn write_attr<V: ToXml + ?Sized>(&mut self, name: &str, value: &V) -> Result<(), Error> { pub fn write_attr<V: ToXml + ?Sized>(
&mut self,
name: &str,
ns: &str,
value: &V,
) -> Result<(), Error> {
if self.state != State::Attribute { if self.state != State::Attribute {
return Err(Error::UnexpectedState); 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; self.state = State::Scalar;
value.serialize(self)?; value.serialize(self)?;
self.state = State::Attribute; self.state = State::Attribute;

View File

@ -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(),
"<NoPrefixAttrNs xmlns:bar=\"BAR\" bar:flag=\"true\"></NoPrefixAttrNs>"
);
}