From 6238ed87c5cc4bae3a735dcd399786ad18e03593 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Wed, 7 Sep 2022 11:14:41 +0200 Subject: [PATCH] Turn serializer API into a proper abstraction --- instant-xml-macros/src/de.rs | 47 +--------- instant-xml-macros/src/lib.rs | 39 +++++++- instant-xml-macros/src/ser.rs | 85 +++++++---------- instant-xml/src/de.rs | 22 +---- instant-xml/src/impls.rs | 37 ++++---- instant-xml/src/lib.rs | 24 ++++- instant-xml/src/ser.rs | 171 ++++++++++++++++------------------ 7 files changed, 200 insertions(+), 225 deletions(-) diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index a2aa59b..88ba950 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -1,7 +1,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use crate::{ContainerMeta, FieldMeta, Namespace}; +use super::{discard_lifetimes, ContainerMeta, FieldMeta, Namespace}; pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream { let ident = &input.ident; @@ -80,8 +80,8 @@ pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream { quote!( impl #xml_impl_generics FromXml<'xml> for #ident #ty_generics #where_clause { fn deserialize<'cx>(deserializer: &'cx mut ::instant_xml::Deserializer<'cx, 'xml>) -> Result { - use ::instant_xml::de::{Deserializer, Id, Node}; - use ::instant_xml::Error; + use ::instant_xml::de::{Deserializer, Node}; + use ::instant_xml::{Error, Id}; use ::core::marker::PhantomData; enum __Elements { @@ -142,7 +142,7 @@ pub(crate) fn from_xml(input: &syn::DeriveInput) -> TokenStream { 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, name: #name, }); @@ -181,7 +181,7 @@ fn process_field( }; 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 } ); )); @@ -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) - } - } - } -} diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 7585b87..77598d9 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -5,7 +5,7 @@ mod ser; 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 syn::parse_macro_input; 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) + } + } + } +} diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index 0ee11f3..16abc79 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}; +use super::{discard_lifetimes, ContainerMeta, FieldMeta}; pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { 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 { prefixes.extend(quote!( if serializer.parent_namespaces.get(#val).is_none() { - serializer.output.write_str(" xmlns:")?; - serializer.output.write_str(#key)?; - serializer.output.write_str("=\"")?; - serializer.output.write_str(#val)?; - serializer.output.write_char('\"')?; + serializer.write_prefix(#key, #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, serializer: &mut instant_xml::Serializer, ) -> 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 - serializer.output.write_char('<')?; - if serializer.parent_default_namespace() != #default_namespace { - 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)?; + match serializer.parent_default_namespace() == #default_namespace { + true => serializer.write_start(None, #root_name, None)?, + false => serializer.write_start(None, #root_name, Some(#default_namespace))?, } serializer.update_parent_default_namespace(#default_namespace); let mut to_remove: Vec<&str> = Vec::new(); #prefixes #attributes - serializer.consume_current_attributes()?; - serializer.output.write_char('>')?; + serializer.end_start()?; #body // Close tag - serializer.output.write_str("')?; + serializer.write_close(None, #root_name)?; serializer.retrieve_parent_default_namespace(); // Removing current namespaces @@ -102,6 +76,11 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { 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 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); if field_meta.attribute { attributes.extend(quote!( - #declaration - - serializer.add_attribute_key(&#name)?; - field.attribute = Some(FieldAttribute::Attribute); - serializer.set_field_context(field)?; - self.#field_value.serialize(serializer)?; + serializer.write_attr(#name, &self.#field_value)?; )); 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!( - #declaration - match serializer.parent_namespaces.get(#ns) { - Some(prefix) => field.attribute = Some(FieldAttribute::Prefix(prefix)), - None => field.attribute = Some(FieldAttribute::Namespace(#ns)), + match <#no_lifetime_type as ToXml>::KIND { + ::instant_xml::Kind::Element(_) => { + 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)?; + } } - serializer.set_field_context(field)?; - self.#field_value.serialize(serializer)?; )); } diff --git a/instant-xml/src/de.rs b/instant-xml/src/de.rs index e22c2c0..b932979 100644 --- a/instant-xml/src/de.rs +++ b/instant-xml/src/de.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, VecDeque}; -use super::Error; +use super::{Error, Id}; use xmlparser::{ElementEnd, Token, Tokenizer}; pub struct Deserializer<'cx, 'xml> { @@ -321,23 +321,3 @@ pub struct Attribute<'xml> { pub local: &'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, -} diff --git a/instant-xml/src/impls.rs b/instant-xml/src/impls.rs index 883b02b..03e5149 100644 --- a/instant-xml/src/impls.rs +++ b/instant-xml/src/impls.rs @@ -2,9 +2,7 @@ use std::borrow::Cow; use std::fmt; use std::str::FromStr; -use crate::de::Kind; -use crate::ser::FieldAttribute; -use crate::{Deserializer, Error, FromXml, Serializer, ToXml}; +use crate::{Deserializer, Error, FromXml, Kind, Serializer, ToXml}; // Deserializer struct FromXmlStr(Option); @@ -42,23 +40,10 @@ where &self, serializer: &mut Serializer, ) -> Result<(), Error> { - let field_context = match serializer.consume_field_context() { - Some(field_context) => field_context, - None => return Err(Error::UnexpectedValue), - }; - - match field_context.attribute { - 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(()) + serializer.write_str(self.0) } + + const KIND: Kind = Kind::Scalar; } macro_rules! to_xml_for_number { @@ -70,6 +55,8 @@ macro_rules! to_xml_for_number { ) -> Result<(), Error> { DisplayToXml(self).serialize(serializer) } + + const KIND: Kind = DisplayToXml::::KIND; } }; } @@ -186,6 +173,8 @@ impl ToXml for bool { DisplayToXml(&value).serialize(serializer) } + + const KIND: Kind = DisplayToXml::::KIND; } impl ToXml for String { @@ -195,6 +184,8 @@ impl ToXml for String { ) -> Result<(), Error> { DisplayToXml(&encode(self)?).serialize(serializer) } + + const KIND: Kind = DisplayToXml::::KIND; } impl ToXml for char { @@ -205,6 +196,8 @@ impl ToXml for char { let mut tmp = [0u8; 4]; DisplayToXml(&encode(&*self.encode_utf8(&mut tmp))?).serialize(serializer) } + + const KIND: Kind = DisplayToXml::::KIND; } impl ToXml for &str { @@ -214,6 +207,8 @@ impl ToXml for &str { ) -> Result<(), Error> { DisplayToXml(&encode(self)?).serialize(serializer) } + + const KIND: Kind = DisplayToXml::::KIND; } impl ToXml for Cow<'_, str> { @@ -223,6 +218,8 @@ impl ToXml for Cow<'_, str> { ) -> Result<(), Error> { DisplayToXml(&encode(self)?).serialize(serializer) } + + const KIND: Kind = DisplayToXml::::KIND; } impl ToXml for Option { @@ -235,6 +232,8 @@ impl ToXml for Option { None => Ok(()), } } + + const KIND: Kind = T::KIND; } fn encode(input: &str) -> Result, Error> { diff --git a/instant-xml/src/lib.rs b/instant-xml/src/lib.rs index 9738c36..529c906 100644 --- a/instant-xml/src/lib.rs +++ b/instant-xml/src/lib.rs @@ -7,8 +7,8 @@ pub use macros::{FromXml, ToXml}; #[doc(hidden)] pub mod de; mod impls; +use de::Context; pub use de::Deserializer; -use de::{Context, Kind}; #[doc(hidden)] pub mod ser; pub use ser::Serializer; @@ -18,6 +18,8 @@ pub trait ToXml { &self, serializer: &mut Serializer, ) -> Result<(), Error>; + + const KIND: Kind; } pub trait FromXml<'xml>: Sized { @@ -93,3 +95,23 @@ pub enum Error { #[error("duplicate value")] 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, +} diff --git a/instant-xml/src/ser.rs b/instant-xml/src/ser.rs index 0a87755..2f23e57 100644 --- a/instant-xml/src/ser.rs +++ b/instant-xml/src/ser.rs @@ -1,20 +1,18 @@ use std::collections::HashMap; -use std::fmt::{self, Write}; +use std::fmt::{self}; use super::Error; +use crate::ToXml; 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 // only if the namespaces do not exist, if it does exist then we are using an already defined parent prefix. #[doc(hidden)] pub parent_namespaces: HashMap<&'xml str, &'xml str>, - #[doc(hidden)] - pub output: &'xml mut W, - + output: &'xml mut W, parent_default_namespace: &'xml str, parent_default_namespace_to_revert: &'xml str, - current_attributes: String, - next_field_context: Option>, + state: State, } impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> { @@ -24,42 +22,88 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> { output, parent_default_namespace: "", parent_default_namespace_to_revert: "", - next_field_context: None, - current_attributes: String::new(), + state: State::Element, } } - pub fn consume_current_attributes(&mut self) -> Result<(), Error> { - self.output.write_str(&self.current_attributes)?; - self.current_attributes.clear(); - Ok(()) - } - - pub fn add_attribute_key(&mut self, attr_key: &impl fmt::Display) -> Result<(), Error> { - 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() { + pub fn write_start( + &mut self, + prefix: Option<&str>, + name: &str, + ns: Option<&str>, + ) -> Result<(), Error> { + if self.state != State::Element { 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(()) } - pub fn consume_field_context(&mut self) -> Option> { - self.next_field_context.take() + pub fn write_attr(&mut self, name: &str, value: &V) -> Result<(), Error> { + 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(&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!(""))?, + None => self.output.write_fmt(format_args!(""))?, + } + + Ok(()) } 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) { 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("')?; - } - } - Ok(()) - } } -pub struct FieldContext<'xml> { - #[doc(hidden)] - pub name: &'xml str, - #[doc(hidden)] - pub attribute: Option>, -} - -pub enum FieldAttribute<'xml> { - Prefix(&'xml str), - Namespace(&'xml str), +#[derive(Debug, Eq, PartialEq)] +enum State { Attribute, + Element, + Scalar, }