From 76a260fece0e5bdc7370c6229a96b7f8e93ca295 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Sat, 3 Sep 2022 11:56:42 +0200 Subject: [PATCH] Allow paths as namespace values --- instant-xml-macros/src/de.rs | 28 +- instant-xml-macros/src/lib.rs | 440 ++++++++++++++++++++++---- instant-xml-macros/src/ser.rs | 71 ++--- instant-xml/tests/all.rs | 509 ------------------------------ instant-xml/tests/de-nested.rs | 8 +- instant-xml/tests/ser-child-ns.rs | 13 +- instant-xml/tests/ser-nested.rs | 8 +- 7 files changed, 435 insertions(+), 642 deletions(-) delete mode 100644 instant-xml/tests/all.rs diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index 2540ec3..1fb4d59 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -35,10 +35,9 @@ impl Deserializer { pub fn new(input: &syn::DeriveInput) -> Deserializer { let ident = &input.ident; let container_meta = ContainerMeta::from_derive(input); - let default_namespace = match &container_meta.ns.default { - Namespace::Default => "", - Namespace::Prefix(_) => panic!("container namespace cannot be prefix"), - Namespace::Literal(ns) => ns, + let default_namespace = match &container_meta.ns.uri { + Some(ns) => quote!(#ns), + None => quote!(""), }; let mut xml_generics = input.generics.clone(); @@ -71,12 +70,6 @@ impl Deserializer { syn::Fields::Named(ref fields) => { fields.named.iter().enumerate().for_each(|(index, 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 { true => &mut attributes_tokens, false => &mut elements_tokens, @@ -230,18 +223,15 @@ impl Deserializer { let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site()); tokens.enum_.extend(quote!(#enum_name,)); - let default_ns = match &field_meta.ns.default { - Namespace::Default => &container_meta.ns.default, - _ => &field_meta.ns.default, + let default_ns = match &field_meta.ns.uri { + None => &container_meta.ns.uri, + _ => &field_meta.ns.uri, }; let ns = match default_ns { - Namespace::Default => "", - Namespace::Prefix(prefix) => match container_meta.ns.prefixes.get(prefix) { - Some(ns) => ns, - None => panic!("undefined prefix {prefix} in xml attribute"), - }, - Namespace::Literal(ns) => ns, + Some(Namespace::Path(path)) => quote!(#path), + Some(Namespace::Literal(ns)) => quote!(#ns), + None => quote!(""), }; tokens.consts.extend(quote!( diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 30a136c..f1559cc 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -3,11 +3,13 @@ extern crate proc_macro; mod de; 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::{parse_macro_input, Meta, NestedMeta}; +use syn::token::Colon2; #[proc_macro_derive(ToXml, attributes(xml))] 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 { ns: NamespaceMeta, } @@ -36,17 +38,15 @@ impl ContainerMeta { let mut meta = ContainerMeta::default(); for item in meta_items(&input.attrs) { match item { - Meta::List(list) if list.path.is_ident("ns") => { - meta.ns = NamespaceMeta::from_list(&list.nested) - } - _ => panic!("invalid xml attribute syntax"), + MetaItem::Attribute => panic!("attribute key invalid in container xml attribute"), + MetaItem::Ns(ns) => meta.ns = ns, } } meta } } -#[derive(Debug, Default)] +#[derive(Default)] struct FieldMeta { attribute: bool, ns: NamespaceMeta, @@ -57,80 +57,396 @@ impl FieldMeta { let mut meta = FieldMeta::default(); for item in meta_items(&input.attrs) { match item { - Meta::Path(path) if path.is_ident("attribute") => meta.attribute = true, - Meta::List(list) if list.path.is_ident("ns") => { - meta.ns = NamespaceMeta::from_list(&list.nested) - } - _ => panic!("invalid xml attribute syntax"), + MetaItem::Attribute => meta.attribute = true, + MetaItem::Ns(ns) => meta.ns = ns, } } meta } } -#[derive(Debug, Default)] +#[derive(Default)] struct NamespaceMeta { - default: Namespace, - prefixes: HashMap, + uri: Option, + prefixes: BTreeMap, } impl NamespaceMeta { - fn from_list(list: &Punctuated) -> NamespaceMeta { - let mut meta = NamespaceMeta::default(); - for (i, item) in list.iter().enumerate() { - match item { - NestedMeta::Meta(inner) => match inner { - Meta::Path(path) => match path.get_ident() { - Some(id) => meta.default = Namespace::Prefix(id.to_string()), - None => panic!("invalid xml attribute syntax"), - }, - Meta::NameValue(nv) => match (nv.path.get_ident(), &nv.lit) { - (Some(id), syn::Lit::Str(lit)) => { - meta.prefixes.insert(id.to_string(), lit.value()); - } - _ => panic!("invalid xml attribute syntax"), - }, - _ => panic!("invalid xml attribute syntax"), - }, - NestedMeta::Lit(syn::Lit::Str(lit)) if i == 0 => { - meta.default = Namespace::Literal(lit.value()) + fn from_tokens(group: Group) -> Self { + let mut new = NamespaceMeta::default(); + let mut state = NsState::Start; + for tree in group.stream() { + state = match (state, tree) { + (NsState::Start, TokenTree::Literal(lit)) => { + new.uri = Some(Namespace::Literal(lit)); + NsState::Comma } - _ => panic!("invalid xml attribute syntax"), - } + (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)), + }, + (NsState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => { + NsState::Prefix + } + ( + NsState::Path { + colon1: None, + colon2: None, + path, + }, + TokenTree::Punct(punct), + ) if punct.as_char() == ':' => NsState::Path { + colon1: Some(punct), + colon2: None, + path, + }, + ( + 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 + } + None => { + let mut segments = Punctuated::new(); + segments.push_value(id.into()); + + syn::Path { + leading_colon: Some(Colon2 { + spans: [colon1.span(), colon2.span()], + }), + segments, + } + } + }; + + 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() + ) + } + }; } - meta + + 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 + '_ { - attrs - .iter() - .filter_map(|attr| { - if !attr.path.is_ident("xml") { - return None; - } +fn meta_items(attrs: &[syn::Attribute]) -> Vec { + let mut items = Vec::new(); + let attr = match attrs.iter().find(|attr| attr.path.is_ident("xml")) { + Some(attr) => attr, + None => return items, + }; - match attr.parse_meta() { - Ok(Meta::List(meta)) => Some(meta.nested.into_iter()), - _ => panic!("unexpected xml attribute syntax"), + 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"); + } + + let mut state = MetaState::Start; + for tree in first { + 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() - .map(|item| match item { - NestedMeta::Meta(item) => item, - NestedMeta::Lit(_) => panic!("unexpected xml attribute syntax"), - }) + (MetaState::Comma, TokenTree::Punct(punct)) if punct.as_char() == ',' => { + MetaState::Start + } + (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)] -enum Namespace { - Default, - Prefix(String), - Literal(String), +enum MetaState { + Start, + Comma, + Ns, } -impl Default for Namespace { - fn default() -> Self { - Namespace::Default +impl MetaState { + fn name(&self) -> &'static str { + match self { + MetaState::Start => "Start", + MetaState::Comma => "Comma", + MetaState::Ns => "Ns", + } + } +} + +enum NsState { + Start, + Comma, + Path { + colon1: Option, + colon2: Option, + path: Option, + }, + Prefix, + Eq { + prefix: Ident, + }, + PrefixValue { + prefix: Ident, + }, + PrefixPath { + prefix: Ident, + colon1: Option, + colon2: Option, + path: Option, + }, +} + +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), + } } } diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index 1a965b0..2079716 100644 --- a/instant-xml-macros/src/ser.rs +++ b/instant-xml-macros/src/ser.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; -use crate::{ContainerMeta, FieldMeta, Namespace}; +use crate::{ContainerMeta, FieldMeta}; pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { let ident = &input.ident; @@ -79,28 +79,34 @@ impl<'a> Serializer { fn add_header(&mut self, output: &'a mut TokenStream) { output.extend(quote!( serializer.output.write_char('<')?; - serializer.output.write_str(field_context.name)?; + )); - let default_namespace = match &self.meta.ns.default { - Namespace::Default => "", - Namespace::Prefix(_) => panic!("type cannot have prefix as namespace"), - Namespace::Literal(ns) => ns, + let default_namespace = match &self.meta.ns.uri { + Some(ns) => quote!(#ns), + None => quote!(""), }; + output.extend(quote!( // Check if parent default namespace equals if serializer.parent_default_namespace() != #default_namespace { - serializer.output.write_str(" xmlns=\"")?; - serializer.output.write_str(#default_namespace)?; - serializer.output.write_char('\"')?; + 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(#default_namespace)?; + serializer.output.write_char('\"')?; + } + } else { + serializer.output.write_str(field_context.name)?; } serializer.update_parent_default_namespace(#default_namespace); )); - let mut sorted_values: Vec<_> = self.meta.ns.prefixes.iter().collect(); - sorted_values.sort(); - - for (key, val) in sorted_values { + for (key, val) in &self.meta.ns.prefixes { output.extend(quote!( if serializer.parent_namespaces.get(#val).is_none() { serializer.output.write_str(" xmlns:")?; @@ -160,37 +166,20 @@ impl<'a> Serializer { return; } - if let Namespace::Literal(ns) = &field_meta.ns.default { - body.extend(quote!( - #declaration - field.attribute = Some(instant_xml::FieldAttribute::Namespace(#ns)); - )); - } 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, - }; - )); - } - None => panic!("Prefix not defined: {}", prefix), - }; - - body.extend(quote!( - field.attribute = Some(instant_xml::FieldAttribute::Prefix(prefix_key)); - )); - } else { - body.extend(quote!( - #declaration - )); + let ns = match field_meta.ns.uri { + Some(ns) => quote!(#ns), + None => match &self.meta.ns.uri { + Some(ns) => quote!(#ns), + None => quote!(""), + }, }; 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)), + } serializer.set_field_context(field)?; self.#field_value.serialize(serializer)?; )); diff --git a/instant-xml/tests/all.rs b/instant-xml/tests/all.rs deleted file mode 100644 index 2a1b78e..0000000 --- a/instant-xml/tests/all.rs +++ /dev/null @@ -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(), ""); - //assert_eq!(Unit::from_xml("").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(), - "truetest1" - ); -} - -#[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(), - "truetruefalse" - ); -} - -#[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(), - "falsefalse" - ); -} - -#[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("falsetrue").unwrap(), - StructWithCustomFieldFromXml { - flag: false, - flag_attribute: true, - test: NestedDe { flag: true } - } - ); - // Different order - assert_eq!( - StructWithCustomFieldFromXml::from_xml("truefalse").unwrap(), - StructWithCustomFieldFromXml { - flag: false, - flag_attribute: 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!( - NestedDe::from_xml( - "true" - ) - .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( - "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(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( - "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(ns("URI"))] -struct StructDirectNamespace { - #[xml(ns("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 - ); -} - -#[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( - "true142stringlifetime alifetime bc1.20trueasd123" - ) - .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( - "true142stringlifetime alifetime bc1.2trueasd123" - ).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( - "<>&"'adsad"strstr&" - ) - .unwrap(), - StructSpecialEntities { - string: String::from("<>&\"'adsad\""), - str: "str", - cow: Cow::Owned("str&".to_string()), - } - ); - - // Wrong str char - assert_eq!( - StructSpecialEntities::from_xml( - "<>&"'adsad"str&" - ) - .unwrap_err(), - Error::Other("Unsupported char: str&".to_string()) - ); - - // Borrowed - let escape_back = StructSpecialEntities::from_xml( - "<>&"'adsad"strstr" - ) - .unwrap(); - - if let Cow::Owned(_) = escape_back.cow { - panic!("Should be Borrowed") - } - - // Owned - let escape_back = StructSpecialEntities::from_xml( - "<>&"'adsad"strstr&" - ) - .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(), - "&"<>'aa&"<>'bb&"<>'cc" - ); -} diff --git a/instant-xml/tests/de-nested.rs b/instant-xml/tests/de-nested.rs index 5c7b08e..9a80a44 100644 --- a/instant-xml/tests/de-nested.rs +++ b/instant-xml/tests/de-nested.rs @@ -3,22 +3,24 @@ use similar_asserts::assert_eq; use instant_xml::{from_str, FromXml}; #[derive(Debug, Eq, PartialEq, FromXml)] -#[xml(ns("URI", bar = "BAZ"))] +#[xml(ns("URI", bar = BAR))] struct NestedDe { - #[xml(ns(bar))] + #[xml(ns(BAR))] flag: bool, } #[derive(Debug, Eq, PartialEq, FromXml)] #[xml(ns("URI", bar = "BAZ", foo = "BAR"))] struct StructWithCustomFieldFromXml { - #[xml(ns(bar))] + #[xml(ns(BAR))] flag: bool, #[xml(attribute)] flag_attribute: bool, test: NestedDe, } +const BAR: &str = "BAZ"; + #[test] fn struct_with_custom_field_from_xml() { assert_eq!( diff --git a/instant-xml/tests/ser-child-ns.rs b/instant-xml/tests/ser-child-ns.rs index 8e83398..3162b2b 100644 --- a/instant-xml/tests/ser-child-ns.rs +++ b/instant-xml/tests/ser-child-ns.rs @@ -3,9 +3,9 @@ use similar_asserts::assert_eq; use instant_xml::{to_string, ToXml}; #[derive(Debug, Eq, PartialEq, ToXml)] -#[xml(ns(dar = "BAZ", internal = "INTERNAL"))] +#[xml(ns(dar = "BAZ", internal = INTERNAL))] struct NestedDifferentNamespace { - #[xml(ns(internal))] + #[xml(ns(INTERNAL))] flag_internal_prefix: bool, } @@ -17,14 +17,17 @@ struct StructChildNamespaces { } #[derive(Debug, Eq, PartialEq, ToXml)] -#[xml(ns("URI", dar = "BAZ", internal = "INTERNAL"))] +#[xml(ns("URI", dar = DAR, internal = INTERNAL))] struct Nested { - #[xml(ns(dar))] + #[xml(ns(DAR))] flag_parent_prefix: bool, - #[xml(ns(internal))] + #[xml(ns(INTERNAL))] flag_internal_prefix: bool, } +const DAR: &str = "BAZ"; +const INTERNAL: &str = "INTERNAL"; + // Tests: // - Different child namespace // - The same child namespace diff --git a/instant-xml/tests/ser-nested.rs b/instant-xml/tests/ser-nested.rs index bd8ae56..2185dc2 100644 --- a/instant-xml/tests/ser-nested.rs +++ b/instant-xml/tests/ser-nested.rs @@ -3,12 +3,14 @@ use similar_asserts::assert_eq; use instant_xml::{to_string, ToXml}; #[derive(Debug, Eq, PartialEq, ToXml)] -#[xml(ns("URI", dar = "BAZ", internal = "INTERNAL"))] +#[xml(ns("URI", dar = "BAZ", internal = INTERNAL))] struct Nested { - #[xml(ns(internal))] + #[xml(ns(INTERNAL))] flag_internal_prefix: bool, } +const INTERNAL: &str = "INTERNAL"; + #[derive(Debug, Eq, PartialEq, ToXml)] #[xml(ns("URI", bar = "BAZ", foo = "BAR"))] struct StructWithCustomField { @@ -42,6 +44,6 @@ fn struct_with_custom_field() { }, }) .unwrap(), - "truetruefalse" + "truetruefalse" ); }