Serialization of custom fields (#6)

This commit is contained in:
choinskib 2022-08-03 13:19:13 +02:00 committed by GitHub
parent afc39e276d
commit 3e9f978846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 319 additions and 48 deletions

View File

@ -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!(
serializer.output.write_char('>')?;
));
} }
output.extend(quote!(+ ">")); fn add_footer(&mut self, root_name: &str, output: &'a mut TokenStream) {
} output.extend(quote!(
serializer.output.write_str("</")?;
fn add_footer(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) { serializer.output.write_str(#root_name)?;
output.extend(quote!(+ "</" + #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;

View File

@ -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,
} }

View File

@ -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();
}