diff --git a/Cargo.toml b/Cargo.toml index a86063ce..e8344fd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = ">=1.0.139", optional = true } tokio = { version = "1.10", optional = true, default-features = false, features = ["io-util"] } memchr = "2.1" arbitrary = { version = "1", features = ["derive"], optional = true } +ref-cast = "1" [dev-dependencies] criterion = "0.4" diff --git a/Changelog.md b/Changelog.md index 97caff84..6f894176 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,10 +15,15 @@ ### New Features +- [#623]: Added `Reader::stream()` that can be used to read arbitrary data + from the inner reader while track position for XML reader. + ### Bug Fixes ### Misc Changes +[#623]: https://github.com/tafia/quick-xml/issues/623 + ## 0.36.0 -- 2024-07-08 diff --git a/src/de/map.rs b/src/de/map.rs index 3989a64d..e9817cb7 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -247,7 +247,7 @@ where // We shouldn't have both `$value` and `$text` fields in the same // struct, so if we have `$value` field, the we should deserialize // text content to `$value` - DeEvent::Text(_) if self.has_value_field => { + DeEvent::Text(_) | DeEvent::Binary(_) if self.has_value_field => { self.source = ValueSource::Content; // Deserialize `key` from special attribute name which means // that value should be taken from the text content of the @@ -255,7 +255,7 @@ where let de = BorrowedStrDeserializer::::new(VALUE_KEY); seed.deserialize(de).map(Some) } - DeEvent::Text(_) => { + DeEvent::Text(_) | DeEvent::Binary(_) => { self.source = ValueSource::Text; // Deserialize `key` from special attribute name which means // that value should be taken from the text content of the @@ -943,6 +943,9 @@ where // SAFETY: we just checked that the next event is Text _ => unreachable!(), }, + DeEvent::Binary(_) => Err(Self::Error::Unsupported( + "undecodable binary data among a sequence of xml elements".into(), + )), DeEvent::Start(_) => match self.map.de.next()? { DeEvent::Start(start) => seed .deserialize(ElementDeserializer { diff --git a/src/de/mod.rs b/src/de/mod.rs index e150e78e..b7f07dd1 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -2005,7 +2005,7 @@ use crate::{ errors::Error, events::{BytesCData, BytesEnd, BytesStart, BytesText, Event}, name::QName, - reader::Reader, + reader::{Config, Reader}, }; use serde::de::{self, Deserialize, DeserializeOwned, DeserializeSeed, SeqAccess, Visitor}; use std::borrow::Cow; @@ -2056,6 +2056,31 @@ impl<'a> From<&'a str> for Text<'a> { } } +/// Docs +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Binary<'a> { + /// Field + pub text: Cow<'a, [u8]>, +} + +impl<'a> Deref for Binary<'a> { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.text.deref() + } +} + +impl<'a> From<&'a [u8]> for Binary<'a> { + #[inline] + fn from(text: &'a [u8]) -> Self { + Self { + text: Cow::Borrowed(text), + } + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// /// Simplified event which contains only these variants that used by deserializer @@ -2074,6 +2099,8 @@ pub enum DeEvent<'a> { /// [`Comment`]: Event::Comment /// [`PI`]: Event::PI Text(Text<'a>), + /// Binary undecoded + Binary(Binary<'a>), /// End of XML document. Eof, } @@ -2179,19 +2206,22 @@ impl<'i, R: XmlRead<'i>, E: EntityResolver> XmlReader<'i, R, E> { /// occurs. Content of all events would be appended to `result` and returned /// as [`DeEvent::Text`]. /// + /// If the resulting text empty, this function returns None to avoid creating an empty Event. + /// /// [`Text`]: PayloadEvent::Text /// [`CData`]: PayloadEvent::CData - fn drain_text(&mut self, mut result: Cow<'i, str>) -> Result, DeError> { + fn drain_text(&mut self, mut result: Cow<'i, str>) -> Result>, DeError> { loop { if self.current_event_is_last_text() { break; } - match self.next_impl()? { PayloadEvent::Text(mut e) => { if self.current_event_is_last_text() { // FIXME: Actually, we should trim after decoding text, but now we trim before - e.inplace_trim_end(); + if self.reader.config().trim_text_end { + e.inplace_trim_end(); + } } result .to_mut() @@ -2200,10 +2230,12 @@ impl<'i, R: XmlRead<'i>, E: EntityResolver> XmlReader<'i, R, E> { PayloadEvent::CData(e) => result.to_mut().push_str(&e.decode()?), // SAFETY: current_event_is_last_text checks that event is Text or CData - _ => unreachable!("Only `Text` and `CData` events can come here"), + e => { + unreachable!("Only `Text` and `CData` events can come here: {:?}", &e); + } } } - Ok(DeEvent::Text(Text { text: result })) + Ok(Some(DeEvent::Text(Text { text: result }))) } /// Return an input-borrowing event. @@ -2213,13 +2245,29 @@ impl<'i, R: XmlRead<'i>, E: EntityResolver> XmlReader<'i, R, E> { PayloadEvent::Start(e) => Ok(DeEvent::Start(e)), PayloadEvent::End(e) => Ok(DeEvent::End(e)), PayloadEvent::Text(mut e) => { - if self.current_event_is_last_text() && e.inplace_trim_end() { - // FIXME: Actually, we should trim after decoding text, but now we trim before - continue; + if self.current_event_is_last_text() { + if self.reader.config().trim_text_end && e.inplace_trim_end() { + continue; + } + } + + match e + .unescape_with(|entity| self.entity_resolver.resolve(entity)) + .map(|res| self.drain_text(res)) + { + Ok(Ok(None)) => continue, + Ok(Ok(Some(x))) => Ok(x), + Ok(Err(x)) => Err(x), + // failed to escape treat as binary blob. + Err(_) => Ok(DeEvent::Binary(Binary { + text: e.into_inner(), + })), } - self.drain_text(e.unescape_with(|entity| self.entity_resolver.resolve(entity))?) } - PayloadEvent::CData(e) => self.drain_text(e.decode()?), + PayloadEvent::CData(e) => match self.drain_text(e.decode()?).transpose() { + None => continue, + Some(x) => x, + }, PayloadEvent::DocType(e) => { self.entity_resolver .capture(e) @@ -2296,6 +2344,16 @@ where T::deserialize(&mut de) } +/// Deserialize from a custom reader. +pub fn from_custom_reader(reader: Reader) -> Result +where + R: BufRead, + T: DeserializeOwned, +{ + let mut de = Deserializer::from_custom_reader(reader); + T::deserialize(&mut de) +} + // TODO: According to the https://www.w3.org/TR/xmlschema11-2/#boolean, // valid boolean representations are only "true", "false", "1", and "0" fn str2bool<'de, V>(value: &str, visitor: V) -> Result @@ -2687,6 +2745,8 @@ where fn read_string_impl(&mut self, allow_start: bool) -> Result, DeError> { match self.next()? { DeEvent::Text(e) => Ok(e.text), + // SAFETY: Binary event should never be emitted for decoded strings. + DeEvent::Binary(e) => unreachable!("{:?}", e), // allow one nested level DeEvent::Start(e) if allow_start => self.read_text(e.name()), DeEvent::Start(e) => Err(DeError::UnexpectedStart(e.name().as_ref().to_owned())), @@ -2708,10 +2768,12 @@ where // The matching tag name is guaranteed by the reader DeEvent::End(_) => Ok(e.text), // SAFETY: Cannot be two consequent Text events, they would be merged into one - DeEvent::Text(_) => unreachable!(), + DeEvent::Text(_) | DeEvent::Binary(_) => unreachable!(), DeEvent::Start(e) => Err(DeError::UnexpectedStart(e.name().as_ref().to_owned())), DeEvent::Eof => Err(Error::missed_end(name, self.reader.decoder()).into()), }, + // SAFETY: Binary event should never be emitted for decoded strings. + DeEvent::Binary(e) => unreachable!("{:?}", e), // We can get End event in case of `` or `` input // Return empty text in that case // The matching tag name is guaranteed by the reader @@ -2827,6 +2889,30 @@ where } } +impl<'de, R> Deserializer<'de, IoReader> +where + R: BufRead, +{ + /// Create new deserializer that will copy data from the specified reader + /// into internal buffer. + /// + /// If you already have a string use [`Self::from_str`] instead, because it + /// will borrow instead of copy. If you have `&[u8]` which is known to represent + /// UTF-8, you can decode it first before using [`from_str`]. + /// + /// Deserializer created with this method will not resolve custom entities. + pub fn from_custom_reader(reader: Reader) -> Self { + Self::new( + IoReader { + reader, + start_trimmer: StartTrimmer::default(), + buf: Vec::new(), + }, + PredefinedEntityResolver, + ) + } +} + impl<'de, R, E> Deserializer<'de, IoReader, E> where R: BufRead, @@ -2884,6 +2970,10 @@ where Cow::Borrowed(s) => visitor.visit_borrowed_str(s), Cow::Owned(s) => visitor.visit_string(s), }, + DeEvent::Binary(e) => match e.text { + Cow::Borrowed(s) => visitor.visit_borrowed_bytes(s), + Cow::Owned(s) => visitor.visit_byte_buf(s), + }, DeEvent::Eof => Err(DeError::UnexpectedEof), } } @@ -2914,7 +3004,7 @@ where self.read_to_end(s.name())?; visitor.visit_unit() } - DeEvent::Text(_) => visitor.visit_unit(), + DeEvent::Text(_) | DeEvent::Binary(_) => visitor.visit_unit(), // SAFETY: The reader is guaranteed that we don't have unmatched tags // If we here, then out deserializer has a bug DeEvent::End(e) => unreachable!("{:?}", e), @@ -3022,7 +3112,7 @@ impl StartTrimmer { /// Converts raw reader's event into a payload event. /// Returns `None`, if event should be skipped. #[inline(always)] - fn trim<'a>(&mut self, event: Event<'a>) -> Option> { + fn trim<'a>(&mut self, event: Event<'a>, trim_text_start: bool) -> Option> { let (event, trim_next_event) = match event { Event::DocType(e) => (PayloadEvent::DocType(e), true), Event::Start(e) => (PayloadEvent::Start(e), true), @@ -3033,7 +3123,10 @@ impl StartTrimmer { Event::CData(e) => (PayloadEvent::CData(e), false), Event::Text(mut e) => { // If event is empty after trimming, skip it - if self.trim_start && e.inplace_trim_start() { + // Or if event is all white space, skip it regardless of trimming settings + if (trim_text_start && self.trim_start && e.inplace_trim_start()) + || e.is_all_whitespace() + { return None; } (PayloadEvent::Text(e), false) @@ -3071,6 +3164,9 @@ pub trait XmlRead<'i> { /// A copy of the reader's decoder used to decode strings. fn decoder(&self) -> Decoder; + + /// Returns a reference to the reader config. + fn config(&self) -> &Config; } /// XML input source that reads from a std::io input stream. @@ -3123,8 +3219,9 @@ impl<'i, R: BufRead> XmlRead<'i> for IoReader { loop { self.buf.clear(); + let trim_text_start = self.reader.config().trim_text_start; let event = self.reader.read_event_into(&mut self.buf)?; - if let Some(event) = self.start_trimmer.trim(event) { + if let Some(event) = self.start_trimmer.trim(event, trim_text_start) { return Ok(event.into_owned()); } } @@ -3140,6 +3237,10 @@ impl<'i, R: BufRead> XmlRead<'i> for IoReader { fn decoder(&self) -> Decoder { self.reader.decoder() } + + fn config(&self) -> &Config { + self.reader.config() + } } /// XML input source that reads from a slice of bytes and can borrow from it. @@ -3189,7 +3290,10 @@ impl<'de> XmlRead<'de> for SliceReader<'de> { fn next(&mut self) -> Result, DeError> { loop { let event = self.reader.read_event()?; - if let Some(event) = self.start_trimmer.trim(event) { + if let Some(event) = self + .start_trimmer + .trim(event, self.config().trim_text_start) + { return Ok(event); } } @@ -3205,6 +3309,10 @@ impl<'de> XmlRead<'de> for SliceReader<'de> { fn decoder(&self) -> Decoder { self.reader.decoder() } + + fn config(&self) -> &Config { + self.reader.config() + } } #[cfg(test)] @@ -4363,7 +4471,7 @@ mod tests { fn start() { let mut de = make_de(" text "); // Text is trimmed from both sides - assert_eq!(de.next().unwrap(), DeEvent::Text("text".into())); + assert_eq!(de.next().unwrap(), DeEvent::Text(" text ".into())); assert_eq!(de.next().unwrap(), DeEvent::Start(BytesStart::new("tag1"))); assert_eq!(de.next().unwrap(), DeEvent::Start(BytesStart::new("tag2"))); assert_eq!(de.next().unwrap(), DeEvent::Eof); diff --git a/src/de/var.rs b/src/de/var.rs index f534283a..84b89b39 100644 --- a/src/de/var.rs +++ b/src/de/var.rs @@ -46,7 +46,7 @@ where seed.deserialize(QNameDeserializer::from_elem(e.raw_name(), decoder)?)?, false, ), - DeEvent::Text(_) => ( + DeEvent::Text(_) | DeEvent::Binary(_) => ( seed.deserialize(BorrowedStrDeserializer::::new(TEXT_KEY))?, true, ), diff --git a/src/errors.rs b/src/errors.rs index 0c5c46e3..8f532d5b 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -464,4 +464,10 @@ pub mod serialize { Self::Custom(e.to_string()) } } + impl From for DeError { + #[inline] + fn from(e: std::io::Error) -> Self { + Self::Custom(e.to_string()) + } + } } diff --git a/src/events/mod.rs b/src/events/mod.rs index c9acb5b7..26f2b55f 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -53,7 +53,7 @@ use crate::escape::{ use crate::name::{LocalName, QName}; #[cfg(feature = "serialize")] use crate::utils::CowRef; -use crate::utils::{name_len, trim_xml_end, trim_xml_start, write_cow_string}; +use crate::utils::{is_whitespace, name_len, trim_xml_end, trim_xml_start, write_cow_string}; use attributes::{Attribute, Attributes}; /// Opening tag data (`Event::Start`), with optional attributes: ``. @@ -622,6 +622,11 @@ impl<'a> BytesText<'a> { self.content = trim_cow(replace(&mut self.content, Cow::Borrowed(b"")), trim_xml_end); self.content.is_empty() } + + /// Returns `true` if all characters are whitespace characters. + pub fn is_all_whitespace(&mut self) -> bool { + self.content.iter().all(|&x| is_whitespace(x)) + } } impl<'a> Debug for BytesText<'a> { diff --git a/src/reader/async_tokio.rs b/src/reader/async_tokio.rs index 2e75b43f..ac74e232 100644 --- a/src/reader/async_tokio.rs +++ b/src/reader/async_tokio.rs @@ -2,14 +2,17 @@ //! as underlying byte stream. This reader fully implements async/await so reading //! can use non-blocking I/O. -use tokio::io::{self, AsyncBufRead, AsyncBufReadExt}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use tokio::io::{self, AsyncBufRead, AsyncBufReadExt, AsyncRead, ReadBuf}; use crate::errors::{Error, Result, SyntaxError}; use crate::events::Event; use crate::name::{QName, ResolveResult}; use crate::parser::{ElementParser, Parser, PiParser}; use crate::reader::buffered_reader::impl_buffered_source; -use crate::reader::{BangType, NsReader, ParseState, ReadTextResult, Reader, Span}; +use crate::reader::{BangType, BinaryStream, NsReader, ParseState, ReadTextResult, Reader, Span}; use crate::utils::is_whitespace; /// A struct for read XML asynchronously from an [`AsyncBufRead`]. @@ -24,6 +27,47 @@ impl<'a, R: AsyncBufRead + Unpin> TokioAdapter<'a, R> { //////////////////////////////////////////////////////////////////////////////////////////////////// +impl<'r, R> AsyncRead for BinaryStream<'r, R> +where + R: AsyncRead + Unpin, +{ + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let start = buf.remaining(); + let this = self.get_mut(); + let poll = Pin::new(&mut *this.inner).poll_read(cx, buf); + + // If something was read, update offset + if let Poll::Ready(Ok(_)) = poll { + let amt = start - buf.remaining(); + *this.offset += amt as u64; + } + poll + } +} + +impl<'r, R> AsyncBufRead for BinaryStream<'r, R> +where + R: AsyncBufRead + Unpin, +{ + #[inline] + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut *self.get_mut().inner).poll_fill_buf(cx) + } + + #[inline] + fn consume(self: Pin<&mut Self>, amt: usize) { + let this = self.get_mut(); + this.inner.consume(amt); + *this.offset += amt as u64; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + impl Reader { /// An asynchronous version of [`read_event_into()`]. Reads the next event into /// given buffer. diff --git a/src/reader/mod.rs b/src/reader/mod.rs index 6e030b73..9c6e7599 100644 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -567,6 +567,65 @@ impl EncodingRef { //////////////////////////////////////////////////////////////////////////////////////////////////// +/// A direct stream to the underlying [`Reader`]s reader which updates +/// [`Reader::buffer_position()`] when read from it. +#[derive(Debug)] +#[must_use = "streams do nothing unless read or polled"] +pub struct BinaryStream<'r, R> { + inner: &'r mut R, + offset: &'r mut u64, +} + +impl<'r, R> BinaryStream<'r, R> { + /// Returns current position in bytes in the original source. + #[inline] + pub const fn offset(&self) -> u64 { + *self.offset + } + + /// Gets a reference to the underlying reader. + #[inline] + pub const fn get_ref(&self) -> &R { + self.inner + } + + /// Gets a mutable reference to the underlying reader. + #[inline] + pub fn get_mut(&mut self) -> &mut R { + self.inner + } +} + +impl<'r, R> io::Read for BinaryStream<'r, R> +where + R: io::Read, +{ + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let amt = self.inner.read(buf)?; + *self.offset += amt as u64; + Ok(amt) + } +} + +impl<'r, R> io::BufRead for BinaryStream<'r, R> +where + R: io::BufRead, +{ + #[inline] + fn fill_buf(&mut self) -> io::Result<&[u8]> { + self.inner.fill_buf() + } + + #[inline] + fn consume(&mut self, amt: usize) { + self.inner.consume(amt); + *self.offset += amt as u64; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + /// A low level encoding-agnostic XML event reader. /// /// Consumes bytes and streams XML [`Event`]s. @@ -759,6 +818,60 @@ impl Reader { pub const fn decoder(&self) -> Decoder { self.state.decoder() } + + /// Get the direct access to the underlying reader, but tracks the amount of + /// read data and update [`Reader::buffer_position()`] accordingly. + /// + /// # Example + /// + /// This example demonstrates, how it is possible to read embedded binary data. + /// Such XML documents are exist in the wild. + /// + /// ``` + /// # use pretty_assertions::assert_eq; + /// use std::io::{BufRead, Read}; + /// use quick_xml::events::{BytesEnd, BytesStart, Event}; + /// use quick_xml::reader::Reader; + /// + /// let mut reader = Reader::from_str("binary << data&>"); + /// // ^ ^ ^ ^ + /// // 0 5 21 27 + /// + /// assert_eq!( + /// (reader.read_event().unwrap(), reader.buffer_position()), + /// // 5 - end of the `` + /// (Event::Start(BytesStart::new("tag")), 5) + /// ); + /// + /// // Reading directly from underlying reader will not update position + /// // let mut inner = reader.get_mut(); + /// + /// // Reading from the stream() advances position + /// let mut inner = reader.stream(); + /// + /// // Read binary data. We somehow should known its size + /// let mut binary = [0u8; 16]; + /// inner.read_exact(&mut binary).unwrap(); + /// assert_eq!(&binary, b"binary << data&>"); + /// // 21 - end of the `binary << data&>` + /// assert_eq!(inner.offset(), 21); + /// assert_eq!(reader.buffer_position(), 21); + /// + /// assert_eq!( + /// (reader.read_event().unwrap(), reader.buffer_position()), + /// // 27 - end of the `` + /// (Event::End(BytesEnd::new("tag")), 27) + /// ); + /// + /// assert_eq!(reader.read_event().unwrap(), Event::Eof); + /// ``` + #[inline] + pub fn stream(&mut self) -> BinaryStream { + BinaryStream { + inner: &mut self.reader, + offset: &mut self.state.offset, + } + } } /// Private sync reading methods diff --git a/src/se/content.rs b/src/se/content.rs index 91e02880..31f3e634 100644 --- a/src/se/content.rs +++ b/src/se/content.rs @@ -58,7 +58,7 @@ macro_rules! write_primitive { /// with indent, sequence of strings become one big string with additional content /// and it would be impossible to distinguish between content of the original /// strings and inserted indent characters. -pub struct ContentSerializer<'w, 'i, W: Write> { +pub struct ContentSerializer<'w, 'i, W> { pub writer: &'w mut W, /// Defines which XML characters need to be escaped in text content pub level: QuoteLevel, diff --git a/src/se/element.rs b/src/se/element.rs index 35aa28f4..c9e0d1a9 100644 --- a/src/se/element.rs +++ b/src/se/element.rs @@ -56,7 +56,7 @@ macro_rules! write_primitive { /// - other variants are not supported ([`DeError::Unsupported`] is returned); /// /// Usage of empty tags depends on the [`ContentSerializer::expand_empty_elements`] setting. -pub struct ElementSerializer<'w, 'k, W: Write> { +pub struct ElementSerializer<'w, 'k, W> { /// The inner serializer that contains the settings and mostly do the actual work pub ser: ContentSerializer<'w, 'k, W>, /// Tag name used to wrap serialized types except enum variants which uses the variant name diff --git a/src/se/io.rs b/src/se/io.rs new file mode 100644 index 00000000..17e098b5 --- /dev/null +++ b/src/se/io.rs @@ -0,0 +1,416 @@ +#![allow(missing_docs)] +//! Serializers to an std::io output stream. + +mod content; +mod element; +mod simple_type; +mod text; + +use content::*; +use element::*; +use simple_type::*; +use text::*; + +use ref_cast::RefCast; +use std::str::from_utf8; + +use serde::ser::{ + self, Impossible, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, + SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, +}; +use serde::{serde_if_integer128, Serialize}; + +use crate::de::VALUE_KEY; +use crate::{de::TEXT_KEY, writer::Indentation, DeError}; + +use super::{simple_type::QuoteTarget, Indent, QuoteLevel, XmlName}; + +/// Wrapper for a std::io::Write writer that also implements std::fmt::Write for +/// compatibility with original serializers that work only with +/// std::fmt::Write writers. +#[derive(RefCast)] +#[repr(transparent)] +pub(crate) struct FmtWriter { + pub(crate) writer: W, +} + +impl std::fmt::Write for FmtWriter { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + self.writer + .write(s.as_bytes()) + .map_err(|_| std::fmt::Error)?; + Ok(()) + } + fn write_char(&mut self, c: char) -> std::fmt::Result { + std::fmt::Write::write_str(self, c.encode_utf8(&mut [0; 4])) + } +} + +pub trait Write: std::io::Write { + fn write_str(&mut self, s: &str) -> Result<(), DeError> { + self.write(s.as_bytes())?; + Ok(()) + } + fn write_char(&mut self, c: char) -> Result<(), DeError> { + self.write_str(c.encode_utf8(&mut [0; 4])) + } +} + +impl Write for W {} + +impl<'i> Indent<'i> { + pub fn write_io_indent(&mut self, mut writer: W) -> Result<(), DeError> { + match self { + Self::None => {} + Self::Owned(i) => { + writer.write_char('\n')?; + writer.write_str(from_utf8(i.current())?)?; + } + Self::Borrow(i) => { + writer.write_char('\n')?; + writer.write_str(from_utf8(i.current())?)?; + } + } + Ok(()) + } +} + +/// Implements serialization method by forwarding it to the serializer created by +/// the helper method [`Serializer::ser`]. +macro_rules! forward { + ($name:ident($ty:ty)) => { + fn $name(self, value: $ty) -> Result { + self.ser(&concat!("`", stringify!($ty), "`"))?.$name(value) + } + }; +} + +/// A Serializer +pub struct Serializer<'w, 'r, W> { + ser: ContentSerializer<'w, 'r, W>, + /// Name of the root tag. If not specified, deduced from the structure name + root_tag: Option>, +} + +impl<'w, 'r, W: std::io::Write> Serializer<'w, 'r, W> { + /// Creates a new `Serializer` that uses struct name as a root tag name. + /// + /// Note, that attempt to serialize a non-struct (including unit structs + /// and newtype structs) will end up to an error. Use `with_root` to create + /// serializer with explicitly defined root element name + pub fn new(writer: &'w mut W) -> Self { + Self { + ser: ContentSerializer { + writer, + level: QuoteLevel::Partial, + indent: Indent::None, + write_indent: false, + expand_empty_elements: false, + }, + root_tag: None, + } + } + + /// Creates a new `Serializer` that uses specified root tag name. `name` should + /// be valid [XML name], otherwise error is returned. + /// + /// # Examples + /// + /// When serializing a primitive type, only its representation will be written: + /// + /// ``` + /// # use pretty_assertions::assert_eq; + /// # use serde::Serialize; + /// # use quick_xml::se::Serializer; + /// + /// let mut buffer = String::new(); + /// let ser = Serializer::with_root(&mut buffer, Some("root")).unwrap(); + /// + /// "node".serialize(ser).unwrap(); + /// assert_eq!(buffer, "node"); + /// ``` + /// + /// When serializing a struct, newtype struct, unit struct or tuple `root_tag` + /// is used as tag name of root(s) element(s): + /// + /// ``` + /// # use pretty_assertions::assert_eq; + /// # use serde::Serialize; + /// # use quick_xml::se::Serializer; + /// + /// #[derive(Debug, PartialEq, Serialize)] + /// struct Struct { + /// question: String, + /// answer: u32, + /// } + /// + /// let mut buffer = String::new(); + /// let ser = Serializer::with_root(&mut buffer, Some("root")).unwrap(); + /// + /// let data = Struct { + /// question: "The Ultimate Question of Life, the Universe, and Everything".into(), + /// answer: 42, + /// }; + /// + /// data.serialize(ser).unwrap(); + /// assert_eq!( + /// buffer, + /// "\ + /// The Ultimate Question of Life, the Universe, and Everything\ + /// 42\ + /// " + /// ); + /// ``` + /// + /// [XML name]: https://www.w3.org/TR/xml11/#NT-Name + pub fn with_root(writer: &'w mut W, root_tag: Option<&'r str>) -> Result { + Ok(Self { + ser: ContentSerializer { + writer, + level: QuoteLevel::Partial, + indent: Indent::None, + write_indent: false, + expand_empty_elements: false, + }, + root_tag: root_tag.map(|tag| XmlName::try_from(tag)).transpose()?, + }) + } + + /// Enable or disable expansion of empty elements. Defaults to `false`. + /// + /// # Examples + /// + /// ``` + /// # use pretty_assertions::assert_eq; + /// # use serde::Serialize; + /// # use quick_xml::se::Serializer; + /// + /// #[derive(Debug, PartialEq, Serialize)] + /// struct Struct { + /// question: Option, + /// } + /// + /// let mut buffer = String::new(); + /// let mut ser = Serializer::new(&mut buffer); + /// ser.expand_empty_elements(true); + /// + /// let data = Struct { + /// question: None, + /// }; + /// + /// data.serialize(ser).unwrap(); + /// assert_eq!( + /// buffer, + /// "" + /// ); + /// ``` + pub fn expand_empty_elements(&mut self, expand: bool) -> &mut Self { + self.ser.expand_empty_elements = expand; + self + } + + /// Configure indent for a serializer + pub fn indent(&mut self, indent_char: char, indent_size: usize) -> &mut Self { + self.ser.indent = Indent::Owned(Indentation::new(indent_char as u8, indent_size)); + self + } + + /// Set the level of quoting used when writing texts + /// + /// Default: [`QuoteLevel::Minimal`] + pub fn set_quote_level(&mut self, level: QuoteLevel) -> &mut Self { + self.ser.level = level; + self + } + + /// Creates actual serializer or returns an error if root tag is not defined. + /// In that case `err` contains the name of type that cannot be serialized. + fn ser(self, err: &str) -> Result, DeError> { + if let Some(key) = self.root_tag { + Ok(ElementSerializer { ser: self.ser, key }) + } else { + Err(DeError::Unsupported( + format!("cannot serialize {} without defined root tag", err).into(), + )) + } + } + + /// Creates actual serializer using root tag or a specified `key` if root tag + /// is not defined. Returns an error if root tag is not defined and a `key` + /// does not conform [XML rules](XmlName::try_from) for names. + fn ser_name(self, key: &'static str) -> Result, DeError> { + Ok(self.ser.into_element_serializer(match self.root_tag { + Some(key) => key, + None => XmlName::try_from(key)?, + })) + } + + /// Get writer. + pub fn get_mut(&mut self) -> &mut W { + self.ser.writer + } +} + +impl<'w, 'r, W: std::io::Write> ser::Serializer for Serializer<'w, 'r, W> { + type Ok = (); + type Error = DeError; + + type SerializeSeq = ElementSerializer<'w, 'r, W>; + type SerializeTuple = ElementSerializer<'w, 'r, W>; + type SerializeTupleStruct = ElementSerializer<'w, 'r, W>; + type SerializeTupleVariant = Tuple<'w, 'r, W>; + type SerializeMap = Map<'w, 'r, W>; + type SerializeStruct = Struct<'w, 'r, W>; + type SerializeStructVariant = Struct<'w, 'r, W>; + + forward!(serialize_bool(bool)); + + forward!(serialize_i8(i8)); + forward!(serialize_i16(i16)); + forward!(serialize_i32(i32)); + forward!(serialize_i64(i64)); + + forward!(serialize_u8(u8)); + forward!(serialize_u16(u16)); + forward!(serialize_u32(u32)); + forward!(serialize_u64(u64)); + + serde_if_integer128! { + forward!(serialize_i128(i128)); + forward!(serialize_u128(u128)); + } + + forward!(serialize_f32(f32)); + forward!(serialize_f64(f64)); + + forward!(serialize_char(char)); + forward!(serialize_str(&str)); + forward!(serialize_bytes(&[u8])); + + fn serialize_none(self) -> Result { + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + self.ser("`()`")?.serialize_unit() + } + + fn serialize_unit_struct(self, name: &'static str) -> Result { + self.ser_name(name)?.serialize_unit_struct(name) + } + + fn serialize_unit_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + if variant == TEXT_KEY { + // We should write some text but we don't known what text to write + Err(DeError::Unsupported( + format!( + "cannot serialize enum unit variant `{}::$text` as text content value", + name + ) + .into(), + )) + } else { + let name = XmlName::try_from(variant)?; + self.ser.write_empty(name) + } + } + + fn serialize_newtype_struct( + self, + name: &'static str, + value: &T, + ) -> Result { + self.ser_name(name)?.serialize_newtype_struct(name, value) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + if variant == TEXT_KEY { + value.serialize(self.ser.into_simple_type_serializer())?; + Ok(()) + } else { + value.serialize(self.ser.try_into_element_serializer(variant)?) + } + } + + fn serialize_seq(self, len: Option) -> Result { + self.ser("sequence")?.serialize_seq(len) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.ser("unnamed tuple")?.serialize_tuple(len) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + self.ser_name(name)?.serialize_tuple_struct(name, len) + } + + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + if variant == TEXT_KEY { + self.ser + .into_simple_type_serializer() + .serialize_tuple_struct(name, len) + .map(Tuple::Text) + } else { + let ser = self.ser.try_into_element_serializer(variant)?; + ser.serialize_tuple_struct(name, len).map(Tuple::Element) + } + } + + fn serialize_map(self, len: Option) -> Result { + self.ser("map")?.serialize_map(len) + } + + fn serialize_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + self.ser_name(name)?.serialize_struct(name, len) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + if variant == TEXT_KEY { + Err(DeError::Unsupported( + format!( + "cannot serialize enum struct variant `{}::$text` as text content value", + name + ) + .into(), + )) + } else { + let ser = self.ser.try_into_element_serializer(variant)?; + ser.serialize_struct(name, len) + } + } +} diff --git a/src/se/io/content.rs b/src/se/io/content.rs new file mode 100644 index 00000000..ecfa637e --- /dev/null +++ b/src/se/io/content.rs @@ -0,0 +1,1445 @@ +use super::*; +use serde::ser::Serializer; + +macro_rules! write_primitive { + ($method:ident ( $ty:ty )) => { + #[inline] + fn $method(self, value: $ty) -> Result { + self.into_simple_type_serializer().$method(value)?; + Ok(()) + } + }; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer used to serialize content of an element. It does not write +/// surrounding tags. Unlike the [`ElementSerializer`], this serializer serializes +/// enums using variant names as tag names, i. e. as `...` +/// +/// This serializer does the following: +/// - numbers converted to a decimal representation and serialized as naked strings; +/// - booleans serialized ether as `"true"` or `"false"`; +/// - strings and characters are serialized as naked strings; +/// - `None` does not write anything; +/// - `Some` and newtypes are serialized as an inner type using the same serializer; +/// - units (`()`) and unit structs does not write anything; +/// - sequences, tuples and tuple structs are serialized without delimiters. +/// `[1, 2, 3]` would be serialized as `123` (if not using indent); +/// - structs and maps are not supported ([`DeError::Unsupported`] is returned); +/// - enums: +/// - unit variants are serialized as self-closed ``; +/// - newtype variants are serialized as inner value wrapped in `...`; +/// - tuple variants are serialized as sequences where each element is wrapped +/// in `...`; +/// - struct variants are serialized as a sequence of fields wrapped in +/// `...`. Each field is serialized recursively using +/// either [`ElementSerializer`], `ContentSerializer` (`$value` fields), or +/// [`SimpleTypeSerializer`] (`$text` fields). In particular, the empty struct +/// is serialized as ``; +/// +/// Usage of empty tags depends on the [`Self::expand_empty_elements`] setting. +/// +/// 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. Even worse, when serializing +/// with indent, sequence of strings become one big string with additional content +/// and it would be impossible to distinguish between content of the original +/// strings and inserted indent characters. +pub struct ContentSerializer<'w, 'i, W> { + pub writer: &'w mut W, + /// Defines which XML characters need to be escaped in text content + pub level: QuoteLevel, + /// Current indentation level. Note, that `Indent::None` means that there is + /// no indentation at all, but `write_indent == false` means only, that indent + /// writing is disabled in this instantiation of `ContentSerializer`, but + /// child serializers should have access to the actual state of indentation. + pub(super) indent: Indent<'i>, + /// If `true`, then current indent will be written before writing the content, + /// but only if content is not empty. + pub write_indent: bool, + // If `true`, then empty elements will be serialized as `` + // instead of ``. + pub expand_empty_elements: bool, + //TODO: add settings to disallow consequent serialization of primitives +} + +impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { + /// Turns this serializer into serializer of a text content + #[inline] + pub fn try_into_element_serializer( + self, + key: &'static str, + ) -> Result, DeError> { + Ok(self.into_element_serializer(XmlName::try_from(key)?)) + } + + /// Turns this serializer into serializer of a text content + #[inline] + pub(crate) fn into_element_serializer(self, key: XmlName<'i>) -> ElementSerializer<'w, 'i, W> { + ElementSerializer { ser: self, key } + } +} + +impl<'w, 'i, W: Write> ContentSerializer<'w, 'i, W> { + /// Turns this serializer into serializer of a text content + #[inline] + pub fn into_simple_type_serializer(self) -> SimpleTypeSerializer<'i, &'w mut W> { + //TODO: Customization point: choose between CDATA and Text representation + SimpleTypeSerializer { + writer: self.writer, + target: QuoteTarget::Text, + level: self.level, + indent: if self.write_indent { + self.indent + } else { + Indent::None + }, + } + } + + /// 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 { + ContentSerializer { + writer: self.writer, + level: self.level, + indent: self.indent.borrow(), + write_indent: self.write_indent, + expand_empty_elements: self.expand_empty_elements, + } + } + + /// Writes `name` as self-closed tag + #[inline] + pub(super) fn write_empty(mut self, name: XmlName) -> Result<(), DeError> { + self.write_indent()?; + if self.expand_empty_elements { + self.writer.write_char('<')?; + self.writer.write_str(name.0)?; + self.writer.write_str(">')?; + } else { + self.writer.write_str("<")?; + self.writer.write_str(name.0)?; + self.writer.write_str("/>")?; + } + Ok(()) + } + + /// Writes simple type content between `name` tags + pub(super) fn write_wrapped(mut self, name: XmlName, serialize: S) -> Result<(), DeError> + where + S: for<'a> FnOnce(SimpleTypeSerializer<'i, &'a mut W>) -> Result<&'a mut W, DeError>, + { + self.write_indent()?; + self.writer.write_char('<')?; + self.writer.write_str(name.0)?; + self.writer.write_char('>')?; + + let writer = serialize(self.into_simple_type_serializer())?; + + writer.write_str("')?; + Ok(()) + } + + pub(super) fn write_indent(&mut self) -> Result<(), DeError> { + if self.write_indent { + self.indent.write_io_indent(&mut self.writer)?; + self.write_indent = false; + } + Ok(()) + } +} + +impl<'w, 'i, W: Write> Serializer for ContentSerializer<'w, 'i, W> { + type Ok = (); + type Error = DeError; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Tuple<'w, 'i, W>; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Struct<'w, 'i, 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)); + + #[inline] + fn serialize_bytes(self, v: &[u8]) -> Result { + self.writer.write(v)?; + Ok(()) + } + + #[inline] + fn serialize_str(self, value: &str) -> Result { + if !value.is_empty() { + self.into_simple_type_serializer().serialize_str(value)?; + } + Ok(()) + } + + /// Does not write anything + #[inline] + fn serialize_none(self) -> Result { + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + /// Does not write anything + #[inline] + fn serialize_unit(self) -> Result { + Ok(()) + } + + /// Does not write anything + #[inline] + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(()) + } + + /// If `variant` is a special `$text` variant, then do nothing, otherwise + /// checks `variant` for XML name validity and writes ``. + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + if variant == TEXT_KEY { + Ok(()) + } else { + let name = XmlName::try_from(variant)?; + self.write_empty(name) + } + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + /// If `variant` is a special `$text` variant, then writes `value` as a `xs:simpleType`, + /// otherwise checks `variant` for XML name validity and writes `value` as a new + /// `` element. + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result { + if variant == TEXT_KEY { + value.serialize(self.into_simple_type_serializer())?; + Ok(()) + } else { + value.serialize(self.into_element_serializer(XmlName::try_from(variant)?)) + } + } + + #[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) + } + + /// Serializes variant as a tuple with name `variant`, producing + /// + /// ```xml + /// + /// + /// + /// + /// ``` + #[inline] + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + if variant == TEXT_KEY { + self.into_simple_type_serializer() + .serialize_tuple_struct(name, len) + .map(Tuple::Text) + } else { + let ser = self.into_element_serializer(XmlName::try_from(variant)?); + ser.serialize_tuple_struct(name, len).map(Tuple::Element) + } + } + + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + "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(), + )) + } + + /// Serializes variant as an element with name `variant`, producing + /// + /// ```xml + /// + /// + /// + /// ``` + /// + /// If struct has no fields which is represented by nested elements or a text, + /// it may be serialized as self-closed element ``. + #[inline] + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + if variant == TEXT_KEY { + Err(DeError::Unsupported( + format!("cannot serialize `$text` struct variant of `{}` enum", name).into(), + )) + } else { + let ser = self.into_element_serializer(XmlName::try_from(variant)?); + ser.serialize_struct(name, len) + } + } +} + +impl<'w, 'i, W: Write> SerializeSeq for ContentSerializer<'w, 'i, W> { + type Ok = (); + type Error = DeError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + value.serialize(self.new_seq_element_serializer())?; + // Write indent for next element + self.write_indent = true; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(()) + } +} + +impl<'w, 'i, W: Write> SerializeTuple for ContentSerializer<'w, 'i, W> { + type Ok = (); + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + +impl<'w, 'i, W: Write> SerializeTupleStruct for ContentSerializer<'w, 'i, W> { + type Ok = (); + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::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), + } + + /// Struct with a special `$text` field + #[derive(Debug, Serialize, PartialEq)] + pub struct Text { + pub before: &'static str, + #[serde(rename = "$text")] + pub content: T, + pub after: &'static str, + } + + /// Struct with a special `$value` field + #[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 { + /// Struct variant with a special `$text` field + Text { + before: &'static str, + #[serde(rename = "$text")] + content: T, + after: &'static str, + }, + /// Struct variant with a special `$value` field + 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:expr) => { + #[test] + fn $name() { + let mut buffer = Vec::new(); + let ser = ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + expand_empty_elements: false, + }; + + $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected.as_bytes()); + } + }; + } + + /// 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 = Vec::new(); + let ser = ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + expand_empty_elements: false, + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `Err({}({}))`, but got `{:?}`", + 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_field { + 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\ + "); + } + + /// `$text` field inside a struct variant of an enum + mod enum_with_text_field { + 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("cannot serialize enum newtype variant `Enum::Newtype` as 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\n\r 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("cannot serialize enum tuple variant `Enum::Tuple` as 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("cannot serialize map as text content value")); + err!(struct_: + SpecialEnum::Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); + } + + /// `$value` field inside a struct variant of an enum + mod enum_with_value_field { + 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\n\r spaces", 3usize) + => "<"&'>\ + with\t\n\r 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; + + 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"#); + } + } + + mod with_indent { + use super::Struct; + use super::*; + use crate::writer::Indentation; + 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 mut buffer = Vec::new(); + let ser = ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + expand_empty_elements: false, + }; + + $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected.as_bytes()); + } + }; + } + + /// 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 = Vec::new(); + let ser = ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + expand_empty_elements: false, + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `Err({}({}))`, but got `{:?}`", + 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: '"' => """); + //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(Enum::Unit) => ""); + + 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 other that indent! + serialize_as!(seq: vec![1, 2, 3] + => "1\n\ + 2\n\ + 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\r\n spaces", 3usize) + => "<"&'>\n\ + with\t\r\n spaces\n\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\n\ + 42"); + serialize_as!(enum_tuple: Enum::Tuple("first", 42) + => "first\n\ + 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) } + => "\n \ + answer\n \ + 42\n \ + 42\n\ + "); + + /// Special field name `$text` should be serialized as text content + mod text_field { + 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", + } + => "\n \ + answer\n \ + 42 42\n \ + answer\n\ + "); + } + + /// `$text` field inside a struct variant of an enum + mod enum_with_text_field { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + 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("cannot serialize enum newtype variant `Enum::Newtype` as 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\n\r 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("cannot serialize enum tuple variant `Enum::Tuple` as 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("cannot serialize map as text content value")); + err!(struct_: + SpecialEnum::Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + SpecialEnum::Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); + } + + /// `$value` field inside a struct variant of an enum + mod enum_with_value_field { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + SpecialEnum::Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + 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] => "1\n 2\n 3"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'>\n \ + with\t\n\r spaces\n \ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\n \ + 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) } + => "\n \ + answer\n \ + 42\n \ + 42\n \ + "); + } + + 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 } + => "\n \ + 42\n\ + "); + serialize_as!(enum_after: Enum::AttributesAfter { key: "answer", val: 42 } + => "\n \ + answer\n\ + "); + } + } +} diff --git a/src/se/io/element.rs b/src/se/io/element.rs new file mode 100644 index 00000000..93475ad7 --- /dev/null +++ b/src/se/io/element.rs @@ -0,0 +1,2067 @@ +use super::*; +use serde::ser::Serializer; +use std::io::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. Unlike the [`ContentSerializer`], +/// this serializer never uses variant names of enum variants, and because of that +/// it is unable to serialize any enum values, except unit variants. +/// +/// This serializer is used for an ordinary fields in structs, which are not special +/// fields named `$text` ([`TEXT_KEY`]) or `$value` ([`VALUE_KEY`]). `$text` field +/// should be serialized using [`SimpleTypeSerializer`] and `$value` field should be +/// serialized using [`ContentSerializer`]. +/// +/// This serializer does the following: +/// - numbers converted to a decimal representation and serialized as `value`; +/// - booleans serialized ether as `true` or `false`; +/// - strings and characters are serialized as `value`. In particular, +/// an empty string is serialized as ``; +/// - `None` is serialized as ``; +/// - `Some` and newtypes are serialized as an inner type using the same serializer; +/// - units (`()`) and unit structs are serialized as ``; +/// - sequences, tuples and tuple structs are serialized as repeated `` tag. +/// In particular, empty sequence is serialized to nothing; +/// - structs are serialized as a sequence of fields wrapped in a `` tag. Each +/// field is serialized recursively using either `ElementSerializer`, [`ContentSerializer`] +/// (`$value` fields), or [`SimpleTypeSerializer`] (`$text` fields). +/// In particular, the empty struct is serialized as ``; +/// - maps are serialized as a sequence of entries wrapped in a `` tag. If key is +/// serialized to a special name, the same rules as for struct fields are applied. +/// In particular, the empty map is serialized as ``; +/// - enums: +/// - unit variants are serialized as `variant`; +/// - other variants are not supported ([`DeError::Unsupported`] is returned); +/// +/// Usage of empty tags depends on the [`ContentSerializer::expand_empty_elements`] setting. +pub struct ElementSerializer<'w, 'k, W> { + /// The inner serializer that contains the settings and mostly do the actual work + pub ser: ContentSerializer<'w, 'k, W>, + /// Tag name used to wrap serialized types except enum variants which uses the variant name + pub(super) key: XmlName<'k>, +} + +impl<'w, 'k, W: super::Write> Serializer for ElementSerializer<'w, 'k, W> { + type Ok = (); + type Error = DeError; + + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Impossible; + type SerializeMap = Map<'w, 'k, W>; + type SerializeStruct = Struct<'w, 'k, W>; + type SerializeStructVariant = Struct<'w, '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) + } + + /// Writes a tag with name [`Self::key`] and content of unit variant inside. + /// If variant is a special `$text` value, then empty tag `` is written. + /// Otherwise a `variant` is written. + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + if variant == TEXT_KEY { + self.ser.write_empty(self.key) + } else { + self.ser.write_wrapped(self.key, |ser| { + ser.serialize_unit_variant(name, variant_index, variant) + }) + } + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + /// Always returns [`DeError::Unsupported`]. Newtype variants can be serialized + /// only in `$value` fields, which is serialized using [`ContentSerializer`]. + #[inline] + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum newtype variant `{}::{}`", + name, variant + ) + .into(), + )) + } + + #[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) + } + + /// Always returns [`DeError::Unsupported`]. Tuple variants can be serialized + /// only in `$value` fields, which is serialized using [`ContentSerializer`]. + #[inline] + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum tuple variant `{}::{}`", + name, variant + ) + .into(), + )) + } + + 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.write_indent()?; + self.ser.indent.increase(); + + self.ser.writer.write_char('<')?; + self.ser.writer.write_str(self.key.0)?; + Ok(Struct { + ser: self, + children: Vec::new(), + }) + } + + /// Always returns [`DeError::Unsupported`]. Struct variants can be serialized + /// only in `$value` fields, which is serialized using [`ContentSerializer`]. + #[inline] + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum struct variant `{}::{}`", + name, variant + ) + .into(), + )) + } +} + +impl<'w, 'k, W: Write> SerializeSeq for ElementSerializer<'w, 'k, W> { + type Ok = (); + 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, + })?; + // Write indent for the next element + self.ser.write_indent = true; + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(()) + } +} + +impl<'w, 'k, W: Write> SerializeTuple for ElementSerializer<'w, 'k, W> { + type Ok = (); + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + +impl<'w, 'k, W: Write> SerializeTupleStruct for ElementSerializer<'w, 'k, W> { + type Ok = (); + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A serializer for tuple variants. Tuples can be serialized in two modes: +/// - wrapping each tuple field into a tag +/// - without wrapping, fields are delimited by a space +pub enum Tuple<'w, 'k, W: Write> { + /// Serialize each tuple field as an element + Element(ElementSerializer<'w, 'k, W>), + /// Serialize tuple as an `xs:list`: space-delimited content of fields + Text(SimpleSeq<'k, &'w mut W>), +} + +impl<'w, 'k, W: Write> SerializeTupleVariant for Tuple<'w, 'k, W> { + type Ok = (); + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + match self { + Self::Element(ser) => SerializeTuple::serialize_element(ser, value), + Self::Text(ser) => SerializeTuple::serialize_element(ser, value), + } + } + + #[inline] + fn end(self) -> Result { + match self { + Self::Element(ser) => SerializeTuple::end(ser), + Self::Text(ser) => SerializeTuple::end(ser).map(|_| ()), + } + } +} + +// //////////////////////////////////////////////////////////////////////////////////////////////////// + +/// 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<'w, 'k, W> { + ser: ElementSerializer<'w, '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: Vec, +} + +impl<'w, 'k, W: super::Write> Struct<'w, '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, + indent: Indent::None, + })?; + 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 [`VALUE_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, + indent: self.ser.ser.indent.borrow(), + write_indent: true, + expand_empty_elements: self.ser.ser.expand_empty_elements, + }; + + if key == TEXT_KEY { + value.serialize(TextSerializer(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<'w, 'k, W: super::Write> SerializeStruct for Struct<'w, 'k, W> { + type Ok = (); + 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 { + self.ser.ser.indent.decrease(); + + if self.children.is_empty() { + if self.ser.ser.expand_empty_elements { + self.ser.ser.writer.write_str(">')?; + } else { + self.ser.ser.writer.write_str("/>")?; + } + } else { + self.ser.ser.writer.write_char('>')?; + self.ser.ser.writer.write(&self.children)?; + + self.ser + .ser + .indent + .write_io_indent(&mut self.ser.ser.writer)?; + + self.ser.ser.writer.write_str("')?; + } + Ok(()) + } +} + +impl<'w, 'k, W: Write> SerializeStructVariant for Struct<'w, 'k, W> { + type Ok = (); + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeStruct::serialize_field(self, key, value) + } + + #[inline] + fn end(self) -> Result { + SerializeStruct::end(self) + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct Map<'w, 'k, W: Write> { + ser: Struct<'w, 'k, W>, + /// Key, serialized by `QNameSerializer` if consumer uses `serialize_key` + + /// `serialize_value` calls instead of `serialize_entry` + key: Option, +} + +impl<'w, 'k, W: Write> Map<'w, 'k, W> { + fn make_key(&mut self, key: &T) -> Result + where + T: ?Sized + Serialize, + { + key.serialize(crate::se::key::QNameSerializer { + writer: String::new(), + }) + } +} + +impl<'w, 'k, W: Write> SerializeMap for Map<'w, 'k, W> { + type Ok = (); + 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::{Indent, 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 mut buffer = Vec::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + expand_empty_elements: false, + }, + key: XmlName("root"), + }; + + $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected.as_bytes()); + } + }; + } + + /// 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 = Vec::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + expand_empty_elements: false, + }, + key: XmlName("root"), + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `Err({}({}))`, but got `{:?}`", + 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 => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + serialize_as!(newtype: Newtype(42) => "42"); + err!(enum_newtype: Enum::Newtype(42) + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype`")); + + serialize_as!(seq: vec![1, 2, 3] + => "1\ + 2\ + 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'>\ + with\t\n\r spaces\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\ + 42"); + err!(enum_tuple: Enum::Tuple("first", 42) + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple`")); + + 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\ + "); + err!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Struct`")); + + /// Special field name `$text` should be serialized as text content. + /// Sequences serialized as an `xs:list` content + mod text_field { + 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("cannot serialize enum newtype variant `Enum::Newtype` as 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\n\r 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("cannot serialize enum tuple variant `Enum::Tuple` as 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("cannot serialize map as text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as 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("cannot serialize enum newtype variant `Enum::Newtype` as 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\n\r 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("cannot serialize enum tuple variant `Enum::Tuple` as 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("cannot serialize map as text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as 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_field { + 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\n\r spaces", 3usize) + => "<"&'>\ + with\t\n\r 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\n\r spaces", 3usize) + => "<"&'>\ + with\t\n\r 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\ + "); + } + } + + 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"#); + + err!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Attributes`")); + + /// 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\ + "); + } + } + + mod with_indent { + use super::*; + use crate::se::content::tests::Struct; + use crate::writer::Indentation; + 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 mut buffer = Vec::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + expand_empty_elements: false, + }, + key: XmlName("root"), + }; + + $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected.as_bytes()); + } + }; + } + + /// 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 = Vec::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::Owned(Indentation::new(b' ', 2)), + write_indent: false, + expand_empty_elements: false, + }, + key: XmlName("root"), + }; + + match $data.serialize(ser).unwrap_err() { + DeError::$kind(e) => assert_eq!(e, $reason), + e => panic!( + "Expected `Err({}({}))`, but got `{:?}`", + 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: '"' => """); + //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::<&str>::None => ""); + serialize_as!(option_some: Some("non-escaped string") => "non-escaped string"); + serialize_as!(option_some_empty: Some("") => ""); + + serialize_as!(unit: () => ""); + serialize_as!(unit_struct: Unit => ""); + serialize_as!(unit_struct_escaped: UnitEscaped => ""); + + serialize_as!(enum_unit: Enum::Unit => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + + serialize_as!(newtype: Newtype(42) => "42"); + err!(enum_newtype: Enum::Newtype(42) + => Unsupported("cannot serialize enum newtype variant `Enum::Newtype`")); + + serialize_as!(seq: vec![1, 2, 3] + => "1\n\ + 2\n\ + 3"); + serialize_as!(seq_empty: Vec::::new() => ""); + serialize_as!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'>\n\ + with\t\n\r spaces\n\ + 3"); + serialize_as!(tuple_struct: Tuple("first", 42) + => "first\n\ + 42"); + err!(enum_tuple: Enum::Tuple("first", 42) + => Unsupported("cannot serialize enum tuple variant `Enum::Tuple`")); + + serialize_as!(map: BTreeMap::from([("_1", 2), ("_3", 4)]) + => "\n \ + <_1>2\n \ + <_3>4\n\ + "); + serialize_as!(struct_: Struct { key: "answer", val: (42, 42) } + => "\n \ + answer\n \ + 42\n \ + 42\n\ + "); + err!(enum_struct: Enum::Struct { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Struct`")); + + /// Special field name `$text` should be serialized as text content. + /// Sequences serialized as an `xs:list` content + mod text_field { + 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!("\n ", $expected,"\n")); + }; + } + + 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("cannot serialize enum newtype variant `Enum::Newtype` as 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\n\r 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("cannot serialize enum tuple variant `Enum::Tuple` as 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("cannot serialize map as text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as text content value")); + } + + /// `$text` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! text { + ($name:ident: $data:expr) => { + serialize_as!($name: + Text { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Text { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + 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("cannot serialize enum newtype variant `Enum::Newtype` as 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\n\r 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("cannot serialize enum tuple variant `Enum::Tuple` as 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("cannot serialize map as text content value")); + err!(struct_: + Text { + before: "answer", + content: Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize struct `Struct` as text content value")); + err!(enum_struct: + Text { + before: "answer", + content: Enum::Struct { key: "answer", val: (42, 42) }, + after: "answer", + } + => Unsupported("cannot serialize enum struct variant `Enum::Struct` as 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_field { + 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!("\n ", $expected,"\n")); + }; + } + + 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"); + + value!(seq: vec![1, 2, 3] => "1\n 2\n 3"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'>\n \ + with\t\n\r spaces\n \ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\n \ + 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) } + => "\n \ + answer\n \ + 42\n \ + 42\n \ + "); + } + + /// `$value` field inside a struct + mod struct_ { + use super::*; + use pretty_assertions::assert_eq; + + macro_rules! value { + ($name:ident: $data:expr) => { + serialize_as!($name: + Value { + before: "answer", + content: $data, + after: "answer", + } + => "\n \ + answer\n \ + answer\n\ + "); + }; + ($name:ident: $data:expr => $expected:literal) => { + serialize_as!($name: + Value { + before: "answer", + content: $data, + after: "answer", + } + => concat!( + "\n answer\n ", + $expected, + "\n answer\n", + )); + }; + } + + 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] => "1\n 2\n 3"); + value!(seq_empty: Vec::::new()); + value!(tuple: ("<\"&'>", "with\t\n\r spaces", 3usize) + => "<"&'>\n \ + with\t\n\r spaces\n \ + 3"); + value!(tuple_struct: Tuple("first", 42) => "first\n 42"); + value!(enum_tuple: Enum::Tuple("first", 42) + => "first\n \ + 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) } + => "\n \ + answer\n \ + 42\n \ + 42\n \ + "); + } + } + + 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)]) + => "\n \ + 2\n\ + "); + + serialize_as!(struct_: Attributes { key: "answer", val: (42, 42) } + => r#""#); + serialize_as!(struct_before: AttributesBefore { key: "answer", val: 42 } + => "\n \ + 42\n\ + "); + serialize_as!(struct_after: AttributesAfter { key: "answer", val: 42 } + => "\n \ + answer\n\ + "); + + err!(enum_: Enum::Attributes { key: "answer", val: (42, 42) } + => Unsupported("cannot serialize enum struct variant `Enum::Attributes`")); + + /// 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("a"), + b: Some("b") + } + => 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 } + => "\n \ + \n\ + "); + serialize_as!(some_empty_str: + OptionalElements { + a: Some(""), + b: Some("") + } + => "\n \ + \n \ + \n\ + "); + serialize_as!(some_non_empty: + OptionalElements { + a: Some("a"), + b: Some("b") + } + => "\n \ + a\n \ + b\n\ + "); + } + } + + mod expand_empty_elements { + use super::*; + 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 mut buffer = Vec::new(); + let ser = ElementSerializer { + ser: ContentSerializer { + writer: &mut buffer, + level: QuoteLevel::Full, + indent: Indent::None, + write_indent: false, + expand_empty_elements: true, + }, + key: XmlName("root"), + }; + + $data.serialize(ser).unwrap(); + assert_eq!(buffer, $expected.as_bytes()); + } + }; + } + + serialize_as!(option_some_empty: Some("") => ""); + 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 => "Unit"); + serialize_as!(enum_unit_escaped: Enum::UnitEscaped => "<"&'>"); + } +} diff --git a/src/se/io/simple_type.rs b/src/se/io/simple_type.rs new file mode 100644 index 00000000..7efd382b --- /dev/null +++ b/src/se/io/simple_type.rs @@ -0,0 +1,312 @@ +use super::super::simple_type::*; +use super::*; + +/// Implements writing primitives to the underlying writer. +/// Implementor must provide `write_str(self, &str) -> Result<(), DeError>` method +macro_rules! write_primitive { + ($method:ident ( $ty:ty )) => { + fn $method(mut self, value: $ty) -> Result { + self.write_str(&value.to_string())?; + Ok(self.writer) + } + }; + () => { + fn serialize_bool(mut self, value: bool) -> Result { + self.write_str(if value { "true" } else { "false" })?; + Ok(self.writer) + } + + 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)); + + fn serialize_char(self, value: char) -> Result { + self.serialize_str(&value.to_string()) + } + + fn serialize_bytes(mut self, value: &[u8]) -> Result { + self.writer.write(&value)?; + Ok(self.writer) + } + + fn serialize_none(self) -> Result { + Ok(self.writer) + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + }; +} + +/// A serializer for a values representing XSD [simple types], which used in: +/// - attribute values (`<... ...="value" ...>`) +/// - text content (`<...>text`) +/// - CDATA content (`<...>`) +/// +/// [simple types]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition +pub struct SimpleTypeSerializer<'i, W: Write> { + /// Writer to which this serializer writes content + pub writer: W, + /// Target for which element is serializing. Affects additional characters to escape. + pub target: QuoteTarget, + /// Defines which XML characters need to be escaped + pub level: QuoteLevel, + /// Indent that should be written before the content if content is not an empty string + pub(crate) indent: Indent<'i>, +} + +impl<'i, W: Write> SimpleTypeSerializer<'i, W> { + fn write_str(&mut self, value: &str) -> Result<(), DeError> { + self.indent.write_io_indent(&mut self.writer)?; + Ok(self.writer.write_str(value)?) + } +} + +impl<'i, W: Write> ser::Serializer for SimpleTypeSerializer<'i, W> { + type Ok = W; + type Error = DeError; + + type SerializeSeq = SimpleSeq<'i, W>; + type SerializeTuple = SimpleSeq<'i, W>; + type SerializeTupleStruct = SimpleSeq<'i, W>; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + write_primitive!(); + + fn serialize_str(mut self, value: &str) -> Result { + if !value.is_empty() { + self.write_str(&super::simple_type::escape_list( + value, + self.target, + self.level, + ))?; + } + Ok(self.writer) + } + + /// Does not write anything + fn serialize_unit(self) -> Result { + Ok(self.writer) + } + + /// Does not write anything + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(self.writer) + } + + /// We cannot store both a variant discriminant and a variant value, + /// so serialization of enum newtype variant returns `Err(Unsupported)` + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + Err(DeError::Unsupported( + format!("cannot serialize enum newtype variant `{}::{}` as an attribute or text content value", name, variant).into(), + )) + } + + #[inline] + fn serialize_seq(self, _len: Option) -> Result { + Ok(SimpleSeq { + writer: self.writer, + target: self.target, + level: self.level, + indent: self.indent, + is_empty: true, + }) + } + + #[inline] + fn serialize_tuple(self, _len: usize) -> Result { + self.serialize_seq(None) + } + + #[inline] + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + self.serialize_seq(None) + } + + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("cannot serialize enum tuple variant `{}::{}` as an attribute or text content value", name, variant).into(), + )) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + "cannot serialize map as an attribute or text content value".into(), + )) + } + + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize struct `{}` as an attribute or text content value", + name + ) + .into(), + )) + } + + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("cannot serialize enum struct variant `{}::{}` as an attribute or text content value", name, variant).into(), + )) + } +} + +/// Serializer for a sequence of atomic values delimited by space +pub struct SimpleSeq<'i, W: Write> { + writer: W, + target: QuoteTarget, + level: QuoteLevel, + /// Indent that should be written before the content if content is not an empty string + indent: Indent<'i>, + /// If `true`, nothing was written yet to the `writer` + is_empty: bool, +} + +impl<'i, W: Write> SerializeSeq for SimpleSeq<'i, W> { + type Ok = W; + type Error = DeError; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + // Write indent for the first element and delimiter for others + let indent = if self.is_empty { + Some(self.indent.borrow()) + } else { + None + }; + if value.serialize(super::simple_type::AtomicSerializer { + writer: FmtWriter::ref_cast_mut(&mut self.writer), + target: self.target, + level: self.level, + indent, + })? { + self.is_empty = false; + } + Ok(()) + } + + #[inline] + fn end(self) -> Result { + Ok(self.writer) + } +} + +impl<'i, W: Write> SerializeTuple for SimpleSeq<'i, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + +impl<'i, W: Write> SerializeTupleStruct for SimpleSeq<'i, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + +impl<'i, W: Write> SerializeTupleVariant for SimpleSeq<'i, W> { + type Ok = W; + type Error = DeError; + + #[inline] + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + SerializeSeq::serialize_element(self, value) + } + + #[inline] + fn end(self) -> Result { + SerializeSeq::end(self) + } +} + +// //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/se/io/text.rs b/src/se/io/text.rs new file mode 100644 index 00000000..fb1fb547 --- /dev/null +++ b/src/se/io/text.rs @@ -0,0 +1,185 @@ +use super::*; + +// //////////////////////////////////////////////////////////////////////////////////////////////////// + +macro_rules! write_primitive_text { + ($method:ident ( $ty:ty )) => { + #[inline] + fn $method(self, value: $ty) -> Result { + self.0.$method(value) + } + }; +} + +/// A serializer used to serialize a `$text` field of a struct or map. +/// +/// This serializer a very similar to [`SimpleTypeSerializer`], but different +/// from it in how it processes unit enum variants. Unlike [`SimpleTypeSerializer`] +/// this serializer does not write anything for the unit variant. +pub struct TextSerializer<'i, W: Write>(pub SimpleTypeSerializer<'i, W>); + +impl<'i, W: Write> ser::Serializer for TextSerializer<'i, W> { + type Ok = W; + type Error = DeError; + + type SerializeSeq = SimpleSeq<'i, W>; + type SerializeTuple = SimpleSeq<'i, W>; + type SerializeTupleStruct = SimpleSeq<'i, W>; + type SerializeTupleVariant = SimpleSeq<'i, W>; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; + + write_primitive_text!(serialize_bool(bool)); + + write_primitive_text!(serialize_i8(i8)); + write_primitive_text!(serialize_i16(i16)); + write_primitive_text!(serialize_i32(i32)); + write_primitive_text!(serialize_i64(i64)); + + write_primitive_text!(serialize_u8(u8)); + write_primitive_text!(serialize_u16(u16)); + write_primitive_text!(serialize_u32(u32)); + write_primitive_text!(serialize_u64(u64)); + + serde_if_integer128! { + write_primitive_text!(serialize_i128(i128)); + write_primitive_text!(serialize_u128(u128)); + } + + write_primitive_text!(serialize_f32(f32)); + write_primitive_text!(serialize_f64(f64)); + + write_primitive_text!(serialize_char(char)); + write_primitive_text!(serialize_str(&str)); + write_primitive_text!(serialize_bytes(&[u8])); + + #[inline] + fn serialize_none(self) -> Result { + self.0.serialize_none() + } + + fn serialize_some(self, value: &T) -> Result { + value.serialize(self) + } + + #[inline] + fn serialize_unit(self) -> Result { + self.0.serialize_unit() + } + + #[inline] + fn serialize_unit_struct(self, name: &'static str) -> Result { + self.0.serialize_unit_struct(name) + } + + #[inline] + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result { + if variant == TEXT_KEY { + Ok(self.0.writer) + } else { + self.0.serialize_unit_variant(name, variant_index, variant) + } + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result { + value.serialize(self) + } + + #[inline] + fn serialize_newtype_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _value: &T, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum newtype variant `{}::{}` as text content value", + name, variant + ) + .into(), + )) + } + + #[inline] + fn serialize_seq(self, len: Option) -> Result { + self.0.serialize_seq(len) + } + + #[inline] + fn serialize_tuple(self, len: usize) -> Result { + self.0.serialize_tuple(len) + } + + #[inline] + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result { + self.0.serialize_tuple_struct(name, len) + } + + #[inline] + fn serialize_tuple_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum tuple variant `{}::{}` as text content value", + name, variant + ) + .into(), + )) + } + + #[inline] + fn serialize_map(self, _len: Option) -> Result { + Err(DeError::Unsupported( + "cannot serialize map as text content value".into(), + )) + } + + #[inline] + fn serialize_struct( + self, + name: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!("cannot serialize struct `{}` as text content value", name).into(), + )) + } + + #[inline] + fn serialize_struct_variant( + self, + name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + Err(DeError::Unsupported( + format!( + "cannot serialize enum struct variant `{}::{}` as text content value", + name, variant + ) + .into(), + )) + } +} diff --git a/src/se/mod.rs b/src/se/mod.rs index 719c89fa..c436a097 100644 --- a/src/se/mod.rs +++ b/src/se/mod.rs @@ -1,5 +1,7 @@ //! Module to handle custom serde `Serializer` +pub mod io; + /// Implements writing primitives to the underlying writer. /// Implementor must provide `write_str(self, &str) -> Result<(), DeError>` method macro_rules! write_primitive { @@ -366,7 +368,7 @@ const fn is_xml11_name_char(ch: char) -> bool { /// Helper struct to self-defense from errors #[derive(Clone, Copy, Debug, PartialEq)] -pub(self) struct XmlName<'n>(&'n str); +pub(crate) struct XmlName<'n>(&'n str); impl<'n> XmlName<'n> { /// Checks correctness of the XML name according to [XML 1.1 specification] @@ -612,6 +614,11 @@ impl<'w, 'r, W: Write> Serializer<'w, 'r, W> { }, }) } + + /// Get writer. + pub fn get_mut(&mut self) -> &mut W { + self.ser.writer + } } impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> { diff --git a/src/se/simple_type.rs b/src/se/simple_type.rs index 2ccb583f..2d06e500 100644 --- a/src/se/simple_type.rs +++ b/src/se/simple_type.rs @@ -26,7 +26,7 @@ pub enum QuoteTarget { /// Escapes atomic value that could be part of a `xs:list`. All whitespace characters /// additionally escaped -fn escape_item(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow { +pub(crate) fn escape_item(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow { use QuoteLevel::*; use QuoteTarget::*; @@ -95,7 +95,7 @@ fn escape_item(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow } /// Escapes XSD simple type value -fn escape_list(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow { +pub(crate) fn escape_list(value: &str, target: QuoteTarget, level: QuoteLevel) -> Cow { use QuoteLevel::*; use QuoteTarget::*; @@ -402,7 +402,7 @@ impl<'i, W: Write> Serializer for AtomicSerializer<'i, W> { /// - CDATA content (`<...>`) /// /// [simple types]: https://www.w3.org/TR/xmlschema11-1/#Simple_Type_Definition -pub struct SimpleTypeSerializer<'i, W: Write> { +pub struct SimpleTypeSerializer<'i, W> { /// Writer to which this serializer writes content pub writer: W, /// Target for which element is serializing. Affects additional characters to escape. diff --git a/tests/async-tokio.rs b/tests/async-tokio.rs index b525dfbf..470f187e 100644 --- a/tests/async-tokio.rs +++ b/tests/async-tokio.rs @@ -1,10 +1,12 @@ +use std::io::Cursor; use std::iter; use pretty_assertions::assert_eq; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event::*}; use quick_xml::name::QName; use quick_xml::reader::Reader; -use tokio::io::BufReader; +use quick_xml::utils::Bytes; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; // Import `small_buffers_tests!` #[macro_use] @@ -88,6 +90,48 @@ mod read_to_end { } } +#[tokio::test] +async fn issue623() { + let mut buf = Vec::new(); + let mut reader = Reader::from_reader(Cursor::new( + b" + + _binary << data&> + + ", + )); + reader.config_mut().trim_text(true); + + assert_eq!( + ( + reader.read_event_into_async(&mut buf).await.unwrap(), + reader.buffer_position() + ), + (Start(BytesStart::new("AppendedData")), 23) + ); + + let mut inner = reader.stream(); + // Read to start of data marker + inner.read_until(b'_', &mut buf).await.unwrap(); + + // Read binary data. We somehow should known its size + let mut binary = [0u8; 16]; + inner.read_exact(&mut binary).await.unwrap(); + assert_eq!(Bytes(&binary), Bytes(b"binary << data&>")); + assert_eq!(inner.offset(), 53); + assert_eq!(reader.buffer_position(), 53); + + assert_eq!( + ( + reader.read_event_into_async(&mut buf).await.unwrap(), + reader.buffer_position() + ), + (End(BytesEnd::new("AppendedData")), 77) + ); + + assert_eq!(reader.read_event_into_async(&mut buf).await.unwrap(), Eof); +} + /// Regression test for https://github.com/tafia/quick-xml/issues/751 /// /// Actually, that error was not found in async reader, but we would to test it as well. diff --git a/tests/issues.rs b/tests/issues.rs index c14f09c6..d35a5990 100644 --- a/tests/issues.rs +++ b/tests/issues.rs @@ -2,7 +2,7 @@ //! //! Name each module / test as `issue` and keep sorted by issue number -use std::io::BufReader; +use std::io::{BufRead, BufReader, Cursor, Read}; use std::iter; use std::sync::mpsc; @@ -10,6 +10,9 @@ use quick_xml::errors::{Error, IllFormedError, SyntaxError}; use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event}; use quick_xml::name::QName; use quick_xml::reader::Reader; +use quick_xml::utils::Bytes; + +use pretty_assertions::assert_eq; /// Regression test for https://github.com/tafia/quick-xml/issues/94 #[test] @@ -258,6 +261,89 @@ fn issue622() { } } +/// Regression test for https://github.com/tafia/quick-xml/issues/623 +mod issue623 { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn borrowed() { + let mut reader = Reader::from_str( + " + + _binary << data&> + + ", + ); + reader.config_mut().trim_text(true); + + assert_eq!( + (reader.read_event().unwrap(), reader.buffer_position()), + (Event::Start(BytesStart::new("AppendedData")), 27) + ); + + let mut inner = reader.stream(); + // Read to start of data marker + inner.read_until(b'_', &mut Vec::new()).unwrap(); + + // Read binary data. We somehow should known its size + let mut binary = [0u8; 16]; + inner.read_exact(&mut binary).unwrap(); + assert_eq!(Bytes(&binary), Bytes(b"binary << data&>")); + assert_eq!(inner.offset(), 61); + assert_eq!(reader.buffer_position(), 61); + + assert_eq!( + (reader.read_event().unwrap(), reader.buffer_position()), + (Event::End(BytesEnd::new("AppendedData")), 89) + ); + + assert_eq!(reader.read_event().unwrap(), Event::Eof); + } + + #[test] + fn buffered() { + let mut buf = Vec::new(); + let mut reader = Reader::from_reader(Cursor::new( + b" + + _binary << data&> + + ", + )); + reader.config_mut().trim_text(true); + + assert_eq!( + ( + reader.read_event_into(&mut buf).unwrap(), + reader.buffer_position() + ), + (Event::Start(BytesStart::new("AppendedData")), 27) + ); + + let mut inner = reader.stream(); + // Read to start of data marker + inner.read_until(b'_', &mut buf).unwrap(); + + // Read binary data. We somehow should known its size + let mut binary = [0u8; 16]; + inner.read_exact(&mut binary).unwrap(); + assert_eq!(Bytes(&binary), Bytes(b"binary << data&>")); + assert_eq!(inner.offset(), 61); + assert_eq!(reader.buffer_position(), 61); + + assert_eq!( + ( + reader.read_event_into(&mut buf).unwrap(), + reader.buffer_position() + ), + (Event::End(BytesEnd::new("AppendedData")), 89) + ); + + assert_eq!(reader.read_event_into(&mut buf).unwrap(), Event::Eof); + } +} + /// Regression test for https://github.com/tafia/quick-xml/issues/706 #[test] fn issue706() { diff --git a/tests/reader.rs b/tests/reader.rs index 2bc27e57..28791679 100644 --- a/tests/reader.rs +++ b/tests/reader.rs @@ -15,6 +15,13 @@ small_buffers_tests!( read_event_into: std::io::BufReader<_> ); +#[test] +fn test_text() { + let mut r = Reader::from_str(" text "); + + assert_eq!(r.read_event().unwrap(), Text(BytesText::new(" text "))); +} + #[test] fn test_start_end() { let mut r = Reader::from_str(""); diff --git a/tests/serde-se.rs b/tests/serde-se.rs index 39f6e66a..a46ab99b 100644 --- a/tests/serde-se.rs +++ b/tests/serde-se.rs @@ -88,7 +88,7 @@ enum ExternallyTagged { /// /// Anyway, deserialization of that type in roundtrip suffers from /// -#[derive(Debug, PartialEq, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Serialize)] enum ExternallyTaggedWorkaround { Flatten { #[serde(flatten)]