From fcf20aa507cc1f63bf1a4df62c05fdbd9285ddce Mon Sep 17 00:00:00 2001 From: choinskib <37154155+choinskib@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:16:19 +0200 Subject: [PATCH] Namespaces fixes - deserializer (#10) --- instant-xml-macros/src/de.rs | 90 ++++++++++--- instant-xml-macros/src/lib.rs | 74 ++++------- instant-xml-macros/src/se.rs | 7 +- instant-xml/src/lib.rs | 152 ++++++++++++++++------ instant-xml/src/parse.rs | 6 +- instant-xml/tests/all.rs | 232 ++++++++++++++++++++++++++++++++-- 6 files changed, 440 insertions(+), 121 deletions(-) diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index e120f8a..a9feeaa 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -1,7 +1,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use crate::{get_namespaces, retrieve_attr}; +use crate::{get_namespaces, retrieve_field_attribute, FieldAttribute}; struct Tokens { enum_: TokenStream, @@ -37,8 +37,9 @@ impl Deserializer { let name = ident.to_string(); let mut out = TokenStream::new(); - let (_, other_namespaces) = get_namespaces(&input.attrs); + let (default_namespace, other_namespaces) = get_namespaces(&input.attrs); let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new();); + for (k, v) in other_namespaces.iter() { namespaces_map.extend(quote!( namespaces_map.insert(#k, #v); @@ -58,16 +59,26 @@ impl Deserializer { match data.fields { syn::Fields::Named(ref fields) => { fields.named.iter().enumerate().for_each(|(index, field)| { - let is_element; - let tokens = match retrieve_attr("attribute", &field.attrs) { - Some(true) => { - is_element = false; - &mut attributes_tokens + let mut field_namespace = None; + let (tokens, def_prefix, is_element) = match retrieve_field_attribute(field) { + Some(FieldAttribute::Namespace(value)) => { + field_namespace = Some(value); + (&mut elements_tokens, None, true) } - _ => { - is_element = true; - &mut elements_tokens + Some(FieldAttribute::PrefixIdentifier(def_prefix)) => { + if other_namespaces.get(&def_prefix).is_none() { + panic!("Namespace with such prefix do not exist for this struct"); + } + + (&mut elements_tokens, Some(def_prefix), true) + }, + Some(FieldAttribute::Attribute) => { + (&mut attributes_tokens, None, false) } + None => { + (&mut elements_tokens, None, true) + }, + }; Self::process_field( @@ -77,10 +88,12 @@ impl Deserializer { &mut return_val, tokens, is_element, + def_prefix, + field_namespace, ); }); } - syn::Fields::Unnamed(_) => todo!(), + syn::Fields::Unnamed(_) => panic!("unamed"), syn::Fields::Unit => {} }; } @@ -137,7 +150,6 @@ impl Deserializer { fn visit_struct<'a>(&self, deserializer: &mut ::instant_xml::Deserializer) -> Result { #declare_values - while let Some(( key, _ )) = deserializer.peek_next_attribute() { match get_attribute(&key) { #attr_type_match @@ -149,10 +161,11 @@ impl Deserializer { XmlRecord::Open(item) => { match get_element(&item.key.as_ref()) { #elem_type_match - __Elements::__Ignore => todo!(), + __Elements::__Ignore => panic!("No such element"), } } XmlRecord::Close(tag) => { + println!("Close: {}", tag); if tag == &#name { break; } @@ -168,7 +181,7 @@ impl Deserializer { } #namespaces_map; - deserializer.deserialize_struct(StructVisitor{}, #name, &namespaces_map) + deserializer.deserialize_struct(StructVisitor{}, #name, #default_namespace, &namespaces_map) } )); @@ -179,6 +192,7 @@ impl Deserializer { Deserializer { out } } + #[allow(clippy::too_many_arguments)] fn process_field( field: &syn::Field, index: usize, @@ -186,13 +200,15 @@ impl Deserializer { return_val: &mut TokenStream, tokens: &mut Tokens, is_element: bool, + def_prefix: Option, + field_namespace: Option, ) { let field_var = field.ident.as_ref().unwrap(); let field_var_str = field_var.to_string(); let const_field_var_str = Ident::new(&field_var_str.to_uppercase(), Span::call_site()); let field_type = match &field.ty { syn::Type::Path(v) => v.path.get_ident(), - _ => todo!(), + _ => panic!("Wrong field attribute format"), }; let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site()); @@ -219,6 +235,18 @@ impl Deserializer { let mut #enum_name: Option<#field_type> = None; )); + let def_prefix = match def_prefix { + Some(def_prefix) => quote!(let def_prefix: Option<&str> = Some(#def_prefix);), + None => quote!(let def_prefix: Option<&str> = None;), + }; + + let field_namespace = match field_namespace { + Some(field_namespace) => { + quote!(let field_namespace: Option<&str> = Some(#field_namespace);) + } + None => quote!(let field_namespace: Option<&str> = None;), + }; + if is_element { tokens.match_.extend(quote!( __Elements::#enum_name => { @@ -226,11 +254,35 @@ impl Deserializer { panic!("duplicated value"); } - if let Some(item) = item.prefix { - let prefix = item.to_owned(); - deserializer.verify_namespace(&prefix); + match item.prefix { + Some(item) => { + let parser_prefix = item.to_owned(); + #def_prefix + match def_prefix { + Some(def_prefix) => { + // Check if defined and gotten namespaces equals for each field + if deserializer.get_parser_namespace(&parser_prefix) + != deserializer.get_def_namespace(def_prefix) { + return Err(Error::WrongNamespace) + } + } + None => { + return Err(Error::WrongNamespace); + } + } + } + None => { + #def_prefix + match def_prefix { + Some(_) => { + return Err(Error::WrongNamespace) + }, + None => (), + } + } } - + #field_namespace + deserializer.set_next_def_namespace(field_namespace)?; #enum_name = Some(#field_type::deserialize(deserializer)?); }, )); diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index ab51d63..ee80de9 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -16,23 +16,25 @@ const XML: &str = "xml"; pub(crate) enum FieldAttribute { Namespace(String), PrefixIdentifier(String), + Attribute, } pub(crate) fn get_namespaces( attributes: &Vec, -) -> (Option, HashMap) { - let mut default_namespace = None; +) -> (String, HashMap) { + let mut default_namespace = String::new(); let mut other_namespaces = HashMap::default(); - let list = match retrieve_attr_list("namespace", attributes) { - Some(v) => v, + let (list, name) = match retrieve_attr_list(attributes) { + Some((Some(list), name)) => (list, name), None => return (default_namespace, other_namespaces), + _ => panic!("wrong parameters"), }; - if list.path.get_ident().unwrap() == "namespace" { + if name == "namespace" { let mut iter = list.nested.iter(); if let Some(NestedMeta::Lit(Lit::Str(v))) = iter.next() { - default_namespace = Some(v.value()); + default_namespace = v.value(); } for item in iter { @@ -50,24 +52,26 @@ pub(crate) fn get_namespaces( (default_namespace, other_namespaces) } -pub(crate) fn retrieve_field_attribute(name: &str, input: &syn::Field) -> Option { - if let Some(list) = retrieve_attr_list(name, &input.attrs) { - match list.nested.first() { - Some(NestedMeta::Lit(Lit::Str(v))) => { - return Some(FieldAttribute::Namespace(v.value())); - } +pub(crate) fn retrieve_field_attribute(input: &syn::Field) -> Option { + match retrieve_attr_list(&input.attrs) { + Some((Some(list), name)) if name.as_str() == "namespace" => match list.nested.first() { + Some(NestedMeta::Lit(Lit::Str(v))) => Some(FieldAttribute::Namespace(v.value())), Some(NestedMeta::Meta(Meta::Path(v))) => { if let Some(ident) = v.get_ident() { - return Some(FieldAttribute::PrefixIdentifier(ident.to_string())); + Some(FieldAttribute::PrefixIdentifier(ident.to_string())) + } else { + panic!("unexpected parameter"); } } - _ => (), - }; + _ => panic!("unexpected parameter"), + }, + Some((None, name)) if name.as_str() == "attribute" => Some(FieldAttribute::Attribute), + None => None, + _ => panic!("unexpected parameter"), } - None } -pub(crate) fn retrieve_attr(name: &str, attributes: &Vec) -> Option { +fn retrieve_attr_list(attributes: &Vec) -> Option<(Option, String)> { for attr in attributes { if !attr.path.is_ident(XML) { continue; @@ -75,41 +79,19 @@ pub(crate) fn retrieve_attr(name: &str, attributes: &Vec) -> Opt let nested = match attr.parse_meta() { Ok(Meta::List(meta)) => meta.nested, - _ => return Some(false), - }; - - let path = match nested.first() { - Some(NestedMeta::Meta(Meta::Path(path))) => path, - _ => return Some(false), - }; - - if path.get_ident()? == name { - return Some(true); - } - } - - None -} - -fn retrieve_attr_list(name: &str, attributes: &Vec) -> Option { - for attr in attributes { - if !attr.path.is_ident(XML) { - continue; - } - - let nested = match attr.parse_meta() { - Ok(Meta::List(meta)) => meta.nested, - _ => return None, + Ok(_) => todo!(), + _ => todo!(), }; let list = match nested.first() { Some(NestedMeta::Meta(Meta::List(list))) => list, + Some(NestedMeta::Meta(Meta::Path(path))) => { + return Some((None, path.get_ident()?.to_string())) + } _ => return None, }; - if list.path.get_ident()? == name { - return Some(list.to_owned()); - } + return Some((Some(list.to_owned()), list.path.get_ident()?.to_string())); } None @@ -158,7 +140,7 @@ pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // Check if prefix exist #( if serializer.parent_prefixes.get(#missing_prefixes).is_none() { - return Err(instant_xml::Error::UnexpectedPrefix); + return Err(instant_xml::Error::WrongNamespace); } )*; diff --git a/instant-xml-macros/src/se.rs b/instant-xml-macros/src/se.rs index 6cbf246..45f49bf 100644 --- a/instant-xml-macros/src/se.rs +++ b/instant-xml-macros/src/se.rs @@ -6,7 +6,7 @@ use quote::quote; use crate::{get_namespaces, retrieve_field_attribute, FieldAttribute}; pub struct Serializer { - default_namespace: Option, + default_namespace: String, other_namespaces: HashMap, } @@ -33,7 +33,8 @@ impl<'a> Serializer { serializer.output.write_str(field_context.name)?; )); - if let Some(default_namespace) = self.default_namespace.as_ref() { + if !self.default_namespace.is_empty() { + let default_namespace = &self.default_namespace; output.extend(quote!( serializer.output.write_str(" xmlns=\"")?; serializer.output.write_str(#default_namespace)?; @@ -82,7 +83,7 @@ impl<'a> Serializer { }; )); - match retrieve_field_attribute("namespace", field) { + match retrieve_field_attribute(field) { Some(FieldAttribute::Namespace(namespace_key)) => { output.extend(quote!( field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace_key)); diff --git a/instant-xml/src/lib.rs b/instant-xml/src/lib.rs index 81e8bed..1e459fd 100644 --- a/instant-xml/src/lib.rs +++ b/instant-xml/src/lib.rs @@ -14,11 +14,8 @@ pub mod parse; pub struct TagData<'xml> { pub key: &'xml str, pub attributes: Vec<(&'xml str, &'xml str)>, - - // TODO: handle default namespace pub default_namespace: Option<&'xml str>, - - pub namespaces: Option>, + pub namespaces: HashMap<&'xml str, &'xml str>, pub prefix: Option<&'xml str>, } @@ -236,18 +233,26 @@ pub trait Visitor<'xml>: Sized { pub struct Deserializer<'xml> { parser: XmlParser<'xml>, - namespaces: HashMap<&'xml str, &'xml str>, + def_namespaces: HashMap<&'xml str, &'xml str>, + parser_namespaces: HashMap<&'xml str, &'xml str>, + def_defualt_namespace: &'xml str, + parser_defualt_namespace: &'xml str, tag_attributes: Vec<(&'xml str, &'xml str)>, next_type: EntityType, + next_def_namespace: Option<&'xml str>, } impl<'xml> Deserializer<'xml> { pub fn new(input: &'xml str) -> Self { Self { parser: XmlParser::new(input), - namespaces: std::collections::HashMap::new(), + def_namespaces: std::collections::HashMap::new(), + parser_namespaces: std::collections::HashMap::new(), + def_defualt_namespace: "", + parser_defualt_namespace: "", tag_attributes: Vec::new(), next_type: EntityType::Element, + next_def_namespace: None, } } @@ -255,8 +260,16 @@ impl<'xml> Deserializer<'xml> { self.parser.peek_next_tag() } - pub fn verify_namespace(&self, namespace_to_verify: &str) -> bool { - self.namespaces.get(namespace_to_verify).is_some() + pub fn get_def_namespace(&self, prefix: &str) -> Option<&&str> { + self.def_namespaces.get(prefix) + } + + pub fn get_parser_namespace(&self, prefix: &str) -> Option<&&str> { + self.parser_namespaces.get(prefix) + } + + pub fn compare_parser_and_def_default_namespaces(&self) -> bool { + self.parser_defualt_namespace == self.def_defualt_namespace } pub fn peek_next_attribute(&self) -> Option<&(&'xml str, &'xml str)> { @@ -267,24 +280,79 @@ impl<'xml> Deserializer<'xml> { &mut self, visitor: V, name: &str, - namespaces: &HashMap<&'xml str, &'xml str>, + def_default_namespace: &'xml str, + def_namespaces: &HashMap<&'xml str, &'xml str>, ) -> Result where V: Visitor<'xml>, { - let new_namespaces = namespaces + // Saveing current defined default namespace + let def_defualt_namespace_to_revert = self.def_defualt_namespace; + self.def_defualt_namespace = def_default_namespace; + + // Adding struct defined namespaces + let new_def_namespaces = def_namespaces .iter() - .filter(|(k, v)| self.namespaces.insert(k, v).is_none()) + .filter(|(k, v)| self.def_namespaces.insert(k, v).is_none()) + .collect::>(); + + // Process open tag + let tag_data = match self.parser.next() { + Some(Ok(XmlRecord::Open(item))) if item.key == name => item, + _ => return Err(Error::UnexpectedValue), + }; + + // Set current attributes + self.tag_attributes = tag_data.attributes; + + // Saveing current parser default namespace + let parser_defualt_namespace_to_revert = self.parser_defualt_namespace; + + // Set parser default namespace + match tag_data.default_namespace { + Some(namespace) => { + self.parser_defualt_namespace = namespace; + } + None => { + // If there is no default namespace in the tag, check if parent default namespace equals the current one + if def_defualt_namespace_to_revert != self.def_defualt_namespace { + return Err(Error::WrongNamespace); + } + } + } + + // Compare parser namespace with defined one + if !self.compare_parser_and_def_default_namespaces() { + return Err(Error::WrongNamespace); + } + + // Adding parser namespaces + let new_parser_namespaces = tag_data + .namespaces + .iter() + .filter(|(k, v)| self.parser_namespaces.insert(k, v).is_none()) .collect::>(); - self.process_open_tag(name, namespaces)?; let ret = visitor.visit_struct(self)?; + // Process close tag self.check_close_tag(name)?; - let _ = new_namespaces - .iter() - .map(|(k, _)| self.namespaces.remove(*k)); + // Removing parser namespaces + let _ = new_parser_namespaces + .iter() + .map(|(k, _)| self.parser_namespaces.remove(*k)); + + // Removing struct defined namespaces + let _ = new_def_namespaces + .iter() + .map(|(k, _)| self.def_namespaces.remove(*k)); + + // Retriving old defined namespace + self.def_defualt_namespace = def_defualt_namespace_to_revert; + + // Retriving old parser namespace + self.parser_defualt_namespace = parser_defualt_namespace_to_revert; Ok(ret) } @@ -303,11 +371,35 @@ impl<'xml> Deserializer<'xml> { ret } + pub fn set_next_def_namespace(&mut self, namespace: Option<&'xml str>) -> Result<(), Error> { + if self.next_def_namespace.is_some() { + return Err(Error::UnexpectedState); + } + + self.next_def_namespace = namespace; + Ok(()) + } + + pub fn consume_next_def_namespace(&mut self) -> Option<&'xml str> { + let ret = self.next_def_namespace; + self.next_def_namespace = None; + ret + } + fn deserialize_bool(&mut self, visitor: V) -> Result where V: Visitor<'xml>, { - self.parser.next(); + // Process open tag + let tag_data = match self.parser.next() { + Some(Ok(XmlRecord::Open(item))) => item, + _ => return Err(Error::UnexpectedValue), + }; + + if tag_data.default_namespace != self.consume_next_def_namespace() { + return Err(Error::WrongNamespace); + } + match self.parser.next() { Some(Ok(XmlRecord::Element(v))) => { let ret = visitor.visit_str(v); @@ -328,28 +420,6 @@ impl<'xml> Deserializer<'xml> { } } - fn process_open_tag( - &mut self, - name: &str, - namespaces: &HashMap<&'xml str, &'xml str>, - ) -> Result<(), Error> { - let item = match self.parser.next() { - Some(Ok(XmlRecord::Open(item))) if item.key == name => item, - _ => return Err(Error::UnexpectedValue), - }; - - for (k, v) in item.namespaces.unwrap() { - match namespaces.get(k) { - Some(item) if *item != v => return Err(Error::UnexpectedPrefix), - None => return Err(Error::MissingdPrefix), - _ => (), - } - } - - self.tag_attributes = item.attributes; - Ok(()) - } - fn check_close_tag(&mut self, name: &str) -> Result<(), Error> { let item = match self.parser.next() { Some(item) => item?, @@ -370,7 +440,7 @@ struct State<'a> { prefix: HashMap<&'a str, &'a str>, } -#[derive(Debug, Error)] +#[derive(Debug, Error, PartialEq, Eq)] pub enum Error { #[error("format: {0}")] Format(#[from] fmt::Error), @@ -392,8 +462,8 @@ pub enum Error { UnexpectedToken, #[error("missing prefix")] MissingdPrefix, - #[error("unexpected prefix")] - UnexpectedPrefix, #[error("unexpected state")] UnexpectedState, + #[error("wrong namespace")] + WrongNamespace, } diff --git a/instant-xml/src/parse.rs b/instant-xml/src/parse.rs index 7e73517..87a47f2 100644 --- a/instant-xml/src/parse.rs +++ b/instant-xml/src/parse.rs @@ -35,8 +35,8 @@ impl<'a> XmlParser<'a> { Ok(Some(XmlRecord::Open(TagData { key: local, attributes: Vec::new(), - default_namespace: None, - namespaces: None, + default_namespace: Some(""), + namespaces: HashMap::new(), prefix, }))) } @@ -89,7 +89,7 @@ impl<'xml> Iterator for XmlParser<'xml> { key: key.unwrap(), attributes, default_namespace, - namespaces: Some(namespaces), + namespaces, prefix: prefix_ret, }))); } diff --git a/instant-xml/tests/all.rs b/instant-xml/tests/all.rs index 413fd6a..ed33593 100644 --- a/instant-xml/tests/all.rs +++ b/instant-xml/tests/all.rs @@ -1,6 +1,6 @@ -use instant_xml::{FromXml, ToXml}; +use instant_xml::{Error, FromXml, ToXml}; -#[derive(Debug, Eq, PartialEq, ToXml, FromXml)] +#[derive(Debug, Eq, PartialEq, ToXml)] struct Nested { #[xml(namespace(bar))] flag: bool, @@ -66,6 +66,13 @@ fn struct_with_custom_field() { ); } +#[derive(Debug, Eq, PartialEq, ToXml, FromXml)] +#[xml(namespace("URI", bar = "BAZ"))] +struct NestedDe { + #[xml(namespace(bar))] + flag: bool, +} + #[derive(Debug, Eq, PartialEq, ToXml)] #[xml(namespace("URI", bar = "BAZ", foo = "BAR"))] struct StructWithCustomFieldWrongPrefix { @@ -89,31 +96,238 @@ struct StructWithCustomFieldFromXml { flag: bool, #[xml(attribute)] flag_attribute: bool, - test: Nested, + test: NestedDe, } #[test] fn struct_with_custom_field_from_xml() { assert_eq!( - StructWithCustomFieldFromXml::from_xml("falsetrue").unwrap(), + StructWithCustomFieldFromXml::from_xml("falsetrue").unwrap(), StructWithCustomFieldFromXml { flag: false, flag_attribute: true, - test: Nested { flag: true } + test: NestedDe { flag: true } } ); // Different order assert_eq!( - StructWithCustomFieldFromXml::from_xml("truefalse").unwrap(), + StructWithCustomFieldFromXml::from_xml("truefalse").unwrap(), StructWithCustomFieldFromXml { flag: false, flag_attribute: true, - test: Nested { flag: true } + test: NestedDe { flag: true } + } + ); + + // Different prefixes then in definition + assert_eq!( + StructWithCustomFieldFromXml::from_xml("falsetrue").unwrap(), + StructWithCustomFieldFromXml { + flag: false, + flag_attribute: true, + test: NestedDe { flag: true } } ); assert_eq!( - Nested::from_xml("true").unwrap(), - Nested { flag: true } + NestedDe::from_xml( + "true" + ) + .unwrap(), + NestedDe { flag: true } + ); +} + +#[derive(Debug, Eq, PartialEq, ToXml, FromXml)] +struct NestedWrongNamespace { + flag: bool, +} + +#[derive(Debug, Eq, PartialEq, FromXml)] +#[xml(namespace("URI", bar = "BAZ"))] +struct StructWithCorrectNestedNamespace { + test: NestedDe, +} + +#[derive(Debug, Eq, PartialEq, FromXml)] +#[xml(namespace("URI", bar = "BAZ"))] +struct StructWithWrongNestedNamespace { + test: NestedWrongNamespace, +} + +#[test] +fn default_namespaces() { + // Default namespace not-nested + assert_eq!( + NestedDe::from_xml( + "true" + ) + .unwrap(), + NestedDe { flag: true } + ); + + // Default namespace not-nested - wrong namespace + assert_eq!( + NestedDe::from_xml( + "true" + ) + .unwrap_err(), + Error::WrongNamespace + ); + + // Correct child namespace + assert_eq!( + StructWithCorrectNestedNamespace::from_xml("true").unwrap(), + StructWithCorrectNestedNamespace { + test: NestedDe { flag: true } + } + ); + + // Correct child namespace - without child redefinition + assert_eq!( + StructWithCorrectNestedNamespace::from_xml("true").unwrap(), + StructWithCorrectNestedNamespace { + test: NestedDe { flag: true } + } + ); + + // Different child namespace + assert_eq!( + StructWithWrongNestedNamespace::from_xml("true").unwrap(), + StructWithWrongNestedNamespace { + test: NestedWrongNamespace { + flag: true + } + } + ); + + // Wrong child namespace + assert_eq!( + StructWithWrongNestedNamespace::from_xml("true").unwrap_err(), + Error::WrongNamespace + ); +} + +#[derive(Debug, Eq, PartialEq, FromXml)] +#[xml(namespace("URI", bar = "BAZ"))] +struct NestedOtherNamespace { + #[xml(namespace(bar))] + flag: bool, +} + +#[derive(Debug, Eq, PartialEq, FromXml)] +#[xml(namespace("URI", bar = "BAZ"))] +struct StructOtherNamespace { + test: NestedOtherNamespace, +} + +#[test] +fn other_namespaces() { + // Other namespace not-nested + assert_eq!( + NestedOtherNamespace::from_xml( + "true" + ) + .unwrap(), + NestedOtherNamespace { flag: true } + ); + + // Other namespace not-nested - wrong defined namespace + assert_eq!( + NestedOtherNamespace::from_xml( + "true" + ) + .unwrap_err(), + Error::WrongNamespace + ); + + // Other namespace not-nested - wrong parser namespace + assert_eq!( + NestedOtherNamespace::from_xml( + "true" + ) + .unwrap_err(), + Error::WrongNamespace + ); + + // Other namespace not-nested - missing parser prefix + assert_eq!( + NestedOtherNamespace::from_xml( + "true" + ) + .unwrap_err(), + Error::WrongNamespace + ); + + // Correct child other namespace + assert_eq!( + StructOtherNamespace::from_xml( + "true" + ) + .unwrap(), + StructOtherNamespace { + test: NestedOtherNamespace { + flag: true, + } + } + ); + + // Correct child other namespace - without child redefinition + assert_eq!( + StructOtherNamespace::from_xml( + "true" + ) + .unwrap(), + StructOtherNamespace { + test: NestedOtherNamespace { + flag: true, + } + } + ); + + // Wrong child other namespace - without child redefinition + assert_eq!( + StructOtherNamespace::from_xml( + "true" + ) + .unwrap_err(), + Error::WrongNamespace + ); +} + +#[derive(Debug, Eq, PartialEq, FromXml)] +#[xml(namespace("URI"))] +struct StructDirectNamespace { + #[xml(namespace("BAZ"))] + flag: bool, +} + +#[test] +fn direct_namespaces() { + // Correct direct namespace + assert_eq!( + StructDirectNamespace::from_xml( + "true" + ) + .unwrap(), + StructDirectNamespace { flag: true } + ); + + // Wrong direct namespace + assert_eq!( + StructDirectNamespace::from_xml( + "true" + ) + .unwrap_err(), + Error::WrongNamespace + ); + + // Wrong direct namespace - missing namespace + assert_eq!( + StructDirectNamespace::from_xml( + "true" + ) + .unwrap_err(), + Error::WrongNamespace ); }