Serialization of custom fields (#6)
This commit is contained in:
parent
afc39e276d
commit
3e9f978846
|
@ -1,8 +1,8 @@
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeSet, HashMap};
|
||||||
use syn::{parse_macro_input, Lit, Meta, NestedMeta};
|
use syn::{parse_macro_input, Lit, Meta, NestedMeta};
|
||||||
|
|
||||||
const XML: &str = "xml";
|
const XML: &str = "xml";
|
||||||
|
@ -54,60 +54,89 @@ impl<'a> Serializer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_header(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) {
|
fn keys_set(&self) -> BTreeSet<&str> {
|
||||||
output.extend(quote!(+ "<" + #root_name));
|
self.other_namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|(k, _)| k.as_str())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_header(&mut self, root_name: &str, output: &'a mut TokenStream) {
|
||||||
|
output.extend(quote!(
|
||||||
|
serializer.output.write_char('<')?;
|
||||||
|
serializer.output.write_str(#root_name)?;
|
||||||
|
));
|
||||||
|
|
||||||
if let Some(default_namespace) = self.default_namespace.as_ref() {
|
if let Some(default_namespace) = self.default_namespace.as_ref() {
|
||||||
output.extend(quote!(+ " xmlns=\"" + #default_namespace + "\""));
|
output.extend(quote!(
|
||||||
|
serializer.output.write_str(" xmlns=\"")?;
|
||||||
|
serializer.output.write_str(#default_namespace)?;
|
||||||
|
serializer.output.write_char('\"')?;
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sorted_values: Vec<_> = self.other_namespaces.iter().collect();
|
let mut sorted_values: Vec<_> = self.other_namespaces.iter().collect();
|
||||||
sorted_values.sort();
|
sorted_values.sort();
|
||||||
|
|
||||||
for (key, val) in sorted_values {
|
for (key, val) in sorted_values {
|
||||||
output.extend(quote!(+ " xmlns:" + #key + "=\"" + #val + "\""));
|
output.extend(quote!(
|
||||||
|
serializer.output.write_str(" xmlns:")?;
|
||||||
|
serializer.output.write_str(#key)?;
|
||||||
|
serializer.output.write_str("=\"")?;
|
||||||
|
serializer.output.write_str(#val)?;
|
||||||
|
serializer.output.write_char('\"')?;
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
output.extend(quote!(
|
||||||
output.extend(quote!(+ ">"));
|
serializer.output.write_char('>')?;
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_footer(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) {
|
fn add_footer(&mut self, root_name: &str, output: &'a mut TokenStream) {
|
||||||
output.extend(quote!(+ "</" + #root_name + ">"));
|
output.extend(quote!(
|
||||||
|
serializer.output.write_str("</")?;
|
||||||
|
serializer.output.write_str(#root_name)?;
|
||||||
|
serializer.output.write_char('>')?;
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_named_field(
|
fn process_named_field(
|
||||||
&mut self,
|
&mut self,
|
||||||
field: &syn::Field,
|
field: &syn::Field,
|
||||||
output: &'a mut proc_macro2::TokenStream,
|
output: &'a mut TokenStream,
|
||||||
|
missing_prefixes: &'a mut BTreeSet<String>,
|
||||||
) {
|
) {
|
||||||
let field_name = field.ident.as_ref().unwrap().to_string();
|
let name = field.ident.as_ref().unwrap().to_string();
|
||||||
let field_value = field.ident.as_ref().unwrap();
|
let field_value = field.ident.as_ref().unwrap();
|
||||||
let mut prefix = String::default();
|
|
||||||
|
output.extend(quote!(
|
||||||
|
let mut field = instant_xml::FieldContext {
|
||||||
|
name: #name,
|
||||||
|
attribute: None,
|
||||||
|
};
|
||||||
|
));
|
||||||
|
|
||||||
match Self::retrieve_field_attribute(field) {
|
match Self::retrieve_field_attribute(field) {
|
||||||
Some(FieldAttribute::Namespace(namespace)) => {
|
Some(FieldAttribute::Namespace(namespace_key)) => {
|
||||||
output.extend(quote!(+ "<" + #field_name + " xmlns=\"" + #namespace + "\""));
|
output.extend(quote!(
|
||||||
|
field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace_key));
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Some(FieldAttribute::PrefixIdentifier(prefix_key))
|
Some(FieldAttribute::PrefixIdentifier(prefix_key)) => {
|
||||||
if !self.other_namespaces.is_empty() =>
|
output.extend(quote!(
|
||||||
{
|
field.attribute = Some(instant_xml::FieldAttribute::Prefix(#prefix_key));
|
||||||
match self.other_namespaces.get(&prefix_key) {
|
));
|
||||||
Some(_) => {
|
|
||||||
prefix = prefix_key + ":";
|
if self.other_namespaces.get(&prefix_key).is_none() {
|
||||||
output.extend(quote!(+ "<" + #prefix + #field_name));
|
missing_prefixes.insert(prefix_key);
|
||||||
}
|
|
||||||
None => todo!(), // return the error
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {}
|
||||||
// Without the namespace
|
|
||||||
output.extend(quote!(+ "<" + #field_name));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
output.extend(
|
output.extend(quote!(
|
||||||
quote!(+ ">" + self.#field_value.to_string().as_str() + "</" + #prefix + #field_name + ">"),
|
self.#field_value.serialize(serializer, Some(&field))?;
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn retrieve_namespace_list(attributes: &Vec<syn::Attribute>) -> Option<syn::MetaList> {
|
fn retrieve_namespace_list(attributes: &Vec<syn::Attribute>) -> Option<syn::MetaList> {
|
||||||
|
@ -154,14 +183,15 @@ impl<'a> Serializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(ToXml, attributes(xml))]
|
#[proc_macro_derive(ToXml, attributes(xml))]
|
||||||
pub fn to_xml(input: TokenStream) -> TokenStream {
|
pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let ast = parse_macro_input!(input as syn::DeriveInput);
|
let ast = parse_macro_input!(input as syn::DeriveInput);
|
||||||
|
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let root_name = ident.to_string();
|
let root_name = ident.to_string();
|
||||||
let mut output: proc_macro2::TokenStream = TokenStream::from(quote!("".to_owned())).into();
|
let mut missing_prefixes = BTreeSet::new();
|
||||||
|
|
||||||
let mut serializer = Serializer::new(&ast.attrs);
|
let mut serializer = Serializer::new(&ast.attrs);
|
||||||
|
|
||||||
|
let mut output = TokenStream::new();
|
||||||
|
|
||||||
serializer.add_header(&root_name, &mut output);
|
serializer.add_header(&root_name, &mut output);
|
||||||
|
|
||||||
match &ast.data {
|
match &ast.data {
|
||||||
|
@ -169,7 +199,7 @@ pub fn to_xml(input: TokenStream) -> TokenStream {
|
||||||
match data.fields {
|
match data.fields {
|
||||||
syn::Fields::Named(ref fields) => {
|
syn::Fields::Named(ref fields) => {
|
||||||
fields.named.iter().for_each(|field| {
|
fields.named.iter().for_each(|field| {
|
||||||
serializer.process_named_field(field, &mut output);
|
serializer.process_named_field(field, &mut output, &mut missing_prefixes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
syn::Fields::Unnamed(_) => todo!(),
|
syn::Fields::Unnamed(_) => todo!(),
|
||||||
|
@ -181,22 +211,50 @@ pub fn to_xml(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
serializer.add_footer(&root_name, &mut output);
|
serializer.add_footer(&root_name, &mut output);
|
||||||
|
|
||||||
TokenStream::from(quote!(
|
let current_prefixes = serializer.keys_set();
|
||||||
|
proc_macro::TokenStream::from(quote!(
|
||||||
impl ToXml for #ident {
|
impl ToXml for #ident {
|
||||||
fn write_xml<W: ::std::fmt::Write>(&self, write: &mut W) -> Result<(), instant_xml::Error> {
|
fn serialize<W>(&self, serializer: &mut instant_xml::Serializer<W>, _field_data: Option<&instant_xml::FieldContext>) -> Result<(), instant_xml::Error>
|
||||||
write.write_str(&(#output))?;
|
where
|
||||||
|
W: std::fmt::Write,
|
||||||
|
{
|
||||||
|
let mut field_context = instant_xml::FieldContext {
|
||||||
|
name: #root_name,
|
||||||
|
attribute: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if prefix exist
|
||||||
|
#(
|
||||||
|
if serializer.parent_prefixes.get(#missing_prefixes).is_none() {
|
||||||
|
return Err(instant_xml::Error::WrongPrefix);
|
||||||
|
}
|
||||||
|
)*;
|
||||||
|
|
||||||
|
// Adding current prefixes
|
||||||
|
let mut to_remove: Vec<&str> = Vec::new();
|
||||||
|
#(if serializer.parent_prefixes.insert(#current_prefixes) {
|
||||||
|
to_remove.push(#current_prefixes);
|
||||||
|
};)*;
|
||||||
|
|
||||||
|
#output
|
||||||
|
|
||||||
|
// Removing current prefixes
|
||||||
|
for it in to_remove {
|
||||||
|
serializer.parent_prefixes.remove(it);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(FromXml, attributes(xml))]
|
#[proc_macro_derive(FromXml, attributes(xml))]
|
||||||
pub fn from_xml(input: TokenStream) -> TokenStream {
|
pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let ast = parse_macro_input!(input as syn::ItemStruct);
|
let ast = parse_macro_input!(input as syn::ItemStruct);
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let name = ident.to_string();
|
let name = ident.to_string();
|
||||||
TokenStream::from(quote!(
|
proc_macro::TokenStream::from(quote!(
|
||||||
impl<'xml> FromXml<'xml> for #ident {
|
impl<'xml> FromXml<'xml> for #ident {
|
||||||
fn from_xml(input: &str) -> Result<Self, ::instant_xml::Error> {
|
fn from_xml(input: &str) -> Result<Self, ::instant_xml::Error> {
|
||||||
use ::instant_xml::parse::Parse;
|
use ::instant_xml::parse::Parse;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeSet, HashMap};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -10,13 +10,175 @@ pub use macros::{FromXml, ToXml};
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
|
||||||
pub trait ToXml {
|
pub trait ToXml {
|
||||||
fn write_xml<W: fmt::Write>(&self, write: &mut W) -> Result<(), Error>;
|
|
||||||
|
|
||||||
fn to_xml(&self) -> Result<String, Error> {
|
fn to_xml(&self) -> Result<String, Error> {
|
||||||
let mut out = String::new();
|
let mut output = String::new();
|
||||||
self.write_xml(&mut out)?;
|
let mut serializer = Serializer::new(&mut output);
|
||||||
Ok(out)
|
self.serialize(&mut serializer, None)?;
|
||||||
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize<W>(
|
||||||
|
&self,
|
||||||
|
serializer: &mut Serializer<W>,
|
||||||
|
field_context: Option<&FieldContext>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: fmt::Write;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! to_xml_for_number {
|
||||||
|
($typ:ty) => {
|
||||||
|
impl ToXml for $typ {
|
||||||
|
fn serialize<W>(
|
||||||
|
&self,
|
||||||
|
serializer: &mut Serializer<W>,
|
||||||
|
field_context: Option<&FieldContext>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
match field_context {
|
||||||
|
Some(field_context) => {
|
||||||
|
serializer.add_open_tag(field_context)?;
|
||||||
|
write!(serializer.output, "{}", &self)?;
|
||||||
|
serializer.add_close_tag(field_context)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => Err(Error::UnexpectedValue),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
to_xml_for_number!(i8);
|
||||||
|
to_xml_for_number!(i16);
|
||||||
|
to_xml_for_number!(i32);
|
||||||
|
to_xml_for_number!(i64);
|
||||||
|
to_xml_for_number!(u8);
|
||||||
|
to_xml_for_number!(u16);
|
||||||
|
to_xml_for_number!(u32);
|
||||||
|
to_xml_for_number!(u64);
|
||||||
|
|
||||||
|
impl ToXml for bool {
|
||||||
|
fn serialize<W>(
|
||||||
|
&self,
|
||||||
|
serializer: &mut Serializer<W>,
|
||||||
|
field_context: Option<&FieldContext>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
let value = match self {
|
||||||
|
true => "true",
|
||||||
|
false => "false",
|
||||||
|
};
|
||||||
|
|
||||||
|
match field_context {
|
||||||
|
Some(field_context) => {
|
||||||
|
serializer.add_open_tag(field_context)?;
|
||||||
|
serializer.output.write_str(value)?;
|
||||||
|
serializer.add_close_tag(field_context)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => Err(Error::UnexpectedValue),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToXml for String {
|
||||||
|
fn serialize<W>(
|
||||||
|
&self,
|
||||||
|
serializer: &mut Serializer<W>,
|
||||||
|
field_context: Option<&FieldContext>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
match field_context {
|
||||||
|
Some(field_context) => {
|
||||||
|
serializer.add_open_tag(field_context)?;
|
||||||
|
serializer.output.write_str(self)?;
|
||||||
|
serializer.add_close_tag(field_context)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
None => Err(Error::UnexpectedValue),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Serializer<'xml, W>
|
||||||
|
where
|
||||||
|
W: fmt::Write,
|
||||||
|
{
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub parent_prefixes: BTreeSet<&'xml str>,
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub output: &'xml mut W,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'xml, W: std::fmt::Write> Serializer<'xml, W> {
|
||||||
|
pub fn new(output: &'xml mut W) -> Self {
|
||||||
|
Self {
|
||||||
|
parent_prefixes: BTreeSet::new(),
|
||||||
|
output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_open_tag(&mut self, field_context: &FieldContext) -> Result<(), Error> {
|
||||||
|
match field_context.attribute {
|
||||||
|
Some(FieldAttribute::Prefix(prefix)) => {
|
||||||
|
self.output.write_char('<')?;
|
||||||
|
self.output.write_str(prefix)?;
|
||||||
|
self.output.write_char(':')?;
|
||||||
|
self.output.write_str(field_context.name)?;
|
||||||
|
self.output.write_char('>')?;
|
||||||
|
}
|
||||||
|
Some(FieldAttribute::Namespace(namespace)) => {
|
||||||
|
self.output.write_char('<')?;
|
||||||
|
self.output.write_str(field_context.name)?;
|
||||||
|
self.output.write_str(" xmlns=\"")?;
|
||||||
|
self.output.write_str(namespace)?;
|
||||||
|
self.output.write_str("\">")?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.output.write_char('<')?;
|
||||||
|
self.output.write_str(field_context.name)?;
|
||||||
|
self.output.write_char('>')?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_close_tag(&mut self, field_context: &FieldContext) -> Result<(), Error> {
|
||||||
|
match field_context.attribute {
|
||||||
|
Some(FieldAttribute::Prefix(prefix)) => {
|
||||||
|
self.output.write_str("</")?;
|
||||||
|
self.output.write_str(prefix)?;
|
||||||
|
self.output.write_char(':')?;
|
||||||
|
self.output.write_str(field_context.name)?;
|
||||||
|
self.output.write_char('>')?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.output.write_str("</")?;
|
||||||
|
self.output.write_str(field_context.name)?;
|
||||||
|
self.output.write_char('>')?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FieldAttribute<'xml> {
|
||||||
|
Prefix(&'xml str),
|
||||||
|
Namespace(&'xml str),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FieldContext<'xml> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub name: &'xml str,
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub attribute: Option<FieldAttribute<'xml>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FromXml<'xml>: Sized {
|
pub trait FromXml<'xml>: Sized {
|
||||||
|
@ -40,4 +202,6 @@ pub enum Error {
|
||||||
UnexpectedEndOfStream,
|
UnexpectedEndOfStream,
|
||||||
#[error("unexpected value")]
|
#[error("unexpected value")]
|
||||||
UnexpectedValue,
|
UnexpectedValue,
|
||||||
|
#[error("wrong prefix")]
|
||||||
|
WrongPrefix,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,30 @@ use instant_xml::{FromXml, ToXml};
|
||||||
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
|
#[derive(Debug, Eq, FromXml, PartialEq, ToXml)]
|
||||||
struct Unit;
|
struct Unit;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
|
#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))]
|
||||||
|
struct StructWithCustomField {
|
||||||
|
test: Nested,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
|
struct Nested {
|
||||||
|
#[xml(namespace(bar))]
|
||||||
|
flag: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
|
#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))]
|
||||||
|
struct StructWithCustomFieldWrongPrefix {
|
||||||
|
test: NestedWrongPrefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
|
struct NestedWrongPrefix {
|
||||||
|
#[xml(namespace(dar))]
|
||||||
|
flag: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))]
|
#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))]
|
||||||
struct StructWithNamedFields {
|
struct StructWithNamedFields {
|
||||||
|
@ -32,3 +56,28 @@ fn struct_with_named_fields() {
|
||||||
"<StructWithNamedFields xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><flag>true</flag><bar:string>test</bar:string><number xmlns=\"typo\">1</number></StructWithNamedFields>"
|
"<StructWithNamedFields xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><flag>true</flag><bar:string>test</bar:string><number xmlns=\"typo\">1</number></StructWithNamedFields>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_with_custom_field() {
|
||||||
|
assert_eq!(
|
||||||
|
StructWithCustomField {
|
||||||
|
test: Nested {
|
||||||
|
flag: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.to_xml()
|
||||||
|
.unwrap(),
|
||||||
|
"<StructWithCustomField xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><Nested><bar:flag>true</bar:flag></Nested></StructWithCustomField>"
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn struct_with_custom_field_wrong_prefix() {
|
||||||
|
StructWithCustomFieldWrongPrefix {
|
||||||
|
test: NestedWrongPrefix { flag: true },
|
||||||
|
}
|
||||||
|
.to_xml()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue