Mandate that attribute namespaces are valid prefixes
This commit is contained in:
parent
570cdc81a5
commit
b30859929b
|
@ -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),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>"
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue