diff --git a/src/se/content.rs b/src/se/content.rs new file mode 100644 index 00000000..29266a44 --- /dev/null +++ b/src/se/content.rs @@ -0,0 +1,614 @@ +//! Contains serializer for content of an XML element + +use crate::errors::serialize::DeError; +use crate::se::element::{ElementSerializer, Struct}; +use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; +use crate::se::{QuoteLevel, XmlName}; +use serde::ser::{ + Impossible, Serialize, SerializeSeq, SerializeTuple, SerializeTupleStruct, Serializer, +}; +use serde::serde_if_integer128; +use std::fmt::Write; + +macro_rules! write_primitive { + ($method:ident ( $ty:ty )) => { + #[inline] + fn $method(self, value: $ty) -> Result { + self.into_simple_type_serializer().$method(value) + } + }; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer used to serialize content of the element. It does not write +/// surrounding tags. +/// +/// This serializer does the following: +/// - primitives (booleans, numbers and strings) serialized as naked strings +/// - `None` does not write anything +/// - sequences serialized without delimiters. `[1, 2, 3]` would be serialized as `123` +/// - units (`()`) and unit structs are not supported +/// - structs and maps are not supported +/// - unit variants serialized as self-closed `<${variant}/>` +/// - tuple variants serialized as sequences where each is wrapped in +/// `<${variant}>...` +/// - struct variants serialized wrapped `<${variant}>...` +/// +/// The difference between this serializer and [`SimpleTypeSerializer`] is in how +/// sequences and maps are serialized. Unlike `SimpleTypeSerializer` it supports +/// any types in sequences and serializes them as list of elements, but that has +/// drawbacks. Sequence of primitives would be serialized without delimiters and +/// it will be impossible to distinguish between them. +pub struct ContentSerializer { + pub writer: W, + /// Defines which XML characters need to be escaped in text content + pub level: QuoteLevel, + //TODO: add settings to disallow consequent serialization of primitives +} + +impl ContentSerializer { + /// Turns this serializer into serializer of a text content + #[inline] + pub fn into_simple_type_serializer(self) -> SimpleTypeSerializer { + //TODO: Customization point: choose between CDATA and Text representation + SimpleTypeSerializer { + writer: self.writer, + target: QuoteTarget::Text, + level: self.level, + } + } + + /// Creates new serializer that shares state with this serializer and + /// writes to the same underlying writer + #[inline] + pub fn new_seq_element_serializer(&mut self) -> ContentSerializer<&mut W> { + ContentSerializer { + writer: &mut self.writer, + level: self.level, + } + } + + /// Writes `name` as self-closed tag + #[inline] + pub(super) fn write_empty(mut self, name: XmlName) -> Result { + self.writer.write_char('<')?; + self.writer.write_str(name.0)?; + self.writer.write_str("/>")?; + Ok(self.writer) + } + + /// Writes simple type content between `name` tags + pub(super) fn write_wrapped(mut self, name: XmlName, serialize: S) -> Result + where + S: FnOnce(SimpleTypeSerializer) -> Result, + { + self.writer.write_char('<')?; + self.writer.write_str(name.0)?; + self.writer.write_char('>')?; + + let mut writer = serialize(self.into_simple_type_serializer())?; + + writer.write_str("')?; + Ok(writer) + } +} + +impl Serializer for ContentSerializer { + type Ok = W; + type Error = DeError; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = ElementSerializer<'static, W>; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Struct<'static, W>; + + write_primitive!(serialize_bool(bool)); + + write_primitive!(serialize_i8(i8)); + write_primitive!(serialize_i16(i16)); + write_primitive!(serialize_i32(i32)); + write_primitive!(serialize_i64(i64)); + + write_primitive!(serialize_u8(u8)); + write_primitive!(serialize_u16(u16)); + write_primitive!(serialize_u32(u32)); + write_primitive!(serialize_u64(u64)); + + serde_if_integer128! { + write_primitive!(serialize_i128(i128)); + write_primitive!(serialize_u128(u128)); + } + + write_primitive!(serialize_f32(f32)); + write_primitive!(serialize_f64(f64)); + + write_primitive!(serialize_char(char)); + write_primitive!(serialize_str(&str)); + write_primitive!(serialize_bytes(&[u8])); + + /// Does not write anything + #[inline] + fn serialize_none(self) -> Result { + Ok(self.writer) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + /// Does not write anything + #[inline] + fn serialize_unit(self) -> Result { + Ok(self.writer) + } + + /// Does not write anything + #[inline] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(self.writer) + } + + /// Checks `variant` for XML name validity and writes `<${variant}/>` + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + let name = XmlName::try_from(variant)?; + self.write_empty(name) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + /// Checks `variant` for XML name validity and writes `value` as new element + /// with name `variant`. + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + value.serialize(ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }) + } + + #[inline] + fn serialize_seq(self, _len: Option) -> Result { + Ok(self) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_tuple(len) + } + + #[inline] + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let ser = ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }; + // `ElementSerializer::serialize_tuple_variant` is the same as + // `ElementSerializer::serialize_tuple_struct`, except that it replaces `.key` + // to `variant` which is not required here + ser.serialize_tuple_struct(name, len) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + format!("serialization of map types is not supported in `$value` field").into(), + )) + } + + #[inline] + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("serialization of struct `{name}` is not supported in `$value` field").into(), + )) + } + + #[inline] + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let ser = ElementSerializer { + key: XmlName::try_from(variant)?, + ser: self, + }; + // `ElementSerializer::serialize_struct_variant` is the same as + // `ElementSerializer::serialize_struct`, except that it replaces `.key` + // to `variant` which is not required here + ser.serialize_struct(name, len) + } +} + +impl SerializeSeq for ContentSerializer { + type Ok = W; + type Error = DeError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + value.serialize(self.new_seq_element_serializer())?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.writer) + } +} + +impl SerializeTuple for ContentSerializer { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +impl SerializeTupleStruct for ContentSerializer { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Make tests public to reuse types in `elements::tests` module +#[cfg(test)] +pub(super) mod tests { + use super::*; + use crate::utils::Bytes; + use serde::Serialize; + use std::collections::BTreeMap; + + #[derive(Debug, Serialize, PartialEq)] + pub struct Unit; + + #[derive(Debug, Serialize, PartialEq)] + #[serde(rename = "<\"&'>")] + pub struct UnitEscaped; + + #[derive(Debug, Serialize, PartialEq)] + pub struct Newtype(pub usize); + + #[derive(Debug, Serialize, PartialEq)] + pub struct Tuple(pub &'static str, pub usize); + + #[derive(Debug, Serialize, PartialEq)] + pub struct Struct { + pub key: &'static str, + pub val: (usize, usize), + } + + #[derive(Debug, Serialize, PartialEq)] + pub struct Text { + pub before: &'static str, + #[serde(rename = "#text")] + pub content: T, + pub after: &'static str, + } + + #[derive(Debug, Serialize, PartialEq)] + pub struct Value { + pub before: &'static str, + #[serde(rename = "$value")] + pub content: T, + pub after: &'static str, + } + + /// Attributes identified by starting with `@` character + #[derive(Debug, Serialize, PartialEq)] + pub struct Attributes { + #[serde(rename = "@key")] + pub key: &'static str, + #[serde(rename = "@val")] + pub val: (usize, usize), + } + #[derive(Debug, Serialize, PartialEq)] + pub struct AttributesBefore { + #[serde(rename = "@key")] + pub key: &'static str, + pub val: usize, + } + #[derive(Debug, Serialize, PartialEq)] + pub struct AttributesAfter { + pub key: &'static str, + #[serde(rename = "@val")] + pub val: usize, + } + + #[derive(Debug, Serialize, PartialEq)] + pub enum Enum { + Unit, + /// Variant name becomes a tag name, but the name of variant is invalid + /// XML name. Serialization of this element should be forbidden + #[serde(rename = "<\"&'>")] + UnitEscaped, + Newtype(usize), + Tuple(&'static str, usize), + Struct { + key: &'static str, + /// Should be serialized as elements + val: (usize, usize), + }, + Attributes { + #[serde(rename = "@key")] + key: &'static str, + #[serde(rename = "@val")] + val: (usize, usize), + }, + AttributesBefore { + #[serde(rename = "@key")] + key: &'static str, + val: usize, + }, + AttributesAfter { + key: &'static str, + #[serde(rename = "@val")] + val: usize, + }, + } + + #[derive(Debug, Serialize, PartialEq)] + pub enum SpecialEnum { + Text { + before: &'static str, + #[serde(rename = "#text")] + content: T, + after: &'static str, + }, + Value { + before: &'static str, + #[serde(rename = "$value")] + content: T, + after: &'static str, + }, + } + + mod without_indent { + use super::Struct; + use super::*; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:literal) => { + #[test] + fn $name() { + let ser = ContentSerializer { + writer: String::new(), + level: QuoteLevel::Full, + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We could write something before fail + // assert_eq!(buffer, ""); + } + }; + } + + // Primitives is serialized in the same way as for SimpleTypeSerializer + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + serialize_as!(char_space: ' ' => " "); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + serialize_as!(option_some_empty_str: Some("") => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + // Unlike SimpleTypeSerializer, enumeration values serialized as tags + serialize_as!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: Enum::UnitEscaped + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + // Newtypes recursively applies ContentSerializer + serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + serialize_as!(seq: vec![1, 2, 3] => "123"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\ + 42"); + serialize_as!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // Structured types cannot be serialized without surrounding tag, which + // only `enum` can provide + err!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: Struct { key: "answer", val: (42, 42) } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + + /// Special field name `#text` should be serialized as a text content + mod text { + use super::*; + use pretty_assertions::assert_eq; + + err!(map: BTreeMap::from([("#text", 2), ("_3", 4)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + Text { + before: "answer", + content: (42, 42), + after: "answer", + } + => Unsupported("serialization of struct `Text` is not supported in `$value` field")); + serialize_as!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: (42, 42), + after: "answer", + } + => "\ + answer\ + 42 42\ + answer\ + "); + } + + mod attributes { + use super::*; + use pretty_assertions::assert_eq; + + err!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) + => Unsupported("serialization of map types is not supported in `$value` field")); + + err!(struct_: Attributes { key: "answer", val: (42, 42) } + => Unsupported("serialization of struct `Attributes` is not supported in `$value` field")); + err!(struct_before: AttributesBefore { key: "answer", val: 42 } + => Unsupported("serialization of struct `AttributesBefore` is not supported in `$value` field")); + err!(struct_after: AttributesAfter { key: "answer", val: 42 } + => Unsupported("serialization of struct `AttributesAfter` is not supported in `$value` field")); + + serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); + } + } +} diff --git a/src/se/element.rs b/src/se/element.rs new file mode 100644 index 00000000..d9f5b13b --- /dev/null +++ b/src/se/element.rs @@ -0,0 +1,1456 @@ +//! Contains serializer for an XML element + +use crate::de::{TEXT_KEY, VALUE_KEY}; +use crate::errors::serialize::DeError; +use crate::se::content::ContentSerializer; +use crate::se::key::QNameSerializer; +use crate::se::simple_type::{QuoteTarget, SimpleTypeSerializer}; +use crate::se::XmlName; +use serde::ser::{ + Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, + SerializeTupleStruct, SerializeTupleVariant, Serializer, +}; +use serde::serde_if_integer128; +use std::fmt::Write; + +macro_rules! write_primitive { + ($method:ident ( $ty:ty )) => { + fn $method(self, value: $ty) -> Result { + self.ser.write_wrapped(self.key, |ser| ser.$method(value)) + } + }; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer used to serialize element with specified name. +pub struct ElementSerializer<'k, W: Write> { + pub ser: ContentSerializer, + /// Tag name used to wrap serialized types except enum variants which uses the variant name + pub(super) key: XmlName<'k>, +} + +impl<'k, W: Write> Serializer for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Self; + type SerializeMap = Map<'k, W>; + type SerializeStruct = Struct<'k, W>; + type SerializeStructVariant = Struct<'k, W>; + + write_primitive!(serialize_bool(bool)); + + write_primitive!(serialize_i8(i8)); + write_primitive!(serialize_i16(i16)); + write_primitive!(serialize_i32(i32)); + write_primitive!(serialize_i64(i64)); + + write_primitive!(serialize_u8(u8)); + write_primitive!(serialize_u16(u16)); + write_primitive!(serialize_u32(u32)); + write_primitive!(serialize_u64(u64)); + + serde_if_integer128! { + write_primitive!(serialize_i128(i128)); + write_primitive!(serialize_u128(u128)); + } + + write_primitive!(serialize_f32(f32)); + write_primitive!(serialize_f64(f64)); + + write_primitive!(serialize_char(char)); + write_primitive!(serialize_bytes(&[u8])); + + fn serialize_str(self, value: &str) -> Result { + if value.is_empty() { + self.ser.write_empty(self.key) + } else { + self.ser + .write_wrapped(self.key, |ser| ser.serialize_str(value)) + } + } + + /// By serde contract we should serialize key of [`None`] values. If someone + /// wants to skip the field entirely, he should use + /// `#[serde(skip_serializing_if = "Option::is_none")]`. + /// + /// In XML when we serialize field, we write field name as: + /// - element name, or + /// - attribute name + /// + /// and field value as + /// - content of the element, or + /// - attribute value + /// + /// So serialization of `None` works the same as [serialization of `()`](#method.serialize_unit) + fn serialize_none(self) -> Result { + self.serialize_unit() + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + self.ser.write_empty(self.key) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.ser.write_empty(self.key) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + let name = XmlName::try_from(variant)?; + self.ser.write_empty(name) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + fn serialize_newtype_variant( + mut self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + self.key = XmlName::try_from(variant)?; + value.serialize(self) + } + + #[inline] + fn serialize_seq(self, _len: Option) -> Result { + Ok(self) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_tuple(len) + } + + #[inline] + fn serialize_tuple_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.key = XmlName::try_from(variant)?; + self.serialize_tuple_struct(name, len) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(Map { + ser: self.serialize_struct("", 0)?, + key: None, + }) + } + + #[inline] + fn serialize_struct( + mut self, + _name: &'static str, + _len: usize, + ) -> Result { + self.ser.writer.write_char('<')?; + self.ser.writer.write_str(self.key.0)?; + Ok(Struct { + ser: self, + children: String::new(), + }) + } + + #[inline] + fn serialize_struct_variant( + mut self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + self.key = XmlName::try_from(variant)?; + self.serialize_struct(name, len) + } +} + +impl<'k, W: Write> SerializeSeq for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + value.serialize(ElementSerializer { + ser: self.ser.new_seq_element_serializer(), + key: self.key, + })?; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.ser.writer) + } +} + +impl<'k, W: Write> SerializeTuple for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +impl<'k, W: Write> SerializeTupleStruct for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +impl<'k, W: Write> SerializeTupleVariant for ElementSerializer<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer for struct variants, which serializes the struct contents inside +/// of wrapping tags (`<${tag}>...`). +/// +/// Serialization of each field depends on it representation: +/// - attributes written directly to the higher serializer +/// - elements buffered into internal buffer and at the end written into higher +/// serializer +pub struct Struct<'k, W: Write> { + ser: ElementSerializer<'k, W>, + /// Buffer to store serialized elements + // TODO: Customization point: allow direct writing of elements, but all + // attributes should be listed first. Fail, if attribute encountered after + // element. Use feature to configure + children: String, +} + +impl<'k, W: Write> Struct<'k, W> { + #[inline] + fn write_field(&mut self, key: &str, value: &T) -> Result<(), DeError> + where + T: ?Sized + Serialize, + { + //TODO: Customization point: allow user to determine if field is attribute or not + if let Some(key) = key.strip_prefix('@') { + let key = XmlName::try_from(key)?; + self.write_attribute(key, value) + } else { + self.write_element(key, value) + } + } + + /// Writes `value` as an attribute + #[inline] + fn write_attribute(&mut self, key: XmlName, value: &T) -> Result<(), DeError> + where + T: ?Sized + Serialize, + { + //TODO: Customization point: each attribute on new line + self.ser.ser.writer.write_char(' ')?; + self.ser.ser.writer.write_str(key.0)?; + self.ser.ser.writer.write_char('=')?; + + //TODO: Customization point: preferred quote style + self.ser.ser.writer.write_char('"')?; + value.serialize(SimpleTypeSerializer { + writer: &mut self.ser.ser.writer, + target: QuoteTarget::DoubleQAttr, + level: self.ser.ser.level, + })?; + self.ser.ser.writer.write_char('"')?; + + Ok(()) + } + + /// Writes `value` either as a text content, or as an element. + /// + /// If `key` has a magic value [`TEXT_KEY`], then `value` serialized as a + /// [simple type]. + /// + /// If `key` has a magic value [`CONTENT_KEY`], then `value` serialized as a + /// [content] without wrapping in tags, otherwise it is wrapped in + /// `<${key}>...`. + /// + /// [simple type]: SimpleTypeSerializer + /// [content]: ContentSerializer + fn write_element(&mut self, key: &str, value: &T) -> Result<(), DeError> + where + T: ?Sized + Serialize, + { + let ser = ContentSerializer { + writer: &mut self.children, + level: self.ser.ser.level, + }; + + if key == TEXT_KEY { + value.serialize(ser.into_simple_type_serializer())?; + } else if key == VALUE_KEY { + value.serialize(ser)?; + } else { + value.serialize(ElementSerializer { + key: XmlName::try_from(key)?, + ser, + })?; + } + Ok(()) + } +} + +impl<'k, W: Write> SerializeStruct for Struct<'k, W> { + type Ok = W; + type Error = DeError; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + self.write_field(key, value) + } + + fn end(mut self) -> Result { + if self.children.is_empty() { + self.ser.ser.writer.write_str("/>")?; + } else { + self.ser.ser.writer.write_char('>')?; + self.ser.ser.writer.write_str(&self.children)?; + self.ser.ser.writer.write_str("')?; + } + Ok(self.ser.ser.writer) + } +} + +impl<'k, W: Write> SerializeStructVariant for Struct<'k, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + ::serialize_field(self, key, value) + } + + #[inline] + fn end(self) -> Result { + ::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct Map<'k, W: Write> { + ser: Struct<'k, W>, + /// Key, serialized by `QNameSerializer` if consumer uses `serialize_key` + + /// `serialize_value` calls instead of `serialize_entry` + key: Option, +} + +impl<'k, W: Write> Map<'k, W> { + fn make_key(&mut self, key: &T) -> Result + where + T: ?Sized + Serialize, + { + key.serialize(QNameSerializer { + writer: String::new(), + }) + } +} + +impl<'k, W: Write> SerializeMap for Map<'k, W> { + type Ok = W; + type Error = DeError; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + if let Some(_) = self.key.take() { + return Err(DeError::Custom( + "calling `serialize_key` twice without `serialize_value`".to_string(), + )); + } + self.key = Some(self.make_key(key)?); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + if let Some(key) = self.key.take() { + return self.ser.write_field(&key, value); + } + Err(DeError::Custom( + "calling `serialize_value` without call of `serialize_key`".to_string(), + )) + } + + fn serialize_entry(&mut self, key: &K, value: &V) -> Result<(), Self::Error> + where + K: ?Sized + Serialize, + V: ?Sized + Serialize, + { + let key = self.make_key(key)?; + self.ser.write_field(&key, value) + } + + fn end(mut self) -> Result { + if let Some(key) = self.key.take() { + return Err(DeError::Custom(format!( + "calling `end` without call of `serialize_value` for key `{key}`" + ))); + } + SerializeStruct::end(self.ser) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] +mod tests { + use super::*; + use crate::se::content::tests::*; + use crate::se::QuoteLevel; + use crate::utils::Bytes; + use serde::Serialize; + use std::collections::BTreeMap; + + #[derive(Debug, Serialize, PartialEq)] + struct OptionalElements { + a: Option<&'static str>, + + #[serde(skip_serializing_if = "Option::is_none")] + b: Option<&'static str>, + } + #[derive(Debug, Serialize, PartialEq)] + struct OptionalAttributes { + #[serde(rename = "@a")] + a: Option<&'static str>, + + #[serde(rename = "@b")] + #[serde(skip_serializing_if = "Option::is_none")] + b: Option<&'static str>, + } + + mod without_indent { + use super::*; + use crate::se::content::tests::Struct; + use pretty_assertions::assert_eq; + + /// Checks that given `$data` successfully serialized as `$expected` + macro_rules! serialize_as { + ($name:ident: $data:expr => $expected:expr) => { + #[test] + fn $name() { + let ser = ElementSerializer { + ser: ContentSerializer { + writer: String::new(), + level: QuoteLevel::Full, + }, + key: XmlName("root"), + }; + + let buffer = $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected); + } + }; + } + + /// Checks that attempt to serialize given `$data` results to a + /// serialization error `$kind` with `$reason` + macro_rules! err { + ($name:ident: $data:expr => $kind:ident($reason:literal)) => { + #[test] + fn $name() { + let mut buffer = String::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + }, + key: XmlName("root"), + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `{}({})`, found `{:?}`", + stringify!($kind), + $reason, + e + ), + } + // We can write something before fail + // assert_eq!(buffer, ""); + } + }; + } + + serialize_as!(false_: false => "false"); + serialize_as!(true_: true => "true"); + + serialize_as!(i8_: -42i8 => "-42"); + serialize_as!(i16_: -4200i16 => "-4200"); + serialize_as!(i32_: -42000000i32 => "-42000000"); + serialize_as!(i64_: -42000000000000i64 => "-42000000000000"); + serialize_as!(isize_: -42000000000000isize => "-42000000000000"); + + serialize_as!(u8_: 42u8 => "42"); + serialize_as!(u16_: 4200u16 => "4200"); + serialize_as!(u32_: 42000000u32 => "42000000"); + serialize_as!(u64_: 42000000000000u64 => "42000000000000"); + serialize_as!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + serialize_as!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + serialize_as!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + serialize_as!(f32_: 4.2f32 => "4.2"); + serialize_as!(f64_: 4.2f64 => "4.2"); + + serialize_as!(char_non_escaped: 'h' => "h"); + serialize_as!(char_lt: '<' => "<"); + serialize_as!(char_gt: '>' => ">"); + serialize_as!(char_amp: '&' => "&"); + serialize_as!(char_apos: '\'' => "'"); + serialize_as!(char_quot: '"' => """); + + serialize_as!(str_non_escaped: "non-escaped string" => "non-escaped string"); + serialize_as!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: Bytes(b"<\"escaped & bytes'>") => Unsupported("`serialize_bytes` not supported yet")); + + serialize_as!(option_none: Option::<&str>::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + serialize_as!(option_some_empty_str: Some("") => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + serialize_as!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: Enum::UnitEscaped + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + serialize_as!(newtype: Newtype(42) => "42"); + serialize_as!(enum_newtype: Enum::Newtype(42) => "42"); + + serialize_as!(seq: vec![1, 2, 3] + => "1\ + 2\ + 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\ + 42"); + serialize_as!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + serialize_as!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) + => "\ + <_1>2\ + <_3>4\ + "); + serialize_as!(struct_: Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + serialize_as!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + + /// Special field name `#text` should be serialized as text content. + /// Sequences serialized as an `xs:list` content + mod text { + use super::*; + + /// `#text` key in a map + mod map { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + BTreeMap::from([("#text", $data)]) + => ""); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + BTreeMap::from([("#text", $data)]) + => concat!("", $expected,"")); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("")); + + text!(unit: ()); + text!(unit_struct: Unit); + text!(unit_struct_escaped: UnitEscaped); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new()); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `#text` field + err!(map: + Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + + /// `#text` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None => ""); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("") => ""); + + text!(unit: () => ""); + text!(unit_struct: Unit => ""); + text!(unit_struct_escaped: UnitEscaped => ""); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new() => ""); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `#text` field + err!(map: + Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + + /// `#text` field inside a struct variant of an enum + mod enum_struct { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + text!(false_: false => "false"); + text!(true_: true => "true"); + + text!(i8_: -42i8 => "-42"); + text!(i16_: -4200i16 => "-4200"); + text!(i32_: -42000000i32 => "-42000000"); + text!(i64_: -42000000000000i64 => "-42000000000000"); + text!(isize_: -42000000000000isize => "-42000000000000"); + + text!(u8_: 42u8 => "42"); + text!(u16_: 4200u16 => "4200"); + text!(u32_: 42000000u32 => "42000000"); + text!(u64_: 42000000000000u64 => "42000000000000"); + text!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + text!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + text!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + text!(f32_: 4.2f32 => "4.2"); + text!(f64_: 4.2f64 => "4.2"); + + text!(char_non_escaped: 'h' => "h"); + text!(char_lt: '<' => "<"); + text!(char_gt: '>' => ">"); + text!(char_amp: '&' => "&"); + text!(char_apos: '\'' => "'"); + text!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + text!(char_space: ' ' => " "); + + text!(str_non_escaped: "non-escaped string" => "non-escaped string"); + text!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Text { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + text!(option_none: Option::<&str>::None => ""); + text!(option_some: Some("non-escaped string") => "non-escaped string"); + text!(option_some_empty_str: Some("") => ""); + + text!(unit: () => ""); + text!(unit_struct: Unit => ""); + text!(unit_struct_escaped: UnitEscaped => ""); + + text!(enum_unit: Enum::Unit => "Unit"); + text!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + text!(newtype: Newtype(42) => "42"); + // We have no space where name of a variant can be stored + err!(enum_newtype: + SpecialEnum::Text { + before: "answer", + content: Enum::Newtype(42), + after: "answer", + } + => Unsupported("enum newtype variant `Enum::Newtype` cannot be serialized as an attribute or text content value")); + + // Sequences are serialized separated by spaces, all spaces inside are escaped + text!(seq: vec![1, 2, 3] => "1 2 3"); + text!(seq_empty: Vec::::new() => ""); + text!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'> \ + with spaces \ + 3"); + text!(tuple_struct: Tuple("first", 42) => "first 42"); + // We have no space where name of a variant can be stored + err!(enum_tuple: + SpecialEnum::Text { + before: "answer", + content: Enum::Tuple("first", 42), + after: "answer", + } + => Unsupported("enum tuple variant `Enum::Tuple` cannot be serialized as an attribute or text content value")); + + // Complex types cannot be serialized in `#text` field + err!(map: + SpecialEnum::Text { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("map cannot be serialized as an attribute or text content value")); + err!(struct_: + SpecialEnum::Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("struct `Struct` cannot be serialized as an attribute or text content value")); + err!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("enum struct variant `Enum::Struct` cannot be serialized as an attribute or text content value")); + } + } + + /// Special field name `$value` should be serialized using name, provided + /// by the type of value instead of a key. Sequences serialized as a list + /// of tags with that name (each element can have their own name) + mod value { + use super::*; + + /// `$value` key in a map + mod map { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + BTreeMap::from([("$value", $data)]) + => ""); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + BTreeMap::from([("$value", $data)]) + => concat!("", $expected,"")); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + BTreeMap::from([("$value", Bytes(b"<\"escaped & bytes'>"))]) + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("")); + + value!(unit: ()); + value!(unit_struct: Unit); + value!(unit_struct_escaped: UnitEscaped); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + BTreeMap::from([("$value", Enum::UnitEscaped)]) + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + BTreeMap::from([("$value", BTreeMap::from([("_1", 2), ("_3", 4)]))]) + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + BTreeMap::from([("$value", Struct { key: "answer", val: (42, 42) })]) + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + } + + /// `$value` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None => ""); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("") => ""); + + value!(unit: () => ""); + value!(unit_struct: Unit => ""); + value!(unit_struct_escaped: UnitEscaped => ""); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new() => ""); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + } + + /// `$value` field inside a struct variant of an enum + mod enum_struct { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "answer", + $expected, + "answer", + )); + }; + } + + value!(false_: false => "false"); + value!(true_: true => "true"); + + value!(i8_: -42i8 => "-42"); + value!(i16_: -4200i16 => "-4200"); + value!(i32_: -42000000i32 => "-42000000"); + value!(i64_: -42000000000000i64 => "-42000000000000"); + value!(isize_: -42000000000000isize => "-42000000000000"); + + value!(u8_: 42u8 => "42"); + value!(u16_: 4200u16 => "4200"); + value!(u32_: 42000000u32 => "42000000"); + value!(u64_: 42000000000000u64 => "42000000000000"); + value!(usize_: 42000000000000usize => "42000000000000"); + + serde_if_integer128! { + value!(i128_: -420000000000000000000000000000i128 => "-420000000000000000000000000000"); + value!(u128_: 420000000000000000000000000000u128 => "420000000000000000000000000000"); + } + + value!(f32_: 4.2f32 => "4.2"); + value!(f64_: 4.2f64 => "4.2"); + + value!(char_non_escaped: 'h' => "h"); + value!(char_lt: '<' => "<"); + value!(char_gt: '>' => ">"); + value!(char_amp: '&' => "&"); + value!(char_apos: '\'' => "'"); + value!(char_quot: '"' => """); + //TODO: add a setting to escape leading/trailing spaces, in order to + // pretty-print does not change the content + value!(char_space: ' ' => " "); + + value!(str_non_escaped: "non-escaped string" => "non-escaped string"); + value!(str_escaped: "<\"escaped & string'>" => "<"escaped & string'>"); + + err!(bytes: + SpecialEnum::Value { + before: "answer", + content: Bytes(b"<\"escaped & bytes'>"), + after: "answer", + } + => Unsupported("`serialize_bytes` not supported yet")); + + value!(option_none: Option::<&str>::None => ""); + value!(option_some: Some("non-escaped string") => "non-escaped string"); + value!(option_some_empty_str: Some("") => ""); + + value!(unit: () => ""); + value!(unit_struct: Unit => ""); + value!(unit_struct_escaped: UnitEscaped => ""); + + value!(enum_unit: Enum::Unit => ""); + err!(enum_unit_escaped: + SpecialEnum::Value { + before: "answer", + content: Enum::UnitEscaped, + after: "answer", + } + => Unsupported("character `<` is not allowed at the start of an XML name `<\"&'>`")); + + value!(newtype: Newtype(42) => "42"); + value!(enum_newtype: Enum::Newtype(42) => "42"); + + // Note that sequences of primitives serialized without delimiters! + value!(seq: vec![1, 2, 3] => "123"); + value!(seq_empty: Vec::::new() => ""); + value!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\ + with\t\r\n spaces\ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\ + 42"); + + // We cannot wrap map or struct in any container and should not + // flatten it, so it is impossible to serialize maps and structs + err!(map: + SpecialEnum::Value { + before: "answer", + content: BTreeMap::from([("_1", 2), ("_3", 4)]), + after: "answer", + } + => Unsupported("serialization of map types is not supported in `$value` field")); + err!(struct_: + SpecialEnum::Value { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("serialization of struct `Struct` is not supported in `$value` field")); + value!(enum_struct: + Enum::Struct { key: "answer", val: (42, 42) } + => "\ + answer\ + 42\ + 42\ + "); + } + } + + mod attributes { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(map_attr: BTreeMap::from([("@key1", 1), ("@key2", 2)]) + => r#""#); + serialize_as!(map_mixed: BTreeMap::from([("@key1", 1), ("key2", 2)]) + => r#"2"#); + + serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); + + serialize_as!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(enum_before: Enum::AttributesBefore { key: "answer", val: 42 } + => r#"42"#); + serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } + => r#"answer"#); + + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(none: + OptionalAttributes { a: None, b: None } + => r#""#); + serialize_as!(some_empty_str: + OptionalAttributes { + a: Some(""), + b: Some(""), + } + => r#""#); + serialize_as!(some_non_empty: + OptionalAttributes { + a: Some("1"), + b: Some("2"), + } + => r#""#); + } + } + + /// Test for https://github.com/tafia/quick-xml/issues/252 + mod optional { + use super::*; + use pretty_assertions::assert_eq; + + serialize_as!(none: + OptionalElements { a: None, b: None } + => "\ + \ + "); + serialize_as!(some_empty_str: + OptionalElements { + a: Some(""), + b: Some(""), + } + => "\ + \ + \ + "); + serialize_as!(some_non_empty: + OptionalElements { + a: Some("1"), + b: Some("2"), + } + => "\ + 1\ + 2\ + "); + } + } +} diff --git a/src/se/mod.rs b/src/se/mod.rs index ef9f733e..130f23f3 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -73,6 +73,8 @@ macro_rules! write_primitive { //////////////////////////////////////////////////////////////////////////////////////////////////// +mod content; +mod element; mod key; pub(crate) mod simple_type; mod var;