macro_rules! write_primitive {
($method:ident ( $ty:ty )) => {
fn $method(mut self, value: $ty) -> Result<Self::Ok, Self::Error> {
self.write_fmt(format_args!("{}", value))?;
Ok(self.writer)
}
};
() => {
fn serialize_bool(mut self, value: bool) -> Result<Self::Ok, Self::Error> {
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));
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::Ok, Self::Error> {
self.serialize_str(value.encode_utf8(&mut [0u8; 4]))
}
fn serialize_bytes(self, _value: &[u8]) -> Result<Self::Ok, Self::Error> {
Err(Self::Error::Unsupported(
"`serialize_bytes` not supported yet".into(),
))
}
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(self.writer)
}
fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<Self::Ok, Self::Error> {
value.serialize(self)
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
self.serialize_str(variant)
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(
self,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error> {
value.serialize(self)
}
};
}
mod content;
mod element;
pub(crate) mod key;
pub(crate) mod simple_type;
mod text;
use self::content::ContentSerializer;
use self::element::{ElementSerializer, Map, Struct, Tuple};
use crate::de::TEXT_KEY;
use crate::writer::{Indentation, ToFmtWrite};
use serde::ser::{self, Serialize};
use std::fmt::Write;
use std::str::from_utf8;
pub use self::simple_type::SimpleTypeSerializer;
pub use crate::errors::serialize::SeError;
pub fn to_writer<W, T>(mut writer: W, value: &T) -> Result<WriteResult, SeError>
where
W: Write,
T: ?Sized + Serialize,
{
value.serialize(Serializer::new(&mut writer))
}
pub fn to_utf8_io_writer<W, T>(writer: W, value: &T) -> Result<WriteResult, SeError>
where
W: std::io::Write,
T: ?Sized + Serialize,
{
value.serialize(Serializer::new(&mut ToFmtWrite(writer)))
}
pub fn to_string<T>(value: &T) -> Result<String, SeError>
where
T: ?Sized + Serialize,
{
let mut buffer = String::new();
to_writer(&mut buffer, value)?;
Ok(buffer)
}
pub fn to_writer_with_root<W, T>(
mut writer: W,
root_tag: &str,
value: &T,
) -> Result<WriteResult, SeError>
where
W: Write,
T: ?Sized + Serialize,
{
value.serialize(Serializer::with_root(&mut writer, Some(root_tag))?)
}
pub fn to_string_with_root<T>(root_tag: &str, value: &T) -> Result<String, SeError>
where
T: ?Sized + Serialize,
{
let mut buffer = String::new();
to_writer_with_root(&mut buffer, root_tag, value)?;
Ok(buffer)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TextFormat {
Text,
CData,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuoteLevel {
Full,
Partial,
Minimal,
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub enum EmptyElementHandling {
#[default]
SelfClosed,
SelfClosedWithSpace,
Expanded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteResult {
Text,
Element,
Nothing,
SensitiveText,
SensitiveNothing,
}
impl WriteResult {
#[inline]
pub fn allow_indent(&self) -> bool {
matches!(self, Self::Element | Self::Nothing)
}
#[inline]
pub fn is_text(&self) -> bool {
matches!(self, Self::Text | Self::SensitiveText)
}
}
macro_rules! forward {
($name:ident($ty:ty)) => {
fn $name(self, value: $ty) -> Result<Self::Ok, Self::Error> {
self.ser(&concat!("`", stringify!($ty), "`"))?.$name(value)
}
};
}
const fn is_xml11_name_start_char(ch: char) -> bool {
#[allow(clippy::match_like_matches_macro)]
match ch {
':'
| 'A'..='Z'
| '_'
| 'a'..='z'
| '\u{00C0}'..='\u{00D6}'
| '\u{00D8}'..='\u{00F6}'
| '\u{00F8}'..='\u{02FF}'
| '\u{0370}'..='\u{037D}'
| '\u{037F}'..='\u{1FFF}'
| '\u{200C}'..='\u{200D}'
| '\u{2070}'..='\u{218F}'
| '\u{2C00}'..='\u{2FEF}'
| '\u{3001}'..='\u{D7FF}'
| '\u{F900}'..='\u{FDCF}'
| '\u{FDF0}'..='\u{FFFD}'
| '\u{10000}'..='\u{EFFFF}' => true,
_ => false,
}
}
const fn is_xml11_name_char(ch: char) -> bool {
match ch {
'-' | '.' | '0'..='9' | '\u{00B7}' | '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}' => {
true
}
_ => is_xml11_name_start_char(ch),
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct XmlName<'n>(&'n str);
impl<'n> XmlName<'n> {
pub fn try_from(name: &'n str) -> Result<XmlName<'n>, SeError> {
match name.chars().next() {
Some(ch) if !is_xml11_name_start_char(ch) => Err(SeError::Unsupported(
format!("character `{ch}` is not allowed at the start of an XML name `{name}`")
.into(),
)),
_ => match name.matches(|ch| !is_xml11_name_char(ch)).next() {
Some(s) => Err(SeError::Unsupported(
format!("character `{s}` is not allowed in an XML name `{name}`").into(),
)),
None => Ok(XmlName(name)),
},
}
}
}
pub(crate) enum Indent<'i> {
None,
Owned(Indentation),
Borrow(&'i mut Indentation),
}
impl<'i> Indent<'i> {
pub fn borrow(&mut self) -> Indent<'_> {
match self {
Self::None => Indent::None,
Self::Owned(ref mut i) => Indent::Borrow(i),
Self::Borrow(i) => Indent::Borrow(i),
}
}
pub fn increase(&mut self) {
match self {
Self::None => {}
Self::Owned(i) => i.grow(),
Self::Borrow(i) => i.grow(),
}
}
pub fn decrease(&mut self) {
match self {
Self::None => {}
Self::Owned(i) => i.shrink(),
Self::Borrow(i) => i.shrink(),
}
}
pub fn write_indent<W: std::fmt::Write>(&mut self, mut writer: W) -> Result<(), SeError> {
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(())
}
}
pub struct Serializer<'w, 'r, W: Write> {
ser: ContentSerializer<'w, 'r, W>,
root_tag: Option<XmlName<'r>>,
}
impl<'w, 'r, W: Write> Serializer<'w, 'r, W> {
pub fn new(writer: &'w mut W) -> Self {
Self {
ser: ContentSerializer {
writer,
level: QuoteLevel::Partial,
indent: Indent::None,
write_indent: false,
text_format: TextFormat::Text,
allow_primitive: true,
empty_element_handling: EmptyElementHandling::SelfClosed,
},
root_tag: None,
}
}
pub fn with_root(writer: &'w mut W, root_tag: Option<&'r str>) -> Result<Self, SeError> {
Ok(Self {
ser: ContentSerializer {
writer,
level: QuoteLevel::Partial,
indent: Indent::None,
write_indent: false,
text_format: TextFormat::Text,
allow_primitive: true,
empty_element_handling: EmptyElementHandling::SelfClosed,
},
root_tag: root_tag.map(XmlName::try_from).transpose()?,
})
}
pub fn empty_element_handling(&mut self, handling: EmptyElementHandling) -> &mut Self {
self.ser.empty_element_handling = handling;
self
}
pub fn expand_empty_elements(&mut self, expand: bool) -> &mut Self {
self.empty_element_handling(if expand {
EmptyElementHandling::Expanded
} else {
EmptyElementHandling::SelfClosed
})
}
pub fn text_format(&mut self, format: TextFormat) -> &mut Self {
self.ser.text_format = format;
self
}
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
}
pub fn set_quote_level(&mut self, level: QuoteLevel) -> &mut Self {
self.ser.level = level;
self
}
pub(crate) fn set_indent(&mut self, indent: Indent<'r>) -> &mut Self {
self.ser.indent = indent;
self
}
fn ser(self, err: &str) -> Result<ElementSerializer<'w, 'r, W>, SeError> {
if let Some(key) = self.root_tag {
Ok(ElementSerializer { ser: self.ser, key })
} else {
Err(SeError::Unsupported(
format!("cannot serialize {} without defined root tag", err).into(),
))
}
}
fn ser_name(self, key: &'static str) -> Result<ElementSerializer<'w, 'r, W>, SeError> {
Ok(ElementSerializer {
ser: self.ser,
key: match self.root_tag {
Some(key) => key,
None => XmlName::try_from(key)?,
},
})
}
}
impl<'w, 'r, W: Write> ser::Serializer for Serializer<'w, 'r, W> {
type Ok = WriteResult;
type Error = SeError;
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));
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<Self::Ok, Self::Error> {
Ok(WriteResult::SensitiveNothing)
}
fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<Self::Ok, Self::Error> {
value.serialize(self)
}
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
self.ser("`()`")?.serialize_unit()
}
fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> {
self.ser_name(name)?.serialize_unit_struct(name)
}
fn serialize_unit_variant(
self,
name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Self::Error> {
if variant == TEXT_KEY {
Err(SeError::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<T: ?Sized + Serialize>(
self,
name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error> {
self.ser_name(name)?.serialize_newtype_struct(name, value)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error> {
if variant == TEXT_KEY {
value.serialize(self.ser.into_simple_type_serializer()?)?;
Ok(WriteResult::SensitiveText)
} else {
let ser = ElementSerializer {
ser: self.ser,
key: XmlName::try_from(variant)?,
};
value.serialize(ser)
}
}
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
self.ser("sequence")?.serialize_seq(len)
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
self.ser("unnamed tuple")?.serialize_tuple(len)
}
fn serialize_tuple_struct(
self,
name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Self::Error> {
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<Self::SerializeTupleVariant, Self::Error> {
if variant == TEXT_KEY {
self.ser
.into_simple_type_serializer()?
.serialize_tuple_struct(name, len)
.map(Tuple::Text)
} else {
let ser = ElementSerializer {
ser: self.ser,
key: XmlName::try_from(variant)?,
};
ser.serialize_tuple_struct(name, len).map(Tuple::Element)
}
}
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
self.ser("map")?.serialize_map(len)
}
fn serialize_struct(
self,
name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Self::Error> {
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<Self::SerializeStructVariant, Self::Error> {
if variant == TEXT_KEY {
Err(SeError::Unsupported(
format!(
"cannot serialize enum struct variant `{}::$text` as text content value",
name
)
.into(),
))
} else {
let ser = ElementSerializer {
ser: self.ser,
key: XmlName::try_from(variant)?,
};
ser.serialize_struct(name, len)
}
}
}
#[cfg(test)]
mod quote_level {
use super::*;
use pretty_assertions::assert_eq;
use serde::Serialize;
#[derive(Debug, PartialEq, Serialize)]
struct Element(&'static str);
#[derive(Debug, PartialEq, Serialize)]
struct Example {
#[serde(rename = "@attribute")]
attribute: &'static str,
element: Element,
}
#[test]
fn default_() {
let example = Example {
attribute: "special chars: &, <, >, \", '",
element: Element("special chars: &, <, >, \", '"),
};
let mut buffer = String::new();
let ser = Serializer::new(&mut buffer);
example.serialize(ser).unwrap();
assert_eq!(
buffer,
"<Example attribute=\"special chars: &, <, >, ", '\">\
<element>special chars: &, <, >, \", '</element>\
</Example>"
);
}
#[test]
fn minimal() {
let example = Example {
attribute: "special chars: &, <, >, \", '",
element: Element("special chars: &, <, >, \", '"),
};
let mut buffer = String::new();
let mut ser = Serializer::new(&mut buffer);
ser.set_quote_level(QuoteLevel::Minimal);
example.serialize(ser).unwrap();
assert_eq!(
buffer,
"<Example attribute=\"special chars: &, <, >, ", '\">\
<element>special chars: &, <, >, \", '</element>\
</Example>"
);
}
#[test]
fn partial() {
let example = Example {
attribute: "special chars: &, <, >, \", '",
element: Element("special chars: &, <, >, \", '"),
};
let mut buffer = String::new();
let mut ser = Serializer::new(&mut buffer);
ser.set_quote_level(QuoteLevel::Partial);
example.serialize(ser).unwrap();
assert_eq!(
buffer,
"<Example attribute=\"special chars: &, <, >, ", '\">\
<element>special chars: &, <, >, \", '</element>\
</Example>"
);
}
#[test]
fn full() {
let example = Example {
attribute: "special chars: &, <, >, \", '",
element: Element("special chars: &, <, >, \", '"),
};
let mut buffer = String::new();
let mut ser = Serializer::new(&mut buffer);
ser.set_quote_level(QuoteLevel::Full);
example.serialize(ser).unwrap();
assert_eq!(
buffer,
"<Example attribute=\"special chars: &, <, >, ", '\">\
<element>special chars: &, <, >, ", '</element>\
</Example>"
);
}
}