Move user-facing serialization interface out of trait

This commit is contained in:
Dirkjan Ochtman 2022-09-05 13:10:27 +02:00
parent 2827bd404e
commit 9577aace57
9 changed files with 67 additions and 42 deletions

View File

@ -49,7 +49,10 @@ pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
proc_macro::TokenStream::from(quote!( proc_macro::TokenStream::from(quote!(
impl #generics ToXml for #ident #generics { impl #generics ToXml for #ident #generics {
fn serialize<W: std::fmt::Write>(&self, serializer: &mut instant_xml::Serializer<W>) -> Result<(), instant_xml::Error> { fn serialize<W: ::core::fmt::Write + ?::core::marker::Sized>(
&self,
serializer: &mut instant_xml::Serializer<W>,
) -> Result<(), instant_xml::Error> {
let _ = serializer.consume_field_context(); let _ = serializer.consume_field_context();
let mut field_context = instant_xml::ser::FieldContext { let mut field_context = instant_xml::ser::FieldContext {
name: #root_name, name: #root_name,

View File

@ -54,10 +54,10 @@ impl<'a, T> ToXml for DisplayToXml<'a, T>
where where
T: fmt::Display, T: fmt::Display,
{ {
fn serialize<W>(&self, serializer: &mut Serializer<W>) -> Result<(), Error> fn serialize<W: fmt::Write + ?Sized>(
where &self,
W: fmt::Write, serializer: &mut Serializer<W>,
{ ) -> Result<(), Error> {
let field_context = match serializer.consume_field_context() { let field_context = match serializer.consume_field_context() {
Some(field_context) => field_context, Some(field_context) => field_context,
None => return Err(Error::UnexpectedValue), None => return Err(Error::UnexpectedValue),
@ -80,7 +80,7 @@ where
macro_rules! to_xml_for_number { macro_rules! to_xml_for_number {
($typ:ty) => { ($typ:ty) => {
impl ToXml for $typ { impl ToXml for $typ {
fn serialize<W: fmt::Write>( fn serialize<W: fmt::Write + ?Sized>(
&self, &self,
serializer: &mut Serializer<W>, serializer: &mut Serializer<W>,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -321,7 +321,10 @@ to_xml_for_number!(f32);
to_xml_for_number!(f64); to_xml_for_number!(f64);
impl ToXml for bool { impl ToXml for bool {
fn serialize<W: fmt::Write>(&self, serializer: &mut Serializer<W>) -> Result<(), Error> { fn serialize<W: fmt::Write + ?Sized>(
&self,
serializer: &mut Serializer<W>,
) -> Result<(), Error> {
let value = match self { let value = match self {
true => "true", true => "true",
false => "false", false => "false",
@ -332,32 +335,47 @@ impl ToXml for bool {
} }
impl ToXml for String { impl ToXml for String {
fn serialize<W: fmt::Write>(&self, serializer: &mut Serializer<W>) -> Result<(), Error> { fn serialize<W: fmt::Write + ?Sized>(
&self,
serializer: &mut Serializer<W>,
) -> Result<(), Error> {
DisplayToXml(&escape(self)?).serialize(serializer) DisplayToXml(&escape(self)?).serialize(serializer)
} }
} }
impl ToXml for char { impl ToXml for char {
fn serialize<W: fmt::Write>(&self, serializer: &mut Serializer<W>) -> Result<(), Error> { fn serialize<W: fmt::Write + ?Sized>(
&self,
serializer: &mut Serializer<W>,
) -> Result<(), Error> {
let mut tmp = [0u8; 4]; let mut tmp = [0u8; 4];
DisplayToXml(&escape(&*self.encode_utf8(&mut tmp))?).serialize(serializer) DisplayToXml(&escape(&*self.encode_utf8(&mut tmp))?).serialize(serializer)
} }
} }
impl ToXml for &str { impl ToXml for &str {
fn serialize<W: fmt::Write>(&self, serializer: &mut Serializer<W>) -> Result<(), Error> { fn serialize<W: fmt::Write + ?Sized>(
&self,
serializer: &mut Serializer<W>,
) -> Result<(), Error> {
DisplayToXml(&escape(self)?).serialize(serializer) DisplayToXml(&escape(self)?).serialize(serializer)
} }
} }
impl ToXml for Cow<'_, str> { impl ToXml for Cow<'_, str> {
fn serialize<W: fmt::Write>(&self, serializer: &mut Serializer<W>) -> Result<(), Error> { fn serialize<W: fmt::Write + ?Sized>(
&self,
serializer: &mut Serializer<W>,
) -> Result<(), Error> {
DisplayToXml(&escape(self)?).serialize(serializer) DisplayToXml(&escape(self)?).serialize(serializer)
} }
} }
impl<T: ToXml> ToXml for Option<T> { impl<T: ToXml> ToXml for Option<T> {
fn serialize<W: fmt::Write>(&self, serializer: &mut Serializer<W>) -> Result<(), Error> { fn serialize<W: fmt::Write + ?Sized>(
&self,
serializer: &mut Serializer<W>,
) -> Result<(), Error> {
match self { match self {
Some(v) => v.serialize(serializer), Some(v) => v.serialize(serializer),
None => Ok(()), None => Ok(()),

View File

@ -13,14 +13,10 @@ pub mod ser;
pub use ser::Serializer; pub use ser::Serializer;
pub trait ToXml { pub trait ToXml {
fn to_xml(&self) -> Result<String, Error> { fn serialize<W: fmt::Write + ?Sized>(
let mut output = String::new(); &self,
let mut serializer = Serializer::new(&mut output); serializer: &mut Serializer<W>,
self.serialize(&mut serializer)?; ) -> Result<(), Error>;
Ok(output)
}
fn serialize<W: fmt::Write>(&self, serializer: &mut Serializer<W>) -> Result<(), Error>;
} }
pub enum FieldAttribute<'xml> { pub enum FieldAttribute<'xml> {
@ -45,6 +41,19 @@ pub fn from_str<'xml, T: FromXml<'xml>>(input: &'xml str) -> Result<T, Error> {
T::deserialize(&mut Deserializer::new(input)) T::deserialize(&mut Deserializer::new(input))
} }
pub fn to_string(value: &(impl ToXml + ?Sized)) -> Result<String, Error> {
let mut output = String::new();
to_writer(value, &mut output)?;
Ok(output)
}
pub fn to_writer(
value: &(impl ToXml + ?Sized),
output: &mut (impl fmt::Write + ?Sized),
) -> Result<(), Error> {
value.serialize(&mut Serializer::new(output))
}
pub enum Kind { pub enum Kind {
Scalar, Scalar,
Element(Id<'static>), Element(Id<'static>),

View File

@ -3,7 +3,7 @@ use std::fmt::{self, Write};
use super::{Error, FieldAttribute}; use super::{Error, FieldAttribute};
pub struct Serializer<'xml, W: fmt::Write> { 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)]
@ -17,7 +17,7 @@ pub struct Serializer<'xml, W: fmt::Write> {
next_field_context: Option<FieldContext<'xml>>, next_field_context: Option<FieldContext<'xml>>,
} }
impl<'xml, W: fmt::Write> Serializer<'xml, W> { impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
pub fn new(output: &'xml mut W) -> Self { pub fn new(output: &'xml mut W) -> Self {
Self { Self {
parent_namespaces: HashMap::new(), parent_namespaces: HashMap::new(),

View File

@ -2,7 +2,7 @@ use std::borrow::Cow;
use similar_asserts::assert_eq; use similar_asserts::assert_eq;
use instant_xml::{from_str, Error, FromXml, ToXml}; use instant_xml::{from_str, to_string, Error, FromXml, ToXml};
#[derive(Debug, PartialEq, Eq, FromXml, ToXml)] #[derive(Debug, PartialEq, Eq, FromXml, ToXml)]
#[xml(ns("URI"))] #[xml(ns("URI"))]
@ -57,13 +57,11 @@ fn escape_back() {
#[test] #[test]
fn special_entities() { fn special_entities() {
assert_eq!( assert_eq!(
StructSpecialEntities{ to_string(&StructSpecialEntities{
string: "&\"<>\'aa".to_string(), string: "&\"<>\'aa".to_string(),
str: "&\"<>\'bb", str: "&\"<>\'bb",
cow: Cow::from("&\"<>\'cc"), cow: Cow::from("&\"<>\'cc"),
} }).unwrap(),
.to_xml() "<StructSpecialEntities xmlns=\"URI\"><string>&amp;&quot;&lt;&gt;&apos;aa</string><str>&amp;&quot;&lt;&gt;&apos;bb</str><cow>&amp;&quot;&lt;&gt;&apos;cc</cow></StructSpecialEntities>",
.unwrap(),
"<StructSpecialEntities xmlns=\"URI\"><string>&amp;&quot;&lt;&gt;&apos;aa</string><str>&amp;&quot;&lt;&gt;&apos;bb</str><cow>&amp;&quot;&lt;&gt;&apos;cc</cow></StructSpecialEntities>"
); );
} }

View File

@ -1,6 +1,6 @@
use similar_asserts::assert_eq; use similar_asserts::assert_eq;
use instant_xml::ToXml; use instant_xml::{to_string, ToXml};
#[derive(Debug, Eq, PartialEq, ToXml)] #[derive(Debug, Eq, PartialEq, ToXml)]
#[xml(ns(dar = "BAZ", internal = "INTERNAL"))] #[xml(ns(dar = "BAZ", internal = "INTERNAL"))]
@ -33,7 +33,7 @@ struct Nested {
#[test] #[test]
fn struct_child_namespaces() { fn struct_child_namespaces() {
assert_eq!( assert_eq!(
StructChildNamespaces { to_string(&StructChildNamespaces {
different_child_namespace: NestedDifferentNamespace { different_child_namespace: NestedDifferentNamespace {
flag_parent_prefix: true, flag_parent_prefix: true,
flag_internal_prefix: false, flag_internal_prefix: false,
@ -42,8 +42,7 @@ fn struct_child_namespaces() {
flag_parent_prefix: true, flag_parent_prefix: true,
flag_internal_prefix: false, flag_internal_prefix: false,
}, },
} })
.to_xml()
.unwrap(), .unwrap(),
"<StructChildNamespaces xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><NestedDifferentNamespace xmlns=\"\" xmlns:internal=\"INTERNAL\"><bar:flag_parent_prefix>true</bar:flag_parent_prefix><internal:flag_internal_prefix>false</internal:flag_internal_prefix></NestedDifferentNamespace><Nested xmlns:internal=\"INTERNAL\"><bar:flag_parent_prefix>true</bar:flag_parent_prefix><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructChildNamespaces>" "<StructChildNamespaces xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><NestedDifferentNamespace xmlns=\"\" xmlns:internal=\"INTERNAL\"><bar:flag_parent_prefix>true</bar:flag_parent_prefix><internal:flag_internal_prefix>false</internal:flag_internal_prefix></NestedDifferentNamespace><Nested xmlns:internal=\"INTERNAL\"><bar:flag_parent_prefix>true</bar:flag_parent_prefix><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructChildNamespaces>"
); );

View File

@ -1,6 +1,6 @@
use similar_asserts::assert_eq; use similar_asserts::assert_eq;
use instant_xml::ToXml; use instant_xml::{to_string, ToXml};
#[derive(Debug, Eq, PartialEq, ToXml)] #[derive(Debug, Eq, PartialEq, ToXml)]
#[xml(ns(bar = "BAZ", foo = "BAR"))] #[xml(ns(bar = "BAZ", foo = "BAR"))]
@ -20,12 +20,11 @@ struct StructWithNamedFields {
#[test] #[test]
fn struct_with_named_fields() { fn struct_with_named_fields() {
assert_eq!( assert_eq!(
StructWithNamedFields { to_string(&StructWithNamedFields {
flag: true, flag: true,
string: "test".to_string(), string: "test".to_string(),
number: 1, number: 1,
} })
.to_xml()
.unwrap(), .unwrap(),
"<StructWithNamedFields xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><flag>true</flag><bar:string>test</bar:string><number xmlns=\"typo\">1</number></StructWithNamedFields>" "<StructWithNamedFields xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><flag>true</flag><bar:string>test</bar:string><number xmlns=\"typo\">1</number></StructWithNamedFields>"
); );

View File

@ -1,6 +1,6 @@
use similar_asserts::assert_eq; use similar_asserts::assert_eq;
use instant_xml::ToXml; use instant_xml::{to_string, ToXml};
#[derive(Debug, Eq, PartialEq, ToXml)] #[derive(Debug, Eq, PartialEq, ToXml)]
#[xml(ns("URI", dar = "BAZ", internal = "INTERNAL"))] #[xml(ns("URI", dar = "BAZ", internal = "INTERNAL"))]
@ -37,7 +37,7 @@ struct StructWithCustomField {
#[test] #[test]
fn struct_with_custom_field() { fn struct_with_custom_field() {
assert_eq!( assert_eq!(
StructWithCustomField { to_string(&StructWithCustomField {
int_attribute: 42, int_attribute: 42,
flag_direct_namespace_same_the_same_as_prefix: true, flag_direct_namespace_same_the_same_as_prefix: true,
flag_prefix: false, flag_prefix: false,
@ -46,8 +46,7 @@ fn struct_with_custom_field() {
flag_parent_prefix: true, flag_parent_prefix: true,
flag_internal_prefix: false, flag_internal_prefix: false,
}, },
} })
.to_xml()
.unwrap(), .unwrap(),
"<StructWithCustomField xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\" int_attribute=\"42\"><flag_direct_namespace_same_the_same_as_prefix xmlns=\"BAZ\">true</flag_direct_namespace_same_the_same_as_prefix><bar:flag_prefix>false</bar:flag_prefix><flag_direct_namespace xmlns=\"DIFFERENT\">true</flag_direct_namespace><Nested xmlns:internal=\"INTERNAL\"><bar:flag_parent_prefix>true</bar:flag_parent_prefix><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructWithCustomField>" "<StructWithCustomField xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\" int_attribute=\"42\"><flag_direct_namespace_same_the_same_as_prefix xmlns=\"BAZ\">true</flag_direct_namespace_same_the_same_as_prefix><bar:flag_prefix>false</bar:flag_prefix><flag_direct_namespace xmlns=\"DIFFERENT\">true</flag_direct_namespace><Nested xmlns:internal=\"INTERNAL\"><bar:flag_parent_prefix>true</bar:flag_parent_prefix><internal:flag_internal_prefix>false</internal:flag_internal_prefix></Nested></StructWithCustomField>"
); );

View File

@ -1,12 +1,12 @@
use similar_asserts::assert_eq; use similar_asserts::assert_eq;
use instant_xml::ToXml; use instant_xml::{to_string, ToXml};
#[derive(Debug, Eq, PartialEq, ToXml)] #[derive(Debug, Eq, PartialEq, ToXml)]
struct Unit; struct Unit;
#[test] #[test]
fn unit() { fn unit() {
assert_eq!(Unit.to_xml().unwrap(), "<Unit></Unit>"); assert_eq!(to_string(&Unit).unwrap(), "<Unit></Unit>");
//assert_eq!(Unit::from_xml("<Unit/>").unwrap(), Unit); //assert_eq!(Unit::from_xml("<Unit/>").unwrap(), Unit);
} }