Turn serializer API into a proper abstraction

This commit is contained in:
Dirkjan Ochtman 2022-09-07 11:14:41 +02:00
parent 0f22e36844
commit 6238ed87c5
7 changed files with 200 additions and 225 deletions

View File

@ -1,7 +1,7 @@
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::quote; use quote::quote;
use crate::{ContainerMeta, FieldMeta, Namespace}; use super::{discard_lifetimes, ContainerMeta, FieldMeta, Namespace};
pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream { pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
let ident = &input.ident; let ident = &input.ident;
@ -80,8 +80,8 @@ pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
quote!( quote!(
impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause {
fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result<Self, ::instant_xml::Error> { fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result<Self, ::instant_xml::Error> {
use ::instant_xml::de::{Deserializer, Id, Node}; use ::instant_xml::de::{Deserializer, Node};
use ::instant_xml::Error; use ::instant_xml::{Error, Id};
use ::core::marker::PhantomData; use ::core::marker::PhantomData;
enum __Elements { enum __Elements {
@ -142,7 +142,7 @@ pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream {
Ok(Self { #return_val }) Ok(Self { #return_val })
} }
const KIND: ::instant_xml::de::Kind = ::instant_xml::de::Kind::Element(::instant_xml::de::Id { const KIND: ::instant_xml::Kind = ::instant_xml::Kind::Element(::instant_xml::Id {
ns: #default_namespace, ns: #default_namespace,
name: #name, name: #name,
}); });
@ -181,7 +181,7 @@ fn process_field(
}; };
tokens.consts.extend(quote!( tokens.consts.extend(quote!(
const #const_field_var_str: Id<'static> = <#no_lifetime_type>::KIND.name( const #const_field_var_str: Id<'static> = <#no_lifetime_type as FromXml<'_>>::KIND.name(
Id { ns: #ns, name: #field_var_str } Id { ns: #ns, name: #field_var_str }
); );
)); ));
@ -249,40 +249,3 @@ impl Default for Tokens {
} }
} }
} }
fn discard_lifetimes(ty: &mut syn::Type) {
match ty {
syn::Type::Path(ty) => discard_path_lifetimes(ty),
syn::Type::Reference(ty) => {
ty.lifetime = None;
discard_lifetimes(&mut ty.elem);
}
_ => {}
}
}
fn discard_path_lifetimes(path: &mut syn::TypePath) {
if let Some(q) = &mut path.qself {
discard_lifetimes(&mut q.ty);
}
for segment in &mut path.path.segments {
match &mut segment.arguments {
syn::PathArguments::None => {}
syn::PathArguments::AngleBracketed(args) => {
args.args.iter_mut().for_each(|arg| match arg {
syn::GenericArgument::Lifetime(lt) => {
*lt = syn::Lifetime::new("'_", Span::call_site())
}
syn::GenericArgument::Type(ty) => discard_lifetimes(ty),
syn::GenericArgument::Binding(_)
| syn::GenericArgument::Constraint(_)
| syn::GenericArgument::Const(_) => {}
})
}
syn::PathArguments::Parenthesized(args) => {
args.inputs.iter_mut().for_each(discard_lifetimes)
}
}
}
}

View File

@ -5,7 +5,7 @@ mod ser;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, TokenStream, TokenTree}; use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
use quote::ToTokens; use quote::ToTokens;
use syn::parse_macro_input; use syn::parse_macro_input;
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
@ -455,3 +455,40 @@ impl ToTokens for Namespace {
} }
} }
} }
fn discard_lifetimes(ty: &mut syn::Type) {
match ty {
syn::Type::Path(ty) => discard_path_lifetimes(ty),
syn::Type::Reference(ty) => {
ty.lifetime = None;
discard_lifetimes(&mut ty.elem);
}
_ => {}
}
}
fn discard_path_lifetimes(path: &mut syn::TypePath) {
if let Some(q) = &mut path.qself {
discard_lifetimes(&mut q.ty);
}
for segment in &mut path.path.segments {
match &mut segment.arguments {
syn::PathArguments::None => {}
syn::PathArguments::AngleBracketed(args) => {
args.args.iter_mut().for_each(|arg| match arg {
syn::GenericArgument::Lifetime(lt) => {
*lt = syn::Lifetime::new("'_", Span::call_site())
}
syn::GenericArgument::Type(ty) => discard_lifetimes(ty),
syn::GenericArgument::Binding(_)
| syn::GenericArgument::Constraint(_)
| syn::GenericArgument::Const(_) => {}
})
}
syn::PathArguments::Parenthesized(args) => {
args.inputs.iter_mut().for_each(discard_lifetimes)
}
}
}
}

View File

@ -1,7 +1,7 @@
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use crate::{ContainerMeta, FieldMeta}; use super::{discard_lifetimes, ContainerMeta, FieldMeta};
pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
let mut body = TokenStream::new(); let mut body = TokenStream::new();
@ -26,11 +26,7 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
for (key, val) in &meta.ns.prefixes { for (key, val) in &meta.ns.prefixes {
prefixes.extend(quote!( prefixes.extend(quote!(
if serializer.parent_namespaces.get(#val).is_none() { if serializer.parent_namespaces.get(#val).is_none() {
serializer.output.write_str(" xmlns:")?; serializer.write_prefix(#key, #val)?;
serializer.output.write_str(#key)?;
serializer.output.write_str("=\"")?;
serializer.output.write_str(#val)?;
serializer.output.write_char('\"')?;
} }
if let ::std::collections::hash_map::Entry::Vacant(v) = serializer.parent_namespaces.entry(#val) { if let ::std::collections::hash_map::Entry::Vacant(v) = serializer.parent_namespaces.entry(#val) {
@ -55,44 +51,22 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
&self, &self,
serializer: &mut instant_xml::Serializer<W>, serializer: &mut instant_xml::Serializer<W>,
) -> Result<(), instant_xml::Error> { ) -> Result<(), instant_xml::Error> {
use ::instant_xml::ser::{FieldAttribute, FieldContext};
let _ = serializer.consume_field_context();
let mut field_context = FieldContext {
name: #root_name,
attribute: None,
};
// Start tag // Start tag
serializer.output.write_char('<')?; match serializer.parent_default_namespace() == #default_namespace {
if serializer.parent_default_namespace() != #default_namespace { true => serializer.write_start(None, #root_name, None)?,
if let Some(prefix) = serializer.parent_namespaces.get(#default_namespace) { false => serializer.write_start(None, #root_name, Some(#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); serializer.update_parent_default_namespace(#default_namespace);
let mut to_remove: Vec<&str> = Vec::new(); let mut to_remove: Vec<&str> = Vec::new();
#prefixes #prefixes
#attributes #attributes
serializer.consume_current_attributes()?; serializer.end_start()?;
serializer.output.write_char('>')?;
#body #body
// Close tag // Close tag
serializer.output.write_str("</")?; serializer.write_close(None, #root_name)?;
serializer.output.write_str(#root_name)?;
serializer.output.write_char('>')?;
serializer.retrieve_parent_default_namespace(); serializer.retrieve_parent_default_namespace();
// Removing current namespaces // Removing current namespaces
@ -102,6 +76,11 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
Ok(()) Ok(())
} }
const KIND: ::instant_xml::Kind = ::instant_xml::Kind::Element(::instant_xml::Id {
ns: #default_namespace,
name: #root_name,
});
}; };
) )
} }
@ -115,22 +94,10 @@ fn process_named_field(
let 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 declaration = quote!(
let mut field = FieldContext {
name: #name,
attribute: None,
};
);
let field_meta = FieldMeta::from_field(field); let field_meta = FieldMeta::from_field(field);
if field_meta.attribute { if field_meta.attribute {
attributes.extend(quote!( attributes.extend(quote!(
#declaration serializer.write_attr(#name, &self.#field_value)?;
serializer.add_attribute_key(&#name)?;
field.attribute = Some(FieldAttribute::Attribute);
serializer.set_field_context(field)?;
self.#field_value.serialize(serializer)?;
)); ));
return; return;
} }
@ -143,13 +110,27 @@ fn process_named_field(
}, },
}; };
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
body.extend(quote!( body.extend(quote!(
#declaration match <#no_lifetime_type as ToXml>::KIND {
match serializer.parent_namespaces.get(#ns) { ::instant_xml::Kind::Element(_) => {
Some(prefix) => field.attribute = Some(FieldAttribute::Prefix(prefix)),
None => field.attribute = Some(FieldAttribute::Namespace(#ns)),
}
serializer.set_field_context(field)?;
self.#field_value.serialize(serializer)?; self.#field_value.serialize(serializer)?;
}
::instant_xml::Kind::Scalar => {
let (prefix, ns) = match serializer.parent_default_namespace() == #ns {
true => (None, None),
false => match serializer.parent_namespaces.get(#ns) {
Some(&prefix) => (Some(prefix), None),
None => (None, Some(#ns)),
},
};
serializer.write_start(prefix, #name, ns)?;
serializer.end_start()?;
self.#field_value.serialize(serializer)?;
serializer.write_close(prefix, #name)?;
}
}
)); ));
} }

View File

@ -1,6 +1,6 @@
use std::collections::{BTreeMap, VecDeque}; use std::collections::{BTreeMap, VecDeque};
use super::Error; use super::{Error, Id};
use xmlparser::{ElementEnd, Token, Tokenizer}; use xmlparser::{ElementEnd, Token, Tokenizer};
pub struct Deserializer<'cx, 'xml> { pub struct Deserializer<'cx, 'xml> {
@ -321,23 +321,3 @@ pub struct Attribute<'xml> {
pub local: &'xml str, pub local: &'xml str,
pub value: &'xml str, pub value: &'xml str,
} }
pub enum Kind {
Scalar,
Element(Id<'static>),
}
impl Kind {
pub const fn name(&self, field: Id<'static>) -> Id<'static> {
match self {
Kind::Scalar => field,
Kind::Element(name) => *name,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Id<'a> {
pub ns: &'a str,
pub name: &'a str,
}

View File

@ -2,9 +2,7 @@ use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use crate::de::Kind; use crate::{Deserializer, Error, FromXml, Kind, Serializer, ToXml};
use crate::ser::FieldAttribute;
use crate::{Deserializer, Error, FromXml, Serializer, ToXml};
// Deserializer // Deserializer
struct FromXmlStr<T: FromStr>(Option<T>); struct FromXmlStr<T: FromStr>(Option<T>);
@ -42,23 +40,10 @@ where
&self, &self,
serializer: &mut Serializer<W>, serializer: &mut Serializer<W>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let field_context = match serializer.consume_field_context() { serializer.write_str(self.0)
Some(field_context) => field_context, }
None => return Err(Error::UnexpectedValue),
};
match field_context.attribute { const KIND: Kind = Kind::Scalar;
Some(FieldAttribute::Attribute) => {
serializer.add_attribute_value(&self.0)?;
}
_ => {
serializer.add_open_tag(&field_context)?;
write!(serializer.output, "{}", self.0)?;
serializer.add_close_tag(field_context)?;
}
}
Ok(())
}
} }
macro_rules! to_xml_for_number { macro_rules! to_xml_for_number {
@ -70,6 +55,8 @@ macro_rules! to_xml_for_number {
) -> Result<(), Error> { ) -> Result<(), Error> {
DisplayToXml(self).serialize(serializer) DisplayToXml(self).serialize(serializer)
} }
const KIND: Kind = DisplayToXml::<Self>::KIND;
} }
}; };
} }
@ -186,6 +173,8 @@ impl ToXml for bool {
DisplayToXml(&value).serialize(serializer) DisplayToXml(&value).serialize(serializer)
} }
const KIND: Kind = DisplayToXml::<Self>::KIND;
} }
impl ToXml for String { impl ToXml for String {
@ -195,6 +184,8 @@ impl ToXml for String {
) -> Result<(), Error> { ) -> Result<(), Error> {
DisplayToXml(&encode(self)?).serialize(serializer) DisplayToXml(&encode(self)?).serialize(serializer)
} }
const KIND: Kind = DisplayToXml::<Self>::KIND;
} }
impl ToXml for char { impl ToXml for char {
@ -205,6 +196,8 @@ impl ToXml for char {
let mut tmp = [0u8; 4]; let mut tmp = [0u8; 4];
DisplayToXml(&encode(&*self.encode_utf8(&mut tmp))?).serialize(serializer) DisplayToXml(&encode(&*self.encode_utf8(&mut tmp))?).serialize(serializer)
} }
const KIND: Kind = DisplayToXml::<Self>::KIND;
} }
impl ToXml for &str { impl ToXml for &str {
@ -214,6 +207,8 @@ impl ToXml for &str {
) -> Result<(), Error> { ) -> Result<(), Error> {
DisplayToXml(&encode(self)?).serialize(serializer) DisplayToXml(&encode(self)?).serialize(serializer)
} }
const KIND: Kind = DisplayToXml::<Self>::KIND;
} }
impl ToXml for Cow<'_, str> { impl ToXml for Cow<'_, str> {
@ -223,6 +218,8 @@ impl ToXml for Cow<'_, str> {
) -> Result<(), Error> { ) -> Result<(), Error> {
DisplayToXml(&encode(self)?).serialize(serializer) DisplayToXml(&encode(self)?).serialize(serializer)
} }
const KIND: Kind = DisplayToXml::<Self>::KIND;
} }
impl<T: ToXml> ToXml for Option<T> { impl<T: ToXml> ToXml for Option<T> {
@ -235,6 +232,8 @@ impl<T: ToXml> ToXml for Option<T> {
None => Ok(()), None => Ok(()),
} }
} }
const KIND: Kind = T::KIND;
} }
fn encode(input: &str) -> Result<Cow<'_, str>, Error> { fn encode(input: &str) -> Result<Cow<'_, str>, Error> {

View File

@ -7,8 +7,8 @@ pub use macros::{FromXml, ToXml};
#[doc(hidden)] #[doc(hidden)]
pub mod de; pub mod de;
mod impls; mod impls;
use de::Context;
pub use de::Deserializer; pub use de::Deserializer;
use de::{Context, Kind};
#[doc(hidden)] #[doc(hidden)]
pub mod ser; pub mod ser;
pub use ser::Serializer; pub use ser::Serializer;
@ -18,6 +18,8 @@ pub trait ToXml {
&self, &self,
serializer: &mut Serializer<W>, serializer: &mut Serializer<W>,
) -> Result<(), Error>; ) -> Result<(), Error>;
const KIND: Kind;
} }
pub trait FromXml<'xml>: Sized { pub trait FromXml<'xml>: Sized {
@ -93,3 +95,23 @@ pub enum Error {
#[error("duplicate value")] #[error("duplicate value")]
DuplicateValue, DuplicateValue,
} }
pub enum Kind {
Scalar,
Element(Id<'static>),
}
impl Kind {
pub const fn name(&self, field: Id<'static>) -> Id<'static> {
match self {
Kind::Scalar => field,
Kind::Element(name) => *name,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Id<'a> {
pub ns: &'a str,
pub name: &'a str,
}

View File

@ -1,20 +1,18 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Write}; use std::fmt::{self};
use super::Error; use super::Error;
use crate::ToXml;
pub struct Serializer<'xml, W: fmt::Write + ?Sized> { pub struct Serializer<'xml, W: fmt::Write + ?Sized> {
// For parent namespaces the key is the namespace and the value is the prefix. We are adding to map // For parent namespaces the key is the namespace and the value is the prefix. We are adding to map
// only if the namespaces do not exist, if it does exist then we are using an already defined parent prefix. // only if the namespaces do not exist, if it does exist then we are using an already defined parent prefix.
#[doc(hidden)] #[doc(hidden)]
pub parent_namespaces: HashMap<&'xml str, &'xml str>, pub parent_namespaces: HashMap<&'xml str, &'xml str>,
#[doc(hidden)] output: &'xml mut W,
pub output: &'xml mut W,
parent_default_namespace: &'xml str, parent_default_namespace: &'xml str,
parent_default_namespace_to_revert: &'xml str, parent_default_namespace_to_revert: &'xml str,
current_attributes: String, state: State,
next_field_context: Option<FieldContext<'xml>>,
} }
impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> { impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
@ -24,42 +22,88 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
output, output,
parent_default_namespace: "", parent_default_namespace: "",
parent_default_namespace_to_revert: "", parent_default_namespace_to_revert: "",
next_field_context: None, state: State::Element,
current_attributes: String::new(),
} }
} }
pub fn consume_current_attributes(&mut self) -> Result<(), Error> { pub fn write_start(
self.output.write_str(&self.current_attributes)?; &mut self,
self.current_attributes.clear(); prefix: Option<&str>,
Ok(()) name: &str,
} ns: Option<&str>,
) -> Result<(), Error> {
pub fn add_attribute_key(&mut self, attr_key: &impl fmt::Display) -> Result<(), Error> { if self.state != State::Element {
self.current_attributes.push(' ');
write!(self.current_attributes, "{}", attr_key)?;
self.current_attributes.push('=');
Ok(())
}
pub fn add_attribute_value(&mut self, attr_value: &impl fmt::Display) -> Result<(), Error> {
self.current_attributes.push('"');
write!(self.current_attributes, "{}", attr_value)?;
self.current_attributes.push('"');
Ok(())
}
pub fn set_field_context(&mut self, field_context: FieldContext<'xml>) -> Result<(), Error> {
if self.next_field_context.is_some() {
return Err(Error::UnexpectedState); return Err(Error::UnexpectedState);
}; }
self.next_field_context = Some(field_context); match prefix {
Some(prefix) => self.output.write_fmt(format_args!("<{prefix}:{name}"))?,
None => match ns {
Some(ns) => self
.output
.write_fmt(format_args!("<{name} xmlns=\"{ns}\""))?,
None => self.output.write_fmt(format_args!("<{name}"))?,
},
}
self.state = State::Attribute;
Ok(()) Ok(())
} }
pub fn consume_field_context(&mut self) -> Option<FieldContext<'xml>> { pub fn write_attr<V: ToXml + ?Sized>(&mut self, name: &str, value: &V) -> Result<(), Error> {
self.next_field_context.take() if self.state != State::Attribute {
return Err(Error::UnexpectedState);
}
self.output.write_fmt(format_args!(" {}=\"", name))?;
self.state = State::Scalar;
value.serialize(self)?;
self.state = State::Attribute;
self.output.write_char('"')?;
Ok(())
}
pub fn write_prefix(&mut self, prefix: &str, ns: &str) -> Result<(), Error> {
if self.state != State::Attribute {
return Err(Error::UnexpectedState);
}
self.output
.write_fmt(format_args!(" xmlns:{prefix}=\"{ns}\""))?;
Ok(())
}
pub fn write_str<V: fmt::Display + ?Sized>(&mut self, value: &V) -> Result<(), Error> {
if !matches!(self.state, State::Element | State::Scalar) {
return Err(Error::UnexpectedState);
}
self.output.write_fmt(format_args!("{}", value))?;
self.state = State::Element;
Ok(())
}
pub fn end_start(&mut self) -> Result<(), Error> {
if self.state != State::Attribute {
return Err(Error::UnexpectedState);
}
self.output.write_char('>')?;
self.state = State::Element;
Ok(())
}
pub fn write_close(&mut self, prefix: Option<&str>, name: &str) -> Result<(), Error> {
if self.state != State::Element {
return Err(Error::UnexpectedState);
}
match prefix {
Some(prefix) => self.output.write_fmt(format_args!("</{prefix}:{name}>"))?,
None => self.output.write_fmt(format_args!("</{name}>"))?,
}
Ok(())
} }
pub fn set_parent_default_namespace(&mut self, namespace: &'xml str) -> Result<(), Error> { pub fn set_parent_default_namespace(&mut self, namespace: &'xml str) -> Result<(), Error> {
@ -79,62 +123,11 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
pub fn retrieve_parent_default_namespace(&mut self) { pub fn retrieve_parent_default_namespace(&mut self) {
self.parent_default_namespace = self.parent_default_namespace_to_revert; self.parent_default_namespace = self.parent_default_namespace_to_revert;
} }
pub 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))
if self.parent_default_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(())
}
pub 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 struct FieldContext<'xml> { #[derive(Debug, Eq, PartialEq)]
#[doc(hidden)] enum State {
pub name: &'xml str,
#[doc(hidden)]
pub attribute: Option<FieldAttribute<'xml>>,
}
pub enum FieldAttribute<'xml> {
Prefix(&'xml str),
Namespace(&'xml str),
Attribute, Attribute,
Element,
Scalar,
} }