From 07a258f8fc864cde9eed3224ae09357a87cc387e Mon Sep 17 00:00:00 2001 From: Marc-Antoine Arnaud Date: Sun, 13 May 2018 17:43:36 +0200 Subject: [PATCH] support de/ser-ialization with namespace --- yaserde/src/de/mod.rs | 17 ---- yaserde/tests/der_namespace.rs | 59 +++++++++++ yaserde/tests/se_namespace.rs | 127 ++++++++++++++++++++++++ yaserde_derive/src/attribute.rs | 41 ++++++-- yaserde_derive/src/de/expand_enum.rs | 3 +- yaserde_derive/src/de/expand_struct.rs | 28 +++++- yaserde_derive/src/de/mod.rs | 4 +- yaserde_derive/src/ser/expand_enum.rs | 21 +++- yaserde_derive/src/ser/expand_struct.rs | 31 ++++-- yaserde_derive/src/ser/mod.rs | 11 +- 10 files changed, 300 insertions(+), 42 deletions(-) create mode 100644 yaserde/tests/der_namespace.rs create mode 100644 yaserde/tests/se_namespace.rs diff --git a/yaserde/src/de/mod.rs b/yaserde/src/de/mod.rs index 3ad0b7e..fee2536 100644 --- a/yaserde/src/de/mod.rs +++ b/yaserde/src/de/mod.rs @@ -130,21 +130,4 @@ impl<'de, R: Read> Deserializer { ))) } } - - // fn prepare_parse_type>(&mut self) -> Result { - // if let XmlEvent::StartElement { .. } = *self.peek()? { - // self.set_map_value() - // } - // self.read_inner_value::(|this| { - // if let XmlEvent::EndElement { .. } = *this.peek()? { - // return Err( - // ErrorKind::UnexpectedToken("EndElement".into(), "Characters".into()).into(), - // ); - // } - - // expect!(this.next()?, XmlEvent::Characters(s) => { - // return Ok(s) - // }) - // }) - // } } diff --git a/yaserde/tests/der_namespace.rs b/yaserde/tests/der_namespace.rs new file mode 100644 index 0000000..d774e81 --- /dev/null +++ b/yaserde/tests/der_namespace.rs @@ -0,0 +1,59 @@ + +extern crate yaserde; +#[macro_use] +extern crate yaserde_derive; +extern crate xml; +#[macro_use] +extern crate log; + +use std::io::Read; +use yaserde::YaDeserialize; +use yaserde::de::from_str; + +macro_rules! convert_and_validate { + ($content:expr, $struct:tt, $model:expr) => { + let loaded : Result<$struct, String> = from_str($content); + assert_eq!(loaded, Ok($model)); + } +} + +#[test] +fn de_struct_namespace() { + #[derive(YaDeserialize, PartialEq, Debug)] + #[yaserde(root="book", prefix="ns", namespace="ns: http://www.sample.com/ns/domain")] + pub struct Book { + #[yaserde(prefix="ns")] + author: String, + #[yaserde(prefix="ns")] + title: String, + } + + let content = "Antoine de Saint-ExupéryLittle prince"; + convert_and_validate!(content, Book, Book{ + author: String::from("Antoine de Saint-Exupéry"), + title: String::from("Little prince") + }); + + let content = "Antoine de Saint-ExupéryLittle prince"; + let loaded : Result = from_str(content); + assert_eq!(loaded, Err("bad namespace".to_string())); +} + +#[test] +fn de_enum_namespace() { + #[derive(YaDeserialize, PartialEq, Debug)] + #[yaserde(root="root", prefix="ns", namespace="ns: http://www.sample.com/ns/domain")] + pub enum XmlStruct { + #[yaserde(prefix="ns")] + Item + } + + impl Default for XmlStruct { + fn default() -> XmlStruct { + XmlStruct::Item + } + } + + let content = "ns:Item"; + convert_and_validate!(content, XmlStruct, XmlStruct::Item); +} diff --git a/yaserde/tests/se_namespace.rs b/yaserde/tests/se_namespace.rs new file mode 100644 index 0000000..1289ddd --- /dev/null +++ b/yaserde/tests/se_namespace.rs @@ -0,0 +1,127 @@ + +extern crate yaserde; +#[macro_use] +extern crate yaserde_derive; +extern crate xml; +#[macro_use] +extern crate log; + +use std::io::Write; +use yaserde::YaSerialize; +use yaserde::ser::to_string; + +macro_rules! convert_and_validate { + ($model:expr, $content:expr) => ( + let data : Result = to_string(&$model); + assert_eq!(data, Ok(String::from($content))); + ) +} + +#[test] +fn ser_struct_namespace() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(root="root", prefix="ns", namespace="ns: http://www.sample.com/ns/domain")] + pub struct XmlStruct { + #[yaserde(prefix="ns")] + item: String + } + + let model = XmlStruct { + item: "something".to_string() + }; + + let content = "something"; + convert_and_validate!(model, content); +} + +#[test] +fn ser_enum_namespace() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(root="root", prefix="ns", namespace="ns: http://www.sample.com/ns/domain")] + pub enum XmlStruct { + #[yaserde(prefix="ns")] + Item + } + + let model = XmlStruct::Item; + + let content = "ns:Item"; + convert_and_validate!(model, content); +} + +#[test] +fn ser_struct_multi_namespace() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(root="root", namespace="ns1: http://www.sample.com/ns/domain1", namespace="ns2: http://www.sample.com/ns/domain2")] + pub struct XmlStruct { + #[yaserde(prefix="ns1")] + item_1: String, + #[yaserde(prefix="ns2")] + item_2: String, + } + + let model = XmlStruct { + item_1: "something 1".to_string(), + item_2: "something 2".to_string(), + }; + + let content = "something 1something 2"; + convert_and_validate!(model, content); +} + + +#[test] +fn ser_enum_multi_namespace() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(root="root", namespace="ns1: http://www.sample.com/ns/domain1", namespace="ns2: http://www.sample.com/ns/domain2")] + pub enum XmlStruct { + #[yaserde(prefix="ns1")] + Item1, + #[yaserde(prefix="ns2")] + Item2, + } + + let model1 = XmlStruct::Item1; + let content = "ns1:Item1"; + convert_and_validate!(model1, content); + let model2 = XmlStruct::Item2; + let content = "ns2:Item2"; + convert_and_validate!(model2, content); +} + + +#[test] +fn ser_struct_attribute_namespace() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(root="root", namespace="ns1: http://www.sample.com/ns/domain1", namespace="ns2: http://www.sample.com/ns/domain2")] + pub struct XmlStruct { + #[yaserde(prefix="ns1")] + item_1: String, + #[yaserde(attribute, prefix="ns2")] + item_2: String, + } + + let model = XmlStruct { + item_1: "something 1".to_string(), + item_2: "something 2".to_string(), + }; + + let content = "something 1"; + convert_and_validate!(model, content); +} + +#[test] +fn ser_struct_default_namespace() { + #[derive(YaSerialize, PartialEq, Debug)] + #[yaserde(root="tt", namespace="http://www.w3.org/ns/ttml", namespace="ttm: http://www.w3.org/ns/ttml#metadata")] + pub struct XmlStruct { + item: String + } + + let model = XmlStruct { + item: "something".to_string() + }; + + let content = "something"; + convert_and_validate!(model, content); +} diff --git a/yaserde_derive/src/attribute.rs b/yaserde_derive/src/attribute.rs index 3830548..22df692 100644 --- a/yaserde_derive/src/attribute.rs +++ b/yaserde_derive/src/attribute.rs @@ -3,12 +3,15 @@ use proc_macro2::TokenTreeIter; use proc_macro2::TokenNode::*; use proc_macro2::Spacing; use proc_macro2::Delimiter::Parenthesis; +use std::collections::BTreeMap; use syn::Attribute; #[derive(Debug, Clone)] pub struct YaSerdeAttribute { pub root: Option, pub rename: Option, + pub prefix: Option, + pub namespaces: BTreeMap, pub attribute: bool, pub text: bool, } @@ -29,9 +32,11 @@ fn get_value(iter: &mut TokenTreeIter) -> Option { impl YaSerdeAttribute { pub fn parse(attrs: &Vec) -> YaSerdeAttribute { - let mut root = None; - let mut rename = None; let mut attribute = false; + let mut namespaces = BTreeMap::new(); + let mut prefix = None; + let mut rename = None; + let mut root = None; let mut text = false; for attr in attrs.iter() { @@ -46,15 +51,31 @@ impl YaSerdeAttribute { match item.kind { Term(t) => { match t.as_str() { - "root" => { - root = get_value(&mut attr_iter); + "attribute" => { + attribute = true; }, + "namespace" => { + if let Some(namespace) = get_value(&mut attr_iter) { + + let splitted : Vec<&str> = namespace.split(": ").collect(); + if splitted.len() == 2 { + namespaces.insert(splitted[0].to_owned(), splitted[1].to_owned()); + } + if splitted.len() == 1 { + namespaces.insert("".to_owned(), splitted[0].to_owned()); + } + + } + }, + "prefix" => { + prefix = get_value(&mut attr_iter); + } "rename" => { rename = get_value(&mut attr_iter); }, - "attribute" => { - attribute = true; - } + "root" => { + root = get_value(&mut attr_iter); + }, "text" => { text = true; } @@ -73,9 +94,11 @@ impl YaSerdeAttribute { } YaSerdeAttribute { - root: root, - rename: rename, attribute: attribute, + namespaces: namespaces, + prefix: prefix, + rename: rename, + root: root, text: text, } } diff --git a/yaserde_derive/src/de/expand_enum.rs b/yaserde_derive/src/de/expand_enum.rs index c132ff0..3848680 100644 --- a/yaserde_derive/src/de/expand_enum.rs +++ b/yaserde_derive/src/de/expand_enum.rs @@ -2,12 +2,13 @@ use attribute::*; use field_type::*; use quote::Tokens; +use std::collections::BTreeMap; use syn::Fields; use syn::Ident; use syn::DataEnum; use proc_macro2::Span; -pub fn parse(data_enum: &DataEnum, name: &Ident, root: &String) -> Tokens { +pub fn parse(data_enum: &DataEnum, name: &Ident, root: &String, namespaces: &BTreeMap) -> Tokens { let variables : Tokens = data_enum.variants.iter().map(|ref variant| { match variant.fields { diff --git a/yaserde_derive/src/de/expand_struct.rs b/yaserde_derive/src/de/expand_struct.rs index 22961a6..577c6e2 100644 --- a/yaserde_derive/src/de/expand_struct.rs +++ b/yaserde_derive/src/de/expand_struct.rs @@ -2,11 +2,33 @@ use attribute::*; use field_type::*; use quote::Tokens; +use std::collections::BTreeMap; use syn::Ident; use syn::DataStruct; use proc_macro2::Span; -pub fn parse(data_struct: &DataStruct, name: &Ident, root: &String) -> Tokens { +pub fn parse(data_struct: &DataStruct, name: &Ident, root: &String, namespaces: &BTreeMap) -> Tokens { + + let validate_namespace : Tokens = namespaces.iter().map(|(ref prefix, ref namespace)| { + Some(quote!( + + let mut found = false; + for (key, value) in namespace { + if #namespace == value { + found = true; + } + } + if !found { + return Err("bad namespace".to_string()); + } + // println!("{}: {}", #prefix, #namespace); + )) + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut tokens, token| {tokens.append_all(token); tokens}); + + let variables: Tokens = data_struct.fields.iter().map(|ref field| { let label = field.ident; @@ -353,7 +375,9 @@ pub fn parse(data_struct: &DataStruct, name: &Ident, root: &String) -> Tokens { loop { match reader.peek()?.to_owned() { - XmlEvent::StartElement{ref name, ref attributes, ..} => { + XmlEvent::StartElement{ref name, ref attributes, ref namespace} => { + #validate_namespace + match name.local_name.as_str() { #call_visitors named_element => { diff --git a/yaserde_derive/src/de/mod.rs b/yaserde_derive/src/de/mod.rs index 37d72fe..098207b 100644 --- a/yaserde_derive/src/de/mod.rs +++ b/yaserde_derive/src/de/mod.rs @@ -19,10 +19,10 @@ pub fn expand_derive_deserialize(ast: &syn::DeriveInput) -> Result { - expand_struct::parse(data_struct, &name, &root) + expand_struct::parse(data_struct, &name, &root, &root_attrs.namespaces) }, &syn::Data::Enum(ref data_enum) => { - expand_enum::parse(data_enum, &name, &root) + expand_enum::parse(data_enum, &name, &root, &root_attrs.namespaces) }, &syn::Data::Union(ref _data_union) => { unimplemented!() diff --git a/yaserde_derive/src/ser/expand_enum.rs b/yaserde_derive/src/ser/expand_enum.rs index cc57078..b7edf4c 100644 --- a/yaserde_derive/src/ser/expand_enum.rs +++ b/yaserde_derive/src/ser/expand_enum.rs @@ -2,12 +2,13 @@ use attribute::*; use field_type::*; use quote::Tokens; +use std::collections::BTreeMap; use syn::Fields; use syn::Ident; use syn::DataEnum; use proc_macro2::Span; -pub fn serialize(data_enum: &DataEnum, name: &Ident, root: &String) -> Tokens { +pub fn serialize(data_enum: &DataEnum, name: &Ident, root: &String, namespaces: &BTreeMap) -> Tokens { let write_enum_content : Tokens = data_enum.variants.iter().map(|ref variant| { let variant_attrs = YaSerdeAttribute::parse(&variant.attrs); @@ -17,7 +18,12 @@ pub fn serialize(data_enum: &DataEnum, name: &Ident, root: &String) -> Tokens { None => variant.ident }; let label = variant.ident; - let label_name = renamed_label.to_string(); + let label_name = + if let Some(prefix) = variant_attrs.prefix { + prefix + ":" + renamed_label.to_string().as_ref() + } else { + renamed_label.to_string() + }; match variant.fields { Fields::Unit => { @@ -133,6 +139,15 @@ pub fn serialize(data_enum: &DataEnum, name: &Ident, root: &String) -> Tokens { .map(|x| x.unwrap()) .fold(Tokens::new(), |mut tokens, token| {tokens.append_all(token); tokens}); + let add_namespaces : Tokens = namespaces.iter().map(|(ref prefix, ref namespace)| { + Some(quote!( + .ns(#prefix, #namespace) + )) + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut tokens, token| {tokens.append_all(token); tokens}); + quote! { use xml::writer::XmlEvent; @@ -142,7 +157,7 @@ pub fn serialize(data_enum: &DataEnum, name: &Ident, root: &String) -> Tokens { error!("Enum: start to expand {:?}", #root); if !writer.skip_start_end() { - let struct_start_event = XmlEvent::start_element(#root); + let struct_start_event = XmlEvent::start_element(#root)#add_namespaces; let _ret = writer.write(struct_start_event); } diff --git a/yaserde_derive/src/ser/expand_struct.rs b/yaserde_derive/src/ser/expand_struct.rs index c2136b2..17c05ce 100644 --- a/yaserde_derive/src/ser/expand_struct.rs +++ b/yaserde_derive/src/ser/expand_struct.rs @@ -2,12 +2,13 @@ use attribute::*; use field_type::*; use quote::Tokens; +use std::collections::BTreeMap; use syn::Ident; use syn::DataStruct; use proc_macro2::Span; use std::string::ToString; -pub fn serialize(data_struct: &DataStruct, name: &Ident, root: &String) -> Tokens { +pub fn serialize(data_struct: &DataStruct, name: &Ident, root: &String, namespaces: &BTreeMap) -> Tokens { let build_attributes : Tokens = data_struct.fields.iter().map(|ref field| { let field_attrs = YaSerdeAttribute::parse(&field.attrs); @@ -21,7 +22,12 @@ pub fn serialize(data_struct: &DataStruct, name: &Ident, root: &String) -> Token None => field.ident }; let label = field.ident; - let label_name = renamed_label.unwrap().to_string(); + let label_name = + if let Some(prefix) = field_attrs.prefix { + prefix + ":" + renamed_label.unwrap().to_string().as_ref() + } else { + renamed_label.unwrap().to_string() + }; match get_field_type(field) { Some(FieldType::FieldTypeString) => @@ -53,6 +59,15 @@ pub fn serialize(data_struct: &DataStruct, name: &Ident, root: &String) -> Token .map(|x| x.unwrap()) .fold(Tokens::new(), |mut tokens, token| {tokens.append_all(token); tokens}); + let add_namespaces : Tokens = namespaces.iter().map(|(ref prefix, ref namespace)| { + Some(quote!( + .ns(#prefix, #namespace) + )) + }) + .filter(|x| x.is_some()) + .map(|x| x.unwrap()) + .fold(Tokens::new(), |mut tokens, token| {tokens.append_all(token); tokens}); + let struct_inspector : Tokens = data_struct.fields.iter().map(|ref field| { let field_attrs = YaSerdeAttribute::parse(&field.attrs); @@ -73,7 +88,13 @@ pub fn serialize(data_struct: &DataStruct, name: &Ident, root: &String) -> Token Some(value) => Some(Ident::new(&format!("{}", value), Span::call_site())), None => field.ident }; - let label_name = renamed_label.unwrap().to_string(); + + let label_name = + if let Some(prefix) = field_attrs.prefix { + prefix + ":" + renamed_label.unwrap().to_string().as_ref() + } else { + renamed_label.unwrap().to_string() + }; match get_field_type(field) { Some(FieldType::FieldTypeString) => @@ -136,8 +157,6 @@ pub fn serialize(data_struct: &DataStruct, name: &Ident, root: &String) -> Token .map(|x| x.unwrap()) .fold(Tokens::new(), |mut tokens, token| {tokens.append_all(token); tokens}); - // println!("{:?}", struct_inspector); - quote! { use xml::writer::XmlEvent; @@ -147,7 +166,7 @@ pub fn serialize(data_struct: &DataStruct, name: &Ident, root: &String) -> Token error!("Struct: start to expand {:?}", #root); if !writer.skip_start_end() { - let struct_start_event = XmlEvent::start_element(#root)#build_attributes; + let struct_start_event = XmlEvent::start_element(#root)#build_attributes#add_namespaces; let _ret = writer.write(struct_start_event); } diff --git a/yaserde_derive/src/ser/mod.rs b/yaserde_derive/src/ser/mod.rs index f21ed76..60c3157 100644 --- a/yaserde_derive/src/ser/mod.rs +++ b/yaserde_derive/src/ser/mod.rs @@ -16,13 +16,20 @@ pub fn expand_derive_serialize(ast: &syn::DeriveInput) -> Result { - expand_struct::serialize(data_struct, &name, &root) + expand_struct::serialize(data_struct, &name, &root, &root_attrs.namespaces) }, &syn::Data::Enum(ref data_enum) => { - expand_enum::serialize(data_enum, &name, &root) + expand_enum::serialize(data_enum, &name, &root, &root_attrs.namespaces) }, &syn::Data::Union(ref _data_union) => { unimplemented!()