Allow paths as namespace values
This commit is contained in:
parent
cf4e1d4e55
commit
76a260fece
|
@ -35,10 +35,9 @@ impl Deserializer {
|
||||||
pub fn new(input: &syn::DeriveInput) -> Deserializer {
|
pub fn new(input: &syn::DeriveInput) -> Deserializer {
|
||||||
let ident = &input.ident;
|
let ident = &input.ident;
|
||||||
let container_meta = ContainerMeta::from_derive(input);
|
let container_meta = ContainerMeta::from_derive(input);
|
||||||
let default_namespace = match &container_meta.ns.default {
|
let default_namespace = match &container_meta.ns.uri {
|
||||||
Namespace::Default => "",
|
Some(ns) => quote!(#ns),
|
||||||
Namespace::Prefix(_) => panic!("container namespace cannot be prefix"),
|
None => quote!(""),
|
||||||
Namespace::Literal(ns) => ns,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut xml_generics = input.generics.clone();
|
let mut xml_generics = input.generics.clone();
|
||||||
|
@ -71,12 +70,6 @@ impl Deserializer {
|
||||||
syn::Fields::Named(ref fields) => {
|
syn::Fields::Named(ref fields) => {
|
||||||
fields.named.iter().enumerate().for_each(|(index, field)| {
|
fields.named.iter().enumerate().for_each(|(index, field)| {
|
||||||
let field_meta = FieldMeta::from_field(field);
|
let field_meta = FieldMeta::from_field(field);
|
||||||
if let Namespace::Prefix(prefix) = &field_meta.ns.default {
|
|
||||||
if !container_meta.ns.prefixes.contains_key(prefix) {
|
|
||||||
panic!("unknown prefix for this type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = match field_meta.attribute {
|
let tokens = match field_meta.attribute {
|
||||||
true => &mut attributes_tokens,
|
true => &mut attributes_tokens,
|
||||||
false => &mut elements_tokens,
|
false => &mut elements_tokens,
|
||||||
|
@ -230,18 +223,15 @@ impl Deserializer {
|
||||||
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
|
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
|
||||||
tokens.enum_.extend(quote!(#enum_name,));
|
tokens.enum_.extend(quote!(#enum_name,));
|
||||||
|
|
||||||
let default_ns = match &field_meta.ns.default {
|
let default_ns = match &field_meta.ns.uri {
|
||||||
Namespace::Default => &container_meta.ns.default,
|
None => &container_meta.ns.uri,
|
||||||
_ => &field_meta.ns.default,
|
_ => &field_meta.ns.uri,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ns = match default_ns {
|
let ns = match default_ns {
|
||||||
Namespace::Default => "",
|
Some(Namespace::Path(path)) => quote!(#path),
|
||||||
Namespace::Prefix(prefix) => match container_meta.ns.prefixes.get(prefix) {
|
Some(Namespace::Literal(ns)) => quote!(#ns),
|
||||||
Some(ns) => ns,
|
None => quote!(""),
|
||||||
None => panic!("undefined prefix {prefix} in xml attribute"),
|
|
||||||
},
|
|
||||||
Namespace::Literal(ns) => ns,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tokens.consts.extend(quote!(
|
tokens.consts.extend(quote!(
|
||||||
|
|
|
@ -3,11 +3,13 @@ extern crate proc_macro;
|
||||||
mod de;
|
mod de;
|
||||||
mod ser;
|
mod ser;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use quote::quote;
|
use proc_macro2::{Delimiter, Group, Ident, Punct, TokenStream, TokenTree, Literal};
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::parse_macro_input;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::{parse_macro_input, Meta, NestedMeta};
|
use syn::token::Colon2;
|
||||||
|
|
||||||
#[proc_macro_derive(ToXml, attributes(xml))]
|
#[proc_macro_derive(ToXml, attributes(xml))]
|
||||||
pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
@ -26,7 +28,7 @@ pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Default)]
|
||||||
struct ContainerMeta {
|
struct ContainerMeta {
|
||||||
ns: NamespaceMeta,
|
ns: NamespaceMeta,
|
||||||
}
|
}
|
||||||
|
@ -36,17 +38,15 @@ impl ContainerMeta {
|
||||||
let mut meta = ContainerMeta::default();
|
let mut meta = ContainerMeta::default();
|
||||||
for item in meta_items(&input.attrs) {
|
for item in meta_items(&input.attrs) {
|
||||||
match item {
|
match item {
|
||||||
Meta::List(list) if list.path.is_ident("ns") => {
|
MetaItem::Attribute => panic!("attribute key invalid in container xml attribute"),
|
||||||
meta.ns = NamespaceMeta::from_list(&list.nested)
|
MetaItem::Ns(ns) => meta.ns = ns,
|
||||||
}
|
|
||||||
_ => panic!("invalid xml attribute syntax"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
meta
|
meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Default)]
|
||||||
struct FieldMeta {
|
struct FieldMeta {
|
||||||
attribute: bool,
|
attribute: bool,
|
||||||
ns: NamespaceMeta,
|
ns: NamespaceMeta,
|
||||||
|
@ -57,80 +57,396 @@ impl FieldMeta {
|
||||||
let mut meta = FieldMeta::default();
|
let mut meta = FieldMeta::default();
|
||||||
for item in meta_items(&input.attrs) {
|
for item in meta_items(&input.attrs) {
|
||||||
match item {
|
match item {
|
||||||
Meta::Path(path) if path.is_ident("attribute") => meta.attribute = true,
|
MetaItem::Attribute => meta.attribute = true,
|
||||||
Meta::List(list) if list.path.is_ident("ns") => {
|
MetaItem::Ns(ns) => meta.ns = ns,
|
||||||
meta.ns = NamespaceMeta::from_list(&list.nested)
|
|
||||||
}
|
|
||||||
_ => panic!("invalid xml attribute syntax"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
meta
|
meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Default)]
|
||||||
struct NamespaceMeta {
|
struct NamespaceMeta {
|
||||||
default: Namespace,
|
uri: Option<Namespace>,
|
||||||
prefixes: HashMap<String, String>,
|
prefixes: BTreeMap<String, Namespace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NamespaceMeta {
|
impl NamespaceMeta {
|
||||||
fn from_list(list: &Punctuated<NestedMeta, syn::token::Comma>) -> NamespaceMeta {
|
fn from_tokens(group: Group) -> Self {
|
||||||
let mut meta = NamespaceMeta::default();
|
let mut new = NamespaceMeta::default();
|
||||||
for (i, item) in list.iter().enumerate() {
|
let mut state = NsState::Start;
|
||||||
match item {
|
for tree in group.stream() {
|
||||||
NestedMeta::Meta(inner) => match inner {
|
state = match (state, tree) {
|
||||||
Meta::Path(path) => match path.get_ident() {
|
(NsState::Start, TokenTree::Literal(lit)) => {
|
||||||
Some(id) => meta.default = Namespace::Prefix(id.to_string()),
|
new.uri = Some(Namespace::Literal(lit));
|
||||||
None => panic!("invalid xml attribute syntax"),
|
NsState::Comma
|
||||||
|
}
|
||||||
|
(NsState::Start, TokenTree::Punct(punct)) if punct.as_char() == ':' => {
|
||||||
|
NsState::Path {
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(NsState::Start, TokenTree::Ident(id)) => NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(syn::Path::from(id)),
|
||||||
},
|
},
|
||||||
Meta::NameValue(nv) => match (nv.path.get_ident(), &nv.lit) {
|
(NsState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => {
|
||||||
(Some(id), syn::Lit::Str(lit)) => {
|
NsState::Prefix
|
||||||
meta.prefixes.insert(id.to_string(), lit.value());
|
|
||||||
}
|
}
|
||||||
_ => panic!("invalid xml attribute syntax"),
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
},
|
},
|
||||||
_ => panic!("invalid xml attribute syntax"),
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::Path {
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
},
|
},
|
||||||
NestedMeta::Lit(syn::Lit::Str(lit)) if i == 0 => {
|
(
|
||||||
meta.default = Namespace::Literal(lit.value())
|
NsState::Path {
|
||||||
|
colon1: colon1 @ Some(_),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::Path {
|
||||||
|
colon1,
|
||||||
|
colon2: Some(punct),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: Some(colon1),
|
||||||
|
colon2: Some(colon2),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Ident(id),
|
||||||
|
) => {
|
||||||
|
let path = match path {
|
||||||
|
Some(mut path) => {
|
||||||
|
path.segments.push(syn::PathSegment::from(id));
|
||||||
|
path
|
||||||
}
|
}
|
||||||
_ => panic!("invalid xml attribute syntax"),
|
None => {
|
||||||
|
let mut segments = Punctuated::new();
|
||||||
|
segments.push_value(id.into());
|
||||||
|
|
||||||
|
syn::Path {
|
||||||
|
leading_colon: Some(Colon2 {
|
||||||
|
spans: [colon1.span(), colon2.span()],
|
||||||
|
}),
|
||||||
|
segments,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
meta
|
};
|
||||||
|
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ',' => {
|
||||||
|
new.uri = Some(Namespace::Path(path));
|
||||||
|
NsState::Prefix
|
||||||
|
}
|
||||||
|
(
|
||||||
|
NsState::Path {
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == '=' => {
|
||||||
|
if path.leading_colon.is_some() {
|
||||||
|
panic!("prefix cannot be defined on a path in xml attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.segments.len() != 1 {
|
||||||
|
panic!("prefix key must be a single identifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment = path.segments.into_iter().next().unwrap();
|
||||||
|
if !segment.arguments.is_empty() {
|
||||||
|
panic!("prefix key must be a single identifier without arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
NsState::PrefixValue {
|
||||||
|
prefix: segment.ident,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(NsState::Prefix, TokenTree::Ident(id)) => NsState::Eq { prefix: id },
|
||||||
|
(NsState::Eq { prefix }, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
|
||||||
|
NsState::PrefixValue { prefix }
|
||||||
|
}
|
||||||
|
(NsState::PrefixValue { prefix }, TokenTree::Literal(lit)) => {
|
||||||
|
new.prefixes
|
||||||
|
.insert(prefix.to_string(), Namespace::Literal(lit));
|
||||||
|
NsState::Comma
|
||||||
|
}
|
||||||
|
(NsState::PrefixValue { prefix }, TokenTree::Punct(punct))
|
||||||
|
if punct.as_char() == ':' =>
|
||||||
|
{
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(NsState::PrefixValue { prefix }, TokenTree::Ident(id)) => NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(syn::Path::from(id)),
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: Some(punct),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: colon1 @ Some(_),
|
||||||
|
colon2: None,
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ':' => NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1,
|
||||||
|
colon2: Some(punct),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: Some(colon1),
|
||||||
|
colon2: Some(colon2),
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
TokenTree::Ident(id),
|
||||||
|
) => {
|
||||||
|
let path = match path {
|
||||||
|
Some(mut path) => {
|
||||||
|
path.segments.push(syn::PathSegment::from(id));
|
||||||
|
path
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut segments = Punctuated::new();
|
||||||
|
segments.push_value(id.into());
|
||||||
|
|
||||||
|
syn::Path {
|
||||||
|
leading_colon: Some(Colon2 {
|
||||||
|
spans: [colon1.span(), colon2.span()],
|
||||||
|
}),
|
||||||
|
segments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
NsState::PrefixPath {
|
||||||
|
prefix,
|
||||||
|
colon1: None,
|
||||||
|
colon2: None,
|
||||||
|
path: Some(path),
|
||||||
|
},
|
||||||
|
TokenTree::Punct(punct),
|
||||||
|
) if punct.as_char() == ',' => {
|
||||||
|
new.prefixes
|
||||||
|
.insert(prefix.to_string(), Namespace::Path(path));
|
||||||
|
NsState::Prefix
|
||||||
|
}
|
||||||
|
(state, tree) => {
|
||||||
|
panic!(
|
||||||
|
"invalid state transition while parsing ns in xml attribute ({}, {tree})",
|
||||||
|
state.name()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match state {
|
||||||
|
NsState::Start | NsState::Comma => {}
|
||||||
|
NsState::Path { colon1: None, colon2: None, path: Some(path) } => {
|
||||||
|
new.uri = Some(Namespace::Path(path));
|
||||||
|
}
|
||||||
|
NsState::PrefixPath { prefix, colon1: None, colon2: None, path: Some(path) } => {
|
||||||
|
new.prefixes.insert(prefix.to_string(), Namespace::Path(path));
|
||||||
|
}
|
||||||
|
state => panic!("invalid ns end state in xml attribute ({})", state.name()),
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn meta_items(attrs: &[syn::Attribute]) -> impl Iterator<Item = Meta> + '_ {
|
fn meta_items(attrs: &[syn::Attribute]) -> Vec<MetaItem> {
|
||||||
attrs
|
let mut items = Vec::new();
|
||||||
.iter()
|
let attr = match attrs.iter().find(|attr| attr.path.is_ident("xml")) {
|
||||||
.filter_map(|attr| {
|
Some(attr) => attr,
|
||||||
if !attr.path.is_ident("xml") {
|
None => return items,
|
||||||
return None;
|
};
|
||||||
|
|
||||||
|
let mut iter = attr.tokens.clone().into_iter();
|
||||||
|
let first = match iter.next() {
|
||||||
|
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
|
||||||
|
group.stream()
|
||||||
|
}
|
||||||
|
_ => panic!("expected parenthesized group in xml attribute"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(_) = iter.next() {
|
||||||
|
panic!("expected single token tree in xml attribute");
|
||||||
}
|
}
|
||||||
|
|
||||||
match attr.parse_meta() {
|
let mut state = MetaState::Start;
|
||||||
Ok(Meta::List(meta)) => Some(meta.nested.into_iter()),
|
for tree in first {
|
||||||
_ => panic!("unexpected xml attribute syntax"),
|
state = match (state, tree) {
|
||||||
|
(MetaState::Start, TokenTree::Ident(id)) => {
|
||||||
|
if id == "attribute" {
|
||||||
|
items.push(MetaItem::Attribute);
|
||||||
|
MetaState::Comma
|
||||||
|
} else if id == "ns" {
|
||||||
|
MetaState::Ns
|
||||||
|
} else {
|
||||||
|
panic!("unexpected key in xml attribute");
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.flatten()
|
(MetaState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => {
|
||||||
.map(|item| match item {
|
MetaState::Start
|
||||||
NestedMeta::Meta(item) => item,
|
}
|
||||||
NestedMeta::Lit(_) => panic!("unexpected xml attribute syntax"),
|
(MetaState::Ns, TokenTree::Group(group))
|
||||||
})
|
if group.delimiter() == Delimiter::Parenthesis =>
|
||||||
|
{
|
||||||
|
items.push(MetaItem::Ns(NamespaceMeta::from_tokens(group)));
|
||||||
|
MetaState::Comma
|
||||||
|
}
|
||||||
|
(state, tree) => {
|
||||||
|
panic!(
|
||||||
|
"invalid state transition while parsing xml attribute ({}, {tree})",
|
||||||
|
state.name()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Namespace {
|
enum MetaState {
|
||||||
Default,
|
Start,
|
||||||
Prefix(String),
|
Comma,
|
||||||
Literal(String),
|
Ns,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Namespace {
|
impl MetaState {
|
||||||
fn default() -> Self {
|
fn name(&self) -> &'static str {
|
||||||
Namespace::Default
|
match self {
|
||||||
|
MetaState::Start => "Start",
|
||||||
|
MetaState::Comma => "Comma",
|
||||||
|
MetaState::Ns => "Ns",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NsState {
|
||||||
|
Start,
|
||||||
|
Comma,
|
||||||
|
Path {
|
||||||
|
colon1: Option<Punct>,
|
||||||
|
colon2: Option<Punct>,
|
||||||
|
path: Option<syn::Path>,
|
||||||
|
},
|
||||||
|
Prefix,
|
||||||
|
Eq {
|
||||||
|
prefix: Ident,
|
||||||
|
},
|
||||||
|
PrefixValue {
|
||||||
|
prefix: Ident,
|
||||||
|
},
|
||||||
|
PrefixPath {
|
||||||
|
prefix: Ident,
|
||||||
|
colon1: Option<Punct>,
|
||||||
|
colon2: Option<Punct>,
|
||||||
|
path: Option<syn::Path>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NsState {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
NsState::Start => "Start",
|
||||||
|
NsState::Comma => "Comma",
|
||||||
|
NsState::Path {
|
||||||
|
colon1,
|
||||||
|
colon2,
|
||||||
|
path,
|
||||||
|
} => match (colon1, colon2, path) {
|
||||||
|
(None, None, None) => "Path [000]",
|
||||||
|
(Some(_), None, None) => "Path [100]",
|
||||||
|
(None, Some(_), None) => "Path [010]",
|
||||||
|
(None, None, Some(_)) => "Path [001]",
|
||||||
|
(Some(_), Some(_), None) => "Path [110]",
|
||||||
|
(None, Some(_), Some(_)) => "Path [011]",
|
||||||
|
(Some(_), None, Some(_)) => "Path [101]",
|
||||||
|
(Some(_), Some(_), Some(_)) => "Path [111]",
|
||||||
|
},
|
||||||
|
NsState::Prefix => "Prefix",
|
||||||
|
NsState::Eq { .. } => "Eq",
|
||||||
|
NsState::PrefixValue { .. } => "PrefixValue",
|
||||||
|
NsState::PrefixPath { .. } => "PrefixPath",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MetaItem {
|
||||||
|
Ns(NamespaceMeta),
|
||||||
|
Attribute,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Namespace {
|
||||||
|
Path(syn::Path),
|
||||||
|
Literal(Literal),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Namespace {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
match self {
|
||||||
|
Namespace::Path(path) => path.to_tokens(tokens),
|
||||||
|
Namespace::Literal(lit) => lit.to_tokens(tokens),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
use crate::{ContainerMeta, FieldMeta, Namespace};
|
use crate::{ContainerMeta, FieldMeta};
|
||||||
|
|
||||||
pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
|
||||||
let ident = &input.ident;
|
let ident = &input.ident;
|
||||||
|
@ -79,28 +79,34 @@ impl<'a> Serializer {
|
||||||
fn add_header(&mut self, output: &'a mut TokenStream) {
|
fn add_header(&mut self, output: &'a mut TokenStream) {
|
||||||
output.extend(quote!(
|
output.extend(quote!(
|
||||||
serializer.output.write_char('<')?;
|
serializer.output.write_char('<')?;
|
||||||
serializer.output.write_str(field_context.name)?;
|
|
||||||
));
|
));
|
||||||
|
|
||||||
let default_namespace = match &self.meta.ns.default {
|
let default_namespace = match &self.meta.ns.uri {
|
||||||
Namespace::Default => "",
|
Some(ns) => quote!(#ns),
|
||||||
Namespace::Prefix(_) => panic!("type cannot have prefix as namespace"),
|
None => quote!(""),
|
||||||
Namespace::Literal(ns) => ns,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
output.extend(quote!(
|
output.extend(quote!(
|
||||||
// Check if parent default namespace equals
|
// Check if parent default namespace equals
|
||||||
if serializer.parent_default_namespace() != #default_namespace {
|
if serializer.parent_default_namespace() != #default_namespace {
|
||||||
|
if let Some(prefix) = serializer.parent_namespaces.get(#default_namespace) {
|
||||||
|
serializer.output.write_str(prefix)?;
|
||||||
|
serializer.output.write_char(':')?;
|
||||||
|
serializer.output.write_str(field_context.name)?;
|
||||||
|
} else {
|
||||||
|
serializer.output.write_str(field_context.name)?;
|
||||||
serializer.output.write_str(" xmlns=\"")?;
|
serializer.output.write_str(" xmlns=\"")?;
|
||||||
serializer.output.write_str(#default_namespace)?;
|
serializer.output.write_str(#default_namespace)?;
|
||||||
serializer.output.write_char('\"')?;
|
serializer.output.write_char('\"')?;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
serializer.output.write_str(field_context.name)?;
|
||||||
|
}
|
||||||
serializer.update_parent_default_namespace(#default_namespace);
|
serializer.update_parent_default_namespace(#default_namespace);
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut sorted_values: Vec<_> = self.meta.ns.prefixes.iter().collect();
|
for (key, val) in &self.meta.ns.prefixes {
|
||||||
sorted_values.sort();
|
|
||||||
|
|
||||||
for (key, val) in sorted_values {
|
|
||||||
output.extend(quote!(
|
output.extend(quote!(
|
||||||
if serializer.parent_namespaces.get(#val).is_none() {
|
if serializer.parent_namespaces.get(#val).is_none() {
|
||||||
serializer.output.write_str(" xmlns:")?;
|
serializer.output.write_str(" xmlns:")?;
|
||||||
|
@ -160,37 +166,20 @@ impl<'a> Serializer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Namespace::Literal(ns) = &field_meta.ns.default {
|
let ns = match field_meta.ns.uri {
|
||||||
body.extend(quote!(
|
Some(ns) => quote!(#ns),
|
||||||
#declaration
|
None => match &self.meta.ns.uri {
|
||||||
field.attribute = Some(instant_xml::FieldAttribute::Namespace(#ns));
|
Some(ns) => quote!(#ns),
|
||||||
));
|
None => quote!(""),
|
||||||
} else if let Namespace::Prefix(prefix) = &field_meta.ns.default {
|
},
|
||||||
match self.meta.ns.prefixes.get(prefix) {
|
|
||||||
Some(val) => {
|
|
||||||
body.extend(quote!(
|
|
||||||
#declaration
|
|
||||||
|
|
||||||
// Check if such namespace already exist, if so change its prefix to parent prefix
|
|
||||||
let prefix_key = match serializer.parent_namespaces.get(#val) {
|
|
||||||
Some(key) => key,
|
|
||||||
None => #prefix,
|
|
||||||
};
|
};
|
||||||
));
|
|
||||||
|
body.extend(quote!(
|
||||||
|
#declaration
|
||||||
|
match serializer.parent_namespaces.get(#ns) {
|
||||||
|
Some(prefix) => field.attribute = Some(::instant_xml::FieldAttribute::Prefix(prefix)),
|
||||||
|
None => field.attribute = Some(::instant_xml::FieldAttribute::Namespace(#ns)),
|
||||||
}
|
}
|
||||||
None => panic!("Prefix not defined: {}", prefix),
|
|
||||||
};
|
|
||||||
|
|
||||||
body.extend(quote!(
|
|
||||||
field.attribute = Some(instant_xml::FieldAttribute::Prefix(prefix_key));
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
body.extend(quote!(
|
|
||||||
#declaration
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
body.extend(quote!(
|
|
||||||
serializer.set_field_context(field)?;
|
serializer.set_field_context(field)?;
|
||||||
self.#field_value.serialize(serializer)?;
|
self.#field_value.serialize(serializer)?;
|
||||||
));
|
));
|
||||||
|
|
|
@ -1,509 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use similar_asserts::assert_eq;
|
|
||||||
|
|
||||||
use instant_xml::{Error, FromXml, ToXml};
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
|
||||||
struct Unit;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unit() {
|
|
||||||
assert_eq!(Unit.to_xml().unwrap(), "<Unit></Unit>");
|
|
||||||
//assert_eq!(Unit::from_xml("<Unit/>").unwrap(), Unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
|
||||||
#[xml(ns(bar = "BAZ", foo = "BAR"))]
|
|
||||||
struct StructWithNamedFields {
|
|
||||||
flag: bool,
|
|
||||||
#[xml(ns("BAZ"))]
|
|
||||||
string: String,
|
|
||||||
#[xml(ns("typo"))]
|
|
||||||
number: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests:
|
|
||||||
// - Empty default namespace
|
|
||||||
// - Prefix namespace
|
|
||||||
// - Direct namespace
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn struct_with_named_fields() {
|
|
||||||
assert_eq!(
|
|
||||||
StructWithNamedFields {
|
|
||||||
flag: true,
|
|
||||||
string: "test".to_string(),
|
|
||||||
number: 1,
|
|
||||||
}
|
|
||||||
.to_xml()
|
|
||||||
.unwrap(),
|
|
||||||
"<StructWithNamedFields xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><flag>true</flag><bar:string>test</bar:string><number xmlns=\"typo\">1</number></StructWithNamedFields>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
|
||||||
#[xml(ns("URI", dar = "BAZ", internal = "INTERNAL"))]
|
|
||||||
struct Nested {
|
|
||||||
#[xml(ns(internal))]
|
|
||||||
flag_internal_prefix: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ", foo = "BAR"))]
|
|
||||||
struct StructWithCustomField {
|
|
||||||
#[xml(attribute)]
|
|
||||||
int_attribute: i32,
|
|
||||||
#[xml(ns("BAZ"))]
|
|
||||||
flag_direct_namespace_same_the_same_as_prefix: bool,
|
|
||||||
#[xml(ns("DIFFERENT"))]
|
|
||||||
flag_direct_namespace: bool,
|
|
||||||
test: Nested,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests:
|
|
||||||
// - The same direct namespace as the one from prefix
|
|
||||||
// - Attribute handling
|
|
||||||
// - Omitting redeclared child default namespace
|
|
||||||
// - Omitting redeclared child namespace with different prefix
|
|
||||||
// - Unique direct namespace
|
|
||||||
// - Child unique prefix
|
|
||||||
// - Child repeated prefix
|
|
||||||
// - Child default namespace the same as parent
|
|
||||||
#[test]
|
|
||||||
fn struct_with_custom_field() {
|
|
||||||
assert_eq!(
|
|
||||||
StructWithCustomField {
|
|
||||||
int_attribute: 42,
|
|
||||||
flag_direct_namespace_same_the_same_as_prefix: true,
|
|
||||||
flag_direct_namespace: true,
|
|
||||||
test: Nested {
|
|
||||||
flag_internal_prefix: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.to_xml()
|
|
||||||
.unwrap(),
|
|
||||||
"<StructWithCustomField xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\" int_attribute=\"42\"><flag_direct_namespace_same_the_same_as_prefix xmlns=\"BAZ\">true</flag_direct_namespace_same_the_same_as_prefix><flag_direct_namespace xmlns=\"DIFFERENT\">true</flag_direct_namespace><Nested xmlns:internal=\"INTERNAL\"><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructWithCustomField>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
|
||||||
struct NestedDifferentNamespace {
|
|
||||||
#[xml(ns("INTERNAL"))]
|
|
||||||
flag_internal_prefix: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ", foo = "BAR"))]
|
|
||||||
struct StructChildNamespaces {
|
|
||||||
different_child_namespace: NestedDifferentNamespace,
|
|
||||||
same_child_namespace: Nested,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests:
|
|
||||||
// - Different child namespace
|
|
||||||
// - The same child namespace
|
|
||||||
#[test]
|
|
||||||
fn struct_child_namespaces() {
|
|
||||||
assert_eq!(
|
|
||||||
StructChildNamespaces {
|
|
||||||
different_child_namespace: NestedDifferentNamespace {
|
|
||||||
flag_internal_prefix: false,
|
|
||||||
},
|
|
||||||
same_child_namespace: Nested {
|
|
||||||
flag_internal_prefix: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.to_xml()
|
|
||||||
.unwrap(),
|
|
||||||
"<StructChildNamespaces xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><NestedDifferentNamespace xmlns=\"\"><flag_internal_prefix>false</internal:flag_internal_prefix></NestedDifferentNamespace><Nested xmlns:internal=\"INTERNAL\"><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructChildNamespaces>"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ"))]
|
|
||||||
struct NestedDe {
|
|
||||||
#[xml(ns("BAZ"))]
|
|
||||||
flag: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ", foo = "BAR"))]
|
|
||||||
struct StructWithCustomFieldFromXml {
|
|
||||||
#[xml(ns("BAZ"))]
|
|
||||||
flag: bool,
|
|
||||||
#[xml(attribute)]
|
|
||||||
flag_attribute: bool,
|
|
||||||
test: NestedDe,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn struct_with_custom_field_from_xml() {
|
|
||||||
assert_eq!(
|
|
||||||
StructWithCustomFieldFromXml::from_xml("<StructWithCustomFieldFromXml flag_attribute=\"true\" xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><bar:flag>false</bar:flag><NestedDe><bar:flag>true</bar:flag></NestedDe></StructWithCustomFieldFromXml>").unwrap(),
|
|
||||||
StructWithCustomFieldFromXml {
|
|
||||||
flag: false,
|
|
||||||
flag_attribute: true,
|
|
||||||
test: NestedDe { flag: true }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// Different order
|
|
||||||
assert_eq!(
|
|
||||||
StructWithCustomFieldFromXml::from_xml("<StructWithCustomFieldFromXml xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\" flag_attribute=\"true\"><NestedDe><bar:flag>true</bar:flag></NestedDe><bar:flag>false</bar:flag></StructWithCustomFieldFromXml>").unwrap(),
|
|
||||||
StructWithCustomFieldFromXml {
|
|
||||||
flag: false,
|
|
||||||
flag_attribute: true,
|
|
||||||
test: NestedDe { flag: true }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Different prefixes then in definition
|
|
||||||
assert_eq!(
|
|
||||||
StructWithCustomFieldFromXml::from_xml("<StructWithCustomFieldFromXml flag_attribute=\"true\" xmlns=\"URI\" xmlns:grr=\"BAZ\" xmlns:foo=\"BAR\"><grr:flag>false</grr:flag><NestedDe><grr:flag>true</grr:flag></NestedDe></StructWithCustomFieldFromXml>").unwrap(),
|
|
||||||
StructWithCustomFieldFromXml {
|
|
||||||
flag: false,
|
|
||||||
flag_attribute: true,
|
|
||||||
test: NestedDe { flag: true }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
NestedDe::from_xml(
|
|
||||||
"<NestedDe xmlns=\"URI\" xmlns:bar=\"BAZ\"><bar:flag>true</bar:flag></NestedDe>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
NestedDe { flag: true }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
struct NestedWrongNamespace {
|
|
||||||
flag: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ"))]
|
|
||||||
struct StructWithCorrectNestedNamespace {
|
|
||||||
test: NestedDe,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ"))]
|
|
||||||
struct StructWithWrongNestedNamespace {
|
|
||||||
test: NestedWrongNamespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn default_namespaces() {
|
|
||||||
// Default namespace not-nested
|
|
||||||
assert_eq!(
|
|
||||||
NestedDe::from_xml(
|
|
||||||
"<NestedDe xmlns=\"URI\" xmlns:bar=\"BAZ\"><bar:flag>true</bar:flag></NestedDe>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
NestedDe { flag: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Default namespace not-nested - wrong namespace
|
|
||||||
assert_eq!(
|
|
||||||
NestedDe::from_xml(
|
|
||||||
"<NestedDe xmlns=\"WRONG\" xmlns:bar=\"BAZ\"><bar:flag>true</bar:flag></NestedDe>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
|
|
||||||
// Correct child namespace
|
|
||||||
assert_eq!(
|
|
||||||
StructWithCorrectNestedNamespace::from_xml("<StructWithCorrectNestedNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><NestedDe xmlns=\"URI\" xmlns:bar=\"BAZ\"><bar:flag>true</bar:flag></NestedDe></StructWithCorrectNestedNamespace>").unwrap(),
|
|
||||||
StructWithCorrectNestedNamespace {
|
|
||||||
test: NestedDe { flag: true }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Correct child namespace - without child redefinition
|
|
||||||
assert_eq!(
|
|
||||||
StructWithCorrectNestedNamespace::from_xml("<StructWithCorrectNestedNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><NestedDe><bar:flag>true</bar:flag></NestedDe></StructWithCorrectNestedNamespace>").unwrap(),
|
|
||||||
StructWithCorrectNestedNamespace {
|
|
||||||
test: NestedDe { flag: true }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Different child namespace
|
|
||||||
assert_eq!(
|
|
||||||
StructWithWrongNestedNamespace::from_xml("<StructWithWrongNestedNamespace xmlns=\"URI\" xmlns:dar=\"BAZ\"><NestedWrongNamespace xmlns=\"\"><flag>true</flag></NestedWrongNamespace></StructWithWrongNestedNamespace>").unwrap(),
|
|
||||||
StructWithWrongNestedNamespace {
|
|
||||||
test: NestedWrongNamespace {
|
|
||||||
flag: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wrong child namespace
|
|
||||||
assert_eq!(
|
|
||||||
StructWithWrongNestedNamespace::from_xml("<StructWithWrongNestedNamespace xmlns=\"URI\" xmlns:dar=\"BAZ\"><NestedWrongNamespace><flag>true</flag></NestedWrongNamespace></StructWithWrongNestedNamespace>").unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ"))]
|
|
||||||
struct NestedOtherNamespace {
|
|
||||||
#[xml(ns("BAZ"))]
|
|
||||||
flag: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
#[xml(ns("URI", bar = "BAZ"))]
|
|
||||||
struct StructOtherNamespace {
|
|
||||||
test: NestedOtherNamespace,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn other_namespaces() {
|
|
||||||
// Other namespace not-nested
|
|
||||||
assert_eq!(
|
|
||||||
NestedOtherNamespace::from_xml(
|
|
||||||
"<NestedOtherNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><bar:flag>true</bar:flag></NestedOtherNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
NestedOtherNamespace { flag: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Other namespace not-nested - wrong defined namespace
|
|
||||||
assert_eq!(
|
|
||||||
NestedOtherNamespace::from_xml(
|
|
||||||
"<NestedOtherNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><wrong:flag>true</wrong:flag></NestedOtherNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
|
|
||||||
// Other namespace not-nested - wrong parser namespace
|
|
||||||
assert_eq!(
|
|
||||||
NestedOtherNamespace::from_xml(
|
|
||||||
"<NestedOtherNamespace xmlns=\"URI\" xmlns:bar=\"WRONG\"><bar:flag>true</bar:flag></NestedOtherNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
|
|
||||||
// Other namespace not-nested - missing parser prefix
|
|
||||||
assert_eq!(
|
|
||||||
NestedOtherNamespace::from_xml(
|
|
||||||
"<NestedOtherNamespace xmlns=\"URI\" xmlns:bar=\"BAR\"><flag>true</flag></NestedOtherNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
|
|
||||||
// Correct child other namespace
|
|
||||||
assert_eq!(
|
|
||||||
StructOtherNamespace::from_xml(
|
|
||||||
"<StructOtherNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><NestedOtherNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><bar:flag>true</bar:flag></NestedOtherNamespace></StructOtherNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
StructOtherNamespace {
|
|
||||||
test: NestedOtherNamespace {
|
|
||||||
flag: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Correct child other namespace - without child redefinition
|
|
||||||
assert_eq!(
|
|
||||||
StructOtherNamespace::from_xml(
|
|
||||||
"<StructOtherNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><NestedOtherNamespace><bar:flag>true</bar:flag></NestedOtherNamespace></StructOtherNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
StructOtherNamespace {
|
|
||||||
test: NestedOtherNamespace {
|
|
||||||
flag: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wrong child other namespace - without child redefinition
|
|
||||||
assert_eq!(
|
|
||||||
StructOtherNamespace::from_xml(
|
|
||||||
"<StructOtherNamespace xmlns=\"URI\" xmlns:bar=\"BAZ\"><NestedOtherNamespace><wrong:flag>true</wrong:flag></NestedOtherNamespace></StructOtherNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
|
||||||
#[xml(ns("URI"))]
|
|
||||||
struct StructDirectNamespace {
|
|
||||||
#[xml(ns("BAZ"))]
|
|
||||||
flag: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn direct_namespaces() {
|
|
||||||
// Correct direct namespace
|
|
||||||
assert_eq!(
|
|
||||||
StructDirectNamespace::from_xml(
|
|
||||||
"<StructDirectNamespace xmlns=\"URI\"><flag xmlns=\"BAZ\">true</flag></StructDirectNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
StructDirectNamespace { flag: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wrong direct namespace
|
|
||||||
assert_eq!(
|
|
||||||
StructDirectNamespace::from_xml(
|
|
||||||
"<StructDirectNamespace xmlns=\"URI\"><flag xmlns=\"WRONG\">true</flag></StructDirectNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wrong direct namespace - missing namespace
|
|
||||||
assert_eq!(
|
|
||||||
StructDirectNamespace::from_xml(
|
|
||||||
"<StructDirectNamespace xmlns=\"URI\"><flag>true</flag></StructDirectNamespace>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::WrongNamespace
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, FromXml, ToXml)]
|
|
||||||
#[xml(ns("URI"))]
|
|
||||||
struct NestedLifetimes<'a> {
|
|
||||||
flag: bool,
|
|
||||||
str_type_a: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, FromXml, ToXml)]
|
|
||||||
#[xml(ns("URI"))]
|
|
||||||
struct StructDeserializerScalars<'a, 'b> {
|
|
||||||
bool_type: bool,
|
|
||||||
i8_type: i8,
|
|
||||||
u32_type: u32,
|
|
||||||
string_type: String,
|
|
||||||
str_type_a: &'a str,
|
|
||||||
str_type_b: &'b str,
|
|
||||||
char_type: char,
|
|
||||||
f32_type: f32,
|
|
||||||
nested: NestedLifetimes<'a>,
|
|
||||||
cow: Cow<'a, str>,
|
|
||||||
option: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn scalars() {
|
|
||||||
assert_eq!(
|
|
||||||
StructDeserializerScalars::from_xml(
|
|
||||||
"<StructDeserializerScalars xmlns=\"URI\"><bool_type>true</bool_type><i8_type>1</i8_type><u32_type>42</u32_type><string_type>string</string_type><str_type_a>lifetime a</str_type_a><str_type_b>lifetime b</str_type_b><char_type>c</char_type><f32_type>1.20</f32_type><NestedLifetimes><flag>true</flag><str_type_a>asd</str_type_a></NestedLifetimes><cow>123</cow></StructDeserializerScalars>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
StructDeserializerScalars{
|
|
||||||
bool_type: true,
|
|
||||||
i8_type: 1,
|
|
||||||
u32_type: 42,
|
|
||||||
string_type: "string".to_string(),
|
|
||||||
str_type_a: "lifetime a",
|
|
||||||
str_type_b: "lifetime b",
|
|
||||||
char_type: 'c',
|
|
||||||
f32_type: 1.20,
|
|
||||||
nested: NestedLifetimes {
|
|
||||||
flag: true,
|
|
||||||
str_type_a: "asd"
|
|
||||||
},
|
|
||||||
cow: Cow::from("123"),
|
|
||||||
option: None,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Option none
|
|
||||||
assert_eq!(
|
|
||||||
StructDeserializerScalars::from_xml(
|
|
||||||
"<StructDeserializerScalars xmlns=\"URI\"><bool_type>true</bool_type><i8_type>1</i8_type><u32_type>42</u32_type><string_type>string</string_type><str_type_a>lifetime a</str_type_a><str_type_b>lifetime b</str_type_b><char_type>c</char_type><f32_type>1.2</f32_type><NestedLifetimes><flag>true</flag><str_type_a>asd</str_type_a></NestedLifetimes><cow>123</cow><option>asd</option></StructDeserializerScalars>"
|
|
||||||
).unwrap(),
|
|
||||||
StructDeserializerScalars{
|
|
||||||
bool_type: true,
|
|
||||||
i8_type: 1,
|
|
||||||
u32_type: 42,
|
|
||||||
string_type: "string".to_string(),
|
|
||||||
str_type_a: "lifetime a",
|
|
||||||
str_type_b: "lifetime b",
|
|
||||||
char_type: 'c',
|
|
||||||
f32_type: 1.20,
|
|
||||||
nested: NestedLifetimes {
|
|
||||||
flag: true,
|
|
||||||
str_type_a: "asd"
|
|
||||||
},
|
|
||||||
cow: Cow::from("123"),
|
|
||||||
option: Some("asd"),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, FromXml, ToXml)]
|
|
||||||
#[xml(ns("URI"))]
|
|
||||||
struct StructSpecialEntities<'a> {
|
|
||||||
string: String,
|
|
||||||
str: &'a str,
|
|
||||||
cow: Cow<'a, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn escape_back() {
|
|
||||||
assert_eq!(
|
|
||||||
StructSpecialEntities::from_xml(
|
|
||||||
"<StructSpecialEntities xmlns=\"URI\"><string><>&"'adsad"</string><str>str</str><cow>str&</cow></StructSpecialEntities>"
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
StructSpecialEntities {
|
|
||||||
string: String::from("<>&\"'adsad\""),
|
|
||||||
str: "str",
|
|
||||||
cow: Cow::Owned("str&".to_string()),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wrong str char
|
|
||||||
assert_eq!(
|
|
||||||
StructSpecialEntities::from_xml(
|
|
||||||
"<StructSpecialEntities xmlns=\"URI\"><string><>&"'adsad"</string><str>str&</str></StructSpecialEntities>"
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
Error::Other("Unsupported char: str&".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Borrowed
|
|
||||||
let escape_back = StructSpecialEntities::from_xml(
|
|
||||||
"<StructSpecialEntities xmlns=\"URI\"><string><>&"'adsad"</string><str>str</str><cow>str</cow></StructSpecialEntities>"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Cow::Owned(_) = escape_back.cow {
|
|
||||||
panic!("Should be Borrowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Owned
|
|
||||||
let escape_back = StructSpecialEntities::from_xml(
|
|
||||||
"<StructSpecialEntities xmlns=\"URI\"><string><>&"'adsad"</string><str>str</str><cow>str&</cow></StructSpecialEntities>"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Cow::Borrowed(_) = escape_back.cow {
|
|
||||||
panic!("Should be Owned")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn special_entities() {
|
|
||||||
assert_eq!(
|
|
||||||
StructSpecialEntities{
|
|
||||||
string: "&\"<>\'aa".to_string(),
|
|
||||||
str: "&\"<>\'bb",
|
|
||||||
cow: Cow::from("&\"<>\'cc"),
|
|
||||||
}
|
|
||||||
.to_xml()
|
|
||||||
.unwrap(),
|
|
||||||
"<StructSpecialEntities xmlns=\"URI\"><string>&"<>'aa</string><str>&"<>'bb</str><cow>&"<>'cc</cow></StructSpecialEntities>"
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -3,22 +3,24 @@ use similar_asserts::assert_eq;
|
||||||
use instant_xml::{from_str, FromXml};
|
use instant_xml::{from_str, FromXml};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
#[derive(Debug, Eq, PartialEq, FromXml)]
|
||||||
#[xml(ns("URI", bar = "BAZ"))]
|
#[xml(ns("URI", bar = BAR))]
|
||||||
struct NestedDe {
|
struct NestedDe {
|
||||||
#[xml(ns(bar))]
|
#[xml(ns(BAR))]
|
||||||
flag: bool,
|
flag: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, FromXml)]
|
#[derive(Debug, Eq, PartialEq, FromXml)]
|
||||||
#[xml(ns("URI", bar = "BAZ", foo = "BAR"))]
|
#[xml(ns("URI", bar = "BAZ", foo = "BAR"))]
|
||||||
struct StructWithCustomFieldFromXml {
|
struct StructWithCustomFieldFromXml {
|
||||||
#[xml(ns(bar))]
|
#[xml(ns(BAR))]
|
||||||
flag: bool,
|
flag: bool,
|
||||||
#[xml(attribute)]
|
#[xml(attribute)]
|
||||||
flag_attribute: bool,
|
flag_attribute: bool,
|
||||||
test: NestedDe,
|
test: NestedDe,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BAR: &str = "BAZ";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn struct_with_custom_field_from_xml() {
|
fn struct_with_custom_field_from_xml() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -3,9 +3,9 @@ use similar_asserts::assert_eq;
|
||||||
use instant_xml::{to_string, ToXml};
|
use instant_xml::{to_string, ToXml};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
#[xml(ns(dar = "BAZ", internal = "INTERNAL"))]
|
#[xml(ns(dar = "BAZ", internal = INTERNAL))]
|
||||||
struct NestedDifferentNamespace {
|
struct NestedDifferentNamespace {
|
||||||
#[xml(ns(internal))]
|
#[xml(ns(INTERNAL))]
|
||||||
flag_internal_prefix: bool,
|
flag_internal_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,14 +17,17 @@ struct StructChildNamespaces {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
#[xml(ns("URI", dar = "BAZ", internal = "INTERNAL"))]
|
#[xml(ns("URI", dar = DAR, internal = INTERNAL))]
|
||||||
struct Nested {
|
struct Nested {
|
||||||
#[xml(ns(dar))]
|
#[xml(ns(DAR))]
|
||||||
flag_parent_prefix: bool,
|
flag_parent_prefix: bool,
|
||||||
#[xml(ns(internal))]
|
#[xml(ns(INTERNAL))]
|
||||||
flag_internal_prefix: bool,
|
flag_internal_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DAR: &str = "BAZ";
|
||||||
|
const INTERNAL: &str = "INTERNAL";
|
||||||
|
|
||||||
// Tests:
|
// Tests:
|
||||||
// - Different child namespace
|
// - Different child namespace
|
||||||
// - The same child namespace
|
// - The same child namespace
|
||||||
|
|
|
@ -3,12 +3,14 @@ use similar_asserts::assert_eq;
|
||||||
use instant_xml::{to_string, ToXml};
|
use instant_xml::{to_string, ToXml};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
#[xml(ns("URI", dar = "BAZ", internal = "INTERNAL"))]
|
#[xml(ns("URI", dar = "BAZ", internal = INTERNAL))]
|
||||||
struct Nested {
|
struct Nested {
|
||||||
#[xml(ns(internal))]
|
#[xml(ns(INTERNAL))]
|
||||||
flag_internal_prefix: bool,
|
flag_internal_prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INTERNAL: &str = "INTERNAL";
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
#[xml(ns("URI", bar = "BAZ", foo = "BAR"))]
|
#[xml(ns("URI", bar = "BAZ", foo = "BAR"))]
|
||||||
struct StructWithCustomField {
|
struct StructWithCustomField {
|
||||||
|
@ -42,6 +44,6 @@ fn struct_with_custom_field() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
"<StructWithCustomField xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\" int_attribute=\"42\"><flag_direct_namespace_same_the_same_as_prefix xmlns=\"BAZ\">true</flag_direct_namespace_same_the_same_as_prefix><flag_direct_namespace xmlns=\"DIFFERENT\">true</flag_direct_namespace><Nested xmlns:internal=\"INTERNAL\"><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructWithCustomField>"
|
"<StructWithCustomField xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\" int_attribute=\"42\"><bar:flag_direct_namespace_same_the_same_as_prefix>true</bar:flag_direct_namespace_same_the_same_as_prefix><flag_direct_namespace xmlns=\"DIFFERENT\">true</flag_direct_namespace><Nested xmlns:internal=\"INTERNAL\"><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructWithCustomField>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue