use crate::error::{Error, Result};
use crate::escape::escape;
use serde::ser::{self, Serialize};
use std::io::Write;
use std::sync::Arc;
pub fn to_string<T>(value: &T) -> Result<String>
where
T: Serialize + ?Sized,
{
let mut serializer = Serializer::new();
value.serialize(&mut serializer)?;
Ok(serializer.into_string())
}
pub fn to_string_with_root<T>(value: &T, root: &str) -> Result<String>
where
T: Serialize + ?Sized,
{
let mut serializer = Serializer::with_root(root);
value.serialize(&mut serializer)?;
Ok(serializer.into_string())
}
pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
where
T: Serialize + ?Sized,
{
Ok(to_string(value)?.into_bytes())
}
pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
where
W: Write,
T: Serialize + ?Sized,
{
let xml = to_string(value)?;
let mut writer = writer;
writer.write_all(xml.as_bytes())?;
Ok(())
}
#[derive(Clone)]
enum TagName {
Static(&'static str),
Shared(Arc<str>),
}
impl TagName {
fn as_str(&self) -> &str {
match self {
TagName::Static(s) => s,
TagName::Shared(s) => s,
}
}
}
impl From<&'static str> for TagName {
fn from(s: &'static str) -> Self {
TagName::Static(s)
}
}
pub struct Serializer {
output: String,
root: Option<Arc<str>>,
current_element: Option<&'static str>,
element_stack: Vec<TagName>,
is_key: bool,
current_key: Option<TagName>,
include_declaration: bool,
indent_level: usize,
indent_str: Option<String>,
}
impl Serializer {
pub fn new() -> Self {
Self {
output: String::new(),
root: None,
current_element: None,
element_stack: Vec::new(),
is_key: false,
current_key: None,
include_declaration: false,
indent_level: 0,
indent_str: None,
}
}
pub fn with_root(root: &str) -> Self {
Self {
root: Some(Arc::from(root)),
..Self::new()
}
}
pub fn with_indent(mut self, indent: &str) -> Self {
self.indent_str = Some(indent.to_string());
self
}
pub fn with_declaration(mut self) -> Self {
self.include_declaration = true;
self
}
pub fn into_string(self) -> String {
if self.include_declaration {
let mut result = String::with_capacity(self.output.len() + 40);
result.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
result.push_str(&self.output);
result
} else {
self.output
}
}
fn write_start_tag(&mut self, name: impl Into<TagName>) {
let name = name.into();
self.write_indent();
self.output.push('<');
self.output.push_str(name.as_str());
self.output.push('>');
self.element_stack.push(name);
self.indent_level += 1;
}
fn write_start_tag_with_attrs(&mut self, name: impl Into<TagName>, attrs: &[(String, String)]) {
let name = name.into();
self.write_indent();
self.output.push('<');
self.output.push_str(name.as_str());
for (attr_name, attr_value) in attrs {
self.output.push(' ');
self.output.push_str(attr_name);
self.output.push_str("=\"");
self.output.push_str(&escape(attr_value));
self.output.push('"');
}
self.output.push('>');
self.element_stack.push(name);
self.indent_level += 1;
}
fn undo_start_tag(&mut self, rollback_to: usize) {
self.output.truncate(rollback_to);
self.element_stack.pop();
self.indent_level = self.indent_level.saturating_sub(1);
}
fn write_end_tag(&mut self) {
self.indent_level = self.indent_level.saturating_sub(1);
if let Some(name) = self.element_stack.pop() {
self.write_indent();
self.output.push_str("</");
self.output.push_str(name.as_str());
self.output.push('>');
}
}
fn write_empty_element(&mut self, name: &str) {
self.write_indent();
self.output.push('<');
self.output.push_str(name);
self.output.push_str("/>");
}
fn write_empty_element_with_attrs(&mut self, name: &str, attrs: &[(String, String)]) {
self.write_indent();
self.output.push('<');
self.output.push_str(name);
for (attr_name, attr_value) in attrs {
self.output.push(' ');
self.output.push_str(attr_name);
self.output.push_str("=\"");
self.output.push_str(&escape(attr_value));
self.output.push('"');
}
self.output.push_str("/>");
}
fn write_element(&mut self, name: &str, content: &str) {
self.write_indent();
self.output.push('<');
self.output.push_str(name);
self.output.push('>');
self.output.push_str(&escape(content));
self.output.push_str("</");
self.output.push_str(name);
self.output.push('>');
}
fn write_text(&mut self, content: &str) {
self.output.push_str(&escape(content));
}
fn write_indent(&mut self) {
if let Some(ref indent) = self.indent_str {
if !self.output.is_empty() && !self.output.ends_with('\n') {
self.output.push('\n');
}
for _ in 0..self.indent_level {
self.output.push_str(indent);
}
}
}
fn take_element_name(&mut self, fallback: &'static str) -> TagName {
self.current_key
.take()
.or_else(|| self.current_element.map(TagName::Static))
.or_else(|| self.root.clone().map(TagName::Shared))
.unwrap_or(TagName::Static(fallback))
}
fn emit_scalar(&mut self, text: &str) {
if self.is_key {
self.current_key = Some(TagName::Shared(Arc::from(text)));
self.is_key = false;
} else if let Some(key) = self.current_key.take() {
self.write_element(key.as_str(), text);
} else {
self.write_text(text);
}
}
fn open_field_wrapper(&mut self) -> bool {
match self.current_key.take() {
Some(key) => {
self.write_start_tag(key);
true
}
None => false,
}
}
}
impl Default for Serializer {
fn default() -> Self {
Self::new()
}
}
impl<'a> ser::Serializer for &'a mut Serializer {
type Ok = ();
type Error = Error;
type SerializeSeq = SeqSerializer<'a>;
type SerializeTuple = SeqSerializer<'a>;
type SerializeTupleStruct = SeqSerializer<'a>;
type SerializeTupleVariant = SeqSerializer<'a>;
type SerializeMap = MapSerializer<'a>;
type SerializeStruct = StructSerializer<'a>;
type SerializeStructVariant = StructSerializer<'a>;
fn serialize_bool(self, v: bool) -> Result<()> {
self.emit_scalar(if v { "true" } else { "false" });
Ok(())
}
fn serialize_i8(self, v: i8) -> Result<()> {
self.serialize_i64(v as i64)
}
fn serialize_i16(self, v: i16) -> Result<()> {
self.serialize_i64(v as i64)
}
fn serialize_i32(self, v: i32) -> Result<()> {
self.serialize_i64(v as i64)
}
fn serialize_i64(self, v: i64) -> Result<()> {
let mut buffer = itoa::Buffer::new();
self.emit_scalar(buffer.format(v));
Ok(())
}
fn serialize_u8(self, v: u8) -> Result<()> {
self.serialize_u64(v as u64)
}
fn serialize_u16(self, v: u16) -> Result<()> {
self.serialize_u64(v as u64)
}
fn serialize_u32(self, v: u32) -> Result<()> {
self.serialize_u64(v as u64)
}
fn serialize_u64(self, v: u64) -> Result<()> {
let mut buffer = itoa::Buffer::new();
self.emit_scalar(buffer.format(v));
Ok(())
}
fn serialize_f32(self, v: f32) -> Result<()> {
self.serialize_f64(v as f64)
}
fn serialize_f64(self, v: f64) -> Result<()> {
let mut buffer = ryu::Buffer::new();
self.emit_scalar(buffer.format(v));
Ok(())
}
fn serialize_char(self, v: char) -> Result<()> {
let mut buf = [0u8; 4];
self.emit_scalar(v.encode_utf8(&mut buf));
Ok(())
}
fn serialize_str(self, v: &str) -> Result<()> {
self.emit_scalar(v);
Ok(())
}
fn serialize_bytes(self, v: &[u8]) -> Result<()> {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut encoded = String::with_capacity(v.len() * 2);
for &byte in v {
encoded.push(HEX[(byte >> 4) as usize] as char);
encoded.push(HEX[(byte & 0x0f) as usize] as char);
}
self.emit_scalar(&encoded);
Ok(())
}
fn serialize_none(self) -> Result<()> {
self.current_key = None;
Ok(())
}
fn serialize_some<T>(self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
value.serialize(self)
}
fn serialize_unit(self) -> Result<()> {
if let Some(key) = self.current_key.take() {
self.write_empty_element(key.as_str());
}
Ok(())
}
fn serialize_unit_struct(self, name: &'static str) -> Result<()> {
let elem_name = self.take_element_name(name);
self.write_empty_element(elem_name.as_str());
Ok(())
}
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<()> {
if let Some(key) = self.current_key.take() {
self.write_element(key.as_str(), variant);
} else {
self.write_empty_element(variant);
}
Ok(())
}
fn serialize_newtype_struct<T>(self, name: &'static str, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
self.current_element = Some(name);
value.serialize(self)
}
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<()>
where
T: Serialize + ?Sized,
{
let wrapped = self.open_field_wrapper();
self.write_start_tag(variant);
value.serialize(&mut *self)?;
self.write_end_tag();
if wrapped {
self.write_end_tag();
}
Ok(())
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
let element_name = self.current_key.take().unwrap_or(TagName::Static("item"));
Ok(SeqSerializer {
ser: self,
element_name,
wrapped: false,
})
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(
self,
name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleStruct> {
self.write_start_tag(name);
Ok(SeqSerializer {
ser: self,
element_name: TagName::Static("item"),
wrapped: false,
})
}
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
let wrapped = self.open_field_wrapper();
self.write_start_tag(variant);
Ok(SeqSerializer {
ser: self,
element_name: TagName::Static("item"),
wrapped,
})
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
let name = self.current_key.take()
.or_else(|| self.root.clone().map(TagName::Shared))
.unwrap_or(TagName::Static("map"));
self.write_start_tag(name);
Ok(MapSerializer { ser: self })
}
fn serialize_struct(self, name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
let elem_name = match self.current_key.take() {
Some(key) => key,
None if self.output.is_empty() && self.element_stack.is_empty() => self
.root
.clone()
.map(TagName::Shared)
.unwrap_or(TagName::Static(name)),
None => TagName::Static(name),
};
Ok(StructSerializer {
ser: self,
elem_name: Some(elem_name),
attrs: Vec::new(),
attr_insert_pos: None,
text_content: None,
wrapped: false,
})
}
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
let wrapped = self.open_field_wrapper();
Ok(StructSerializer {
ser: self,
elem_name: Some(TagName::Static(variant)),
attrs: Vec::new(),
attr_insert_pos: None,
text_content: None,
wrapped,
})
}
}
struct AttrValueSerializer {
output: String,
}
impl AttrValueSerializer {
fn new() -> Self {
Self { output: String::new() }
}
fn into_string(self) -> String {
self.output
}
}
impl ser::Serializer for &mut AttrValueSerializer {
type Ok = ();
type Error = Error;
type SerializeSeq = ser::Impossible<(), Error>;
type SerializeTuple = ser::Impossible<(), Error>;
type SerializeTupleStruct = ser::Impossible<(), Error>;
type SerializeTupleVariant = ser::Impossible<(), Error>;
type SerializeMap = ser::Impossible<(), Error>;
type SerializeStruct = ser::Impossible<(), Error>;
type SerializeStructVariant = ser::Impossible<(), Error>;
fn serialize_bool(self, v: bool) -> Result<()> {
self.output.push_str(if v { "true" } else { "false" });
Ok(())
}
fn serialize_i8(self, v: i8) -> Result<()> { self.serialize_i64(v as i64) }
fn serialize_i16(self, v: i16) -> Result<()> { self.serialize_i64(v as i64) }
fn serialize_i32(self, v: i32) -> Result<()> { self.serialize_i64(v as i64) }
fn serialize_i64(self, v: i64) -> Result<()> {
let mut buffer = itoa::Buffer::new();
self.output.push_str(buffer.format(v));
Ok(())
}
fn serialize_u8(self, v: u8) -> Result<()> { self.serialize_u64(v as u64) }
fn serialize_u16(self, v: u16) -> Result<()> { self.serialize_u64(v as u64) }
fn serialize_u32(self, v: u32) -> Result<()> { self.serialize_u64(v as u64) }
fn serialize_u64(self, v: u64) -> Result<()> {
let mut buffer = itoa::Buffer::new();
self.output.push_str(buffer.format(v));
Ok(())
}
fn serialize_f32(self, v: f32) -> Result<()> { self.serialize_f64(v as f64) }
fn serialize_f64(self, v: f64) -> Result<()> {
let mut buffer = ryu::Buffer::new();
self.output.push_str(buffer.format(v));
Ok(())
}
fn serialize_char(self, v: char) -> Result<()> {
self.output.push(v);
Ok(())
}
fn serialize_str(self, v: &str) -> Result<()> {
self.output.push_str(v);
Ok(())
}
fn serialize_bytes(self, _v: &[u8]) -> Result<()> {
Err(Error::unsupported("bytes in attribute"))
}
fn serialize_none(self) -> Result<()> { Ok(()) }
fn serialize_some<T: ?Sized + Serialize>(self, v: &T) -> Result<()> { v.serialize(self) }
fn serialize_unit(self) -> Result<()> { Ok(()) }
fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { Ok(()) }
fn serialize_unit_variant(self, _name: &'static str, _idx: u32, variant: &'static str) -> Result<()> {
self.output.push_str(variant);
Ok(())
}
fn serialize_newtype_struct<T: ?Sized + Serialize>(self, _name: &'static str, v: &T) -> Result<()> {
v.serialize(self)
}
fn serialize_newtype_variant<T: ?Sized + Serialize>(self, _name: &'static str, _idx: u32, _variant: &'static str, v: &T) -> Result<()> {
v.serialize(self)
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
Err(Error::unsupported("sequence in attribute"))
}
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
Err(Error::unsupported("tuple in attribute"))
}
fn serialize_tuple_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeTupleStruct> {
Err(Error::unsupported("tuple struct in attribute"))
}
fn serialize_tuple_variant(self, _name: &'static str, _idx: u32, _variant: &'static str, _len: usize) -> Result<Self::SerializeTupleVariant> {
Err(Error::unsupported("tuple variant in attribute"))
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
Err(Error::unsupported("map in attribute"))
}
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
Err(Error::unsupported("struct in attribute"))
}
fn serialize_struct_variant(self, _name: &'static str, _idx: u32, _variant: &'static str, _len: usize) -> Result<Self::SerializeStructVariant> {
Err(Error::unsupported("struct variant in attribute"))
}
}
pub struct SeqSerializer<'a> {
ser: &'a mut Serializer,
element_name: TagName,
wrapped: bool,
}
impl<'a> ser::SerializeSeq for SeqSerializer<'a> {
type Ok = ();
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
self.ser.current_key = Some(self.element_name.clone());
value.serialize(&mut *self.ser)
}
fn end(self) -> Result<()> {
Ok(())
}
}
impl<'a> ser::SerializeTuple for SeqSerializer<'a> {
type Ok = ();
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<()> {
ser::SerializeSeq::end(self)
}
}
impl<'a> ser::SerializeTupleStruct for SeqSerializer<'a> {
type Ok = ();
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
self.ser.current_key = Some(self.element_name.clone());
value.serialize(&mut *self.ser)
}
fn end(self) -> Result<()> {
self.ser.write_end_tag();
Ok(())
}
}
impl<'a> ser::SerializeTupleVariant for SeqSerializer<'a> {
type Ok = ();
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
self.ser.current_key = Some(self.element_name.clone());
value.serialize(&mut *self.ser)
}
fn end(self) -> Result<()> {
self.ser.write_end_tag();
if self.wrapped {
self.ser.write_end_tag();
}
Ok(())
}
}
pub struct MapSerializer<'a> {
ser: &'a mut Serializer,
}
impl<'a> ser::SerializeMap for MapSerializer<'a> {
type Ok = ();
type Error = Error;
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
self.ser.is_key = true;
key.serialize(&mut *self.ser)
}
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
value.serialize(&mut *self.ser)
}
fn end(self) -> Result<()> {
self.ser.write_end_tag();
Ok(())
}
}
pub struct StructSerializer<'a> {
ser: &'a mut Serializer,
elem_name: Option<TagName>,
attrs: Vec<(String, String)>,
attr_insert_pos: Option<usize>,
text_content: Option<String>,
wrapped: bool,
}
impl<'a> ser::SerializeStruct for StructSerializer<'a> {
type Ok = ();
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
if let Some(attr_name) = key.strip_prefix('@') {
let mut attr_ser = AttrValueSerializer::new();
value.serialize(&mut attr_ser)?;
let attr_value = attr_ser.into_string();
if let Some(pos) = self.attr_insert_pos {
let mut rendered = String::with_capacity(attr_name.len() + attr_value.len() + 4);
rendered.push(' ');
rendered.push_str(attr_name);
rendered.push_str("=\"");
rendered.push_str(&escape(&attr_value));
rendered.push('"');
self.ser.output.insert_str(pos, &rendered);
self.attr_insert_pos = Some(pos + rendered.len());
} else {
self.attrs.push((attr_name.to_string(), attr_value));
}
return Ok(());
}
if key == "$value" || key == "$text" {
let mut buf = self.text_content.take().unwrap_or_default();
buf.clear();
let mut text_ser = Serializer {
output: buf,
..Serializer::new()
};
value.serialize(&mut text_ser)?;
self.text_content = Some(text_ser.output);
return Ok(());
}
if let Some(name) = self.elem_name.take() {
let rollback_to = self.ser.output.len();
self.ser.write_start_tag_with_attrs(name.clone(), &self.attrs);
let content_start = self.ser.output.len();
self.ser.current_key = Some(TagName::Static(key));
value.serialize(&mut *self.ser)?;
if self.ser.output.len() == content_start {
self.ser.undo_start_tag(rollback_to);
self.ser.current_key = None;
self.elem_name = Some(name);
} else {
self.attr_insert_pos = Some(content_start - 1);
}
} else {
self.ser.current_key = Some(TagName::Static(key));
value.serialize(&mut *self.ser)?;
}
Ok(())
}
fn end(self) -> Result<()> {
match self.elem_name {
Some(name) => {
if let Some(text) = self.text_content {
self.ser.write_start_tag_with_attrs(name, &self.attrs);
self.ser.output.push_str(&text);
self.ser.write_end_tag();
} else if self.attrs.is_empty() {
self.ser.write_empty_element(name.as_str());
} else {
self.ser.write_empty_element_with_attrs(name.as_str(), &self.attrs);
}
}
None => {
if let Some(text) = self.text_content {
self.ser.output.push_str(&text);
}
self.ser.write_end_tag();
}
}
if self.wrapped {
self.ser.write_end_tag();
}
Ok(())
}
}
impl<'a> ser::SerializeStructVariant for StructSerializer<'a> {
type Ok = ();
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
ser::SerializeStruct::serialize_field(self, key, value)
}
fn end(self) -> Result<()> {
ser::SerializeStruct::end(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Serialize;
#[test]
fn test_serialize_simple_struct() {
#[derive(Serialize)]
struct Person {
name: String,
age: u32,
}
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let xml = to_string(&person).unwrap();
assert!(xml.contains("<Person>"));
assert!(xml.contains("<name>Alice</name>"));
assert!(xml.contains("<age>30</age>"));
assert!(xml.contains("</Person>"));
}
#[test]
fn test_serialize_with_attributes() {
#[derive(Serialize)]
struct Element {
#[serde(rename = "@id")]
id: String,
#[serde(rename = "@class")]
class: String,
content: String,
}
let elem = Element {
id: "main".to_string(),
class: "container".to_string(),
content: "Hello".to_string(),
};
let xml = to_string(&elem).unwrap();
assert!(xml.contains(r#"id="main""#));
assert!(xml.contains(r#"class="container""#));
assert!(xml.contains("<content>Hello</content>"));
}
#[test]
fn test_serialize_attributes_only() {
#[derive(Serialize)]
struct EmptyElement {
#[serde(rename = "@id")]
id: String,
#[serde(rename = "@disabled")]
disabled: bool,
}
let elem = EmptyElement {
id: "btn".to_string(),
disabled: true,
};
let xml = to_string(&elem).unwrap();
assert!(xml.contains(r#"id="btn""#));
assert!(xml.contains(r#"disabled="true""#));
assert!(xml.contains("/>") || xml.contains("</EmptyElement>"));
}
#[test]
fn test_serialize_text_content() {
#[derive(Serialize)]
struct TextElement {
#[serde(rename = "@id")]
id: String,
#[serde(rename = "$value")]
text: String,
}
let elem = TextElement {
id: "para".to_string(),
text: "Hello World".to_string(),
};
let xml = to_string(&elem).unwrap();
assert!(xml.contains(r#"id="para""#));
assert!(xml.contains("Hello World"));
}
#[test]
fn test_serialize_nested_struct() {
#[derive(Serialize)]
struct Address {
city: String,
country: String,
}
#[derive(Serialize)]
struct Person {
name: String,
address: Address,
}
let person = Person {
name: "Bob".to_string(),
address: Address {
city: "New York".to_string(),
country: "USA".to_string(),
},
};
let xml = to_string(&person).unwrap();
assert!(xml.contains("<address>"));
assert!(xml.contains("<city>New York</city>"));
assert!(xml.contains("</address>"));
}
#[test]
fn test_serialize_optional() {
#[derive(Serialize)]
struct Config {
name: String,
value: Option<String>,
}
let with_value = Config {
name: "test".to_string(),
value: Some("val".to_string()),
};
let xml = to_string(&with_value).unwrap();
assert!(xml.contains("<value>val</value>"));
let without_value = Config {
name: "test".to_string(),
value: None,
};
let xml = to_string(&without_value).unwrap();
assert!(!xml.contains("<value>"));
}
#[test]
fn test_serialize_vector() {
#[derive(Serialize)]
struct Items {
items: Vec<String>,
}
let items = Items {
items: vec!["one".to_string(), "two".to_string(), "three".to_string()],
};
let xml = to_string(&items).unwrap();
assert!(xml.contains("<items>one</items>"));
assert!(xml.contains("<items>two</items>"));
assert!(xml.contains("<items>three</items>"));
}
#[test]
fn test_serialize_escaped_content() {
#[derive(Serialize)]
struct Data {
content: String,
}
let data = Data {
content: "<hello> & \"world\"".to_string(),
};
let xml = to_string(&data).unwrap();
assert!(xml.contains("<hello>"));
assert!(xml.contains("&"));
assert!(xml.contains("""));
}
#[test]
fn test_serialize_escaped_attribute() {
#[derive(Serialize)]
struct Element {
#[serde(rename = "@title")]
title: String,
}
let elem = Element {
title: "Hello \"World\" & <Friends>".to_string(),
};
let xml = to_string(&elem).unwrap();
assert!(xml.contains("""));
assert!(xml.contains("&"));
assert!(xml.contains("<"));
}
#[test]
fn test_serialize_bool() {
#[derive(Serialize)]
struct Flags {
enabled: bool,
active: bool,
}
let flags = Flags {
enabled: true,
active: false,
};
let xml = to_string(&flags).unwrap();
assert!(xml.contains("<enabled>true</enabled>"));
assert!(xml.contains("<active>false</active>"));
}
#[test]
fn test_serialize_numbers() {
#[derive(Serialize)]
struct Numbers {
i: i32,
u: u64,
f: f64,
}
let nums = Numbers {
i: -42,
u: 100,
f: 1.234,
};
let xml = to_string(&nums).unwrap();
assert!(xml.contains("<i>-42</i>"));
assert!(xml.contains("<u>100</u>"));
assert!(xml.contains("<f>1.234</f>"));
}
#[test]
fn test_serialize_enum() {
#[derive(Serialize)]
enum Status {
Active,
}
#[derive(Serialize)]
struct Item {
status: Status,
}
let item = Item {
status: Status::Active,
};
let xml = to_string(&item).unwrap();
assert!(xml.contains("<status>Active</status>") || xml.contains("<Active/>"));
}
#[test]
fn test_serialize_unit_struct() {
#[derive(Serialize)]
struct Empty;
let xml = to_string(&Empty).unwrap();
assert!(xml.contains("<Empty/>"));
}
#[test]
fn test_serialize_char() {
#[derive(Serialize)]
struct Data {
c: char,
}
let data = Data { c: 'A' };
let xml = to_string(&data).unwrap();
assert!(xml.contains("<c>A</c>"));
}
#[test]
fn test_to_vec() {
#[derive(Serialize)]
struct Data {
value: String,
}
let data = Data {
value: "test".to_string(),
};
let bytes = to_vec(&data).unwrap();
let xml = String::from_utf8(bytes).unwrap();
assert!(xml.contains("<value>test</value>"));
}
#[test]
fn test_to_writer() {
#[derive(Serialize)]
struct Data {
value: String,
}
let data = Data {
value: "test".to_string(),
};
let mut buffer = Vec::new();
to_writer(&mut buffer, &data).unwrap();
let xml = String::from_utf8(buffer).unwrap();
assert!(xml.contains("<value>test</value>"));
}
#[test]
fn test_with_root() {
#[derive(Serialize)]
struct Data {
value: String,
}
let data = Data {
value: "test".to_string(),
};
let xml = to_string_with_root(&data, "root").unwrap();
assert_eq!(xml, "<root><value>test</value></root>");
}
#[test]
fn test_with_root_nested_uses_field_name() {
#[derive(Serialize)]
struct Inner {
value: String,
}
#[derive(Serialize)]
struct Outer {
inner: Inner,
}
let outer = Outer {
inner: Inner {
value: "test".to_string(),
},
};
let xml = to_string_with_root(&outer, "root").unwrap();
assert_eq!(xml, "<root><inner><value>test</value></inner></root>");
}
#[test]
fn test_complex_with_attributes() {
#[derive(Serialize)]
struct Item {
#[serde(rename = "@id")]
id: u32,
#[serde(rename = "@class")]
class: String,
name: String,
value: i32,
}
#[derive(Serialize)]
struct Container {
#[serde(rename = "@version")]
version: String,
item: Vec<Item>,
}
let container = Container {
version: "1.0".to_string(),
item: vec![
Item {
id: 1,
class: "primary".to_string(),
name: "First".to_string(),
value: 100,
},
Item {
id: 2,
class: "secondary".to_string(),
name: "Second".to_string(),
value: 200,
},
],
};
let xml = to_string(&container).unwrap();
assert!(xml.contains(r#"version="1.0""#));
assert!(xml.contains(r#"id="1""#));
assert!(xml.contains(r#"class="primary""#));
assert!(xml.contains("<name>First</name>"));
}
#[test]
fn test_with_declaration() {
#[derive(Serialize)]
struct Data {
value: String,
}
let data = Data {
value: "test".to_string(),
};
let mut serializer = Serializer::new().with_declaration();
data.serialize(&mut serializer).unwrap();
let xml = serializer.into_string();
assert!(xml.starts_with(r#"<?xml version="1.0" encoding="UTF-8"?>"#));
assert!(xml.contains("<value>test</value>"));
}
#[test]
fn test_no_declaration_by_default() {
#[derive(Serialize)]
struct Data {
value: String,
}
let data = Data {
value: "test".to_string(),
};
let xml = to_string(&data).unwrap();
assert!(!xml.contains("<?xml"));
}
#[test]
fn test_attribute_after_regular_field() {
#[derive(Serialize)]
struct Thing {
name: String,
#[serde(rename = "@id")]
id: String,
}
let thing = Thing {
name: "widget".to_string(),
id: "7".to_string(),
};
let xml = to_string(&thing).unwrap();
assert_eq!(xml, r#"<Thing id="7"><name>widget</name></Thing>"#);
}
#[test]
fn test_serialize_map_numeric_keys() {
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert(1u32, "one".to_string());
map.insert(2u32, "two".to_string());
let xml = to_string(&map).unwrap();
assert_eq!(xml, "<map><1>one</1><2>two</2></map>");
}
#[test]
fn test_serialize_newtype_variant() {
#[derive(Serialize)]
enum Value {
Count(u32),
}
let xml = to_string(&Value::Count(42)).unwrap();
assert_eq!(xml, "<Count>42</Count>");
}
#[test]
fn test_serialize_tuple_variant() {
#[derive(Serialize)]
enum Value {
Pair(u32, String),
}
let xml = to_string(&Value::Pair(1, "one".to_string())).unwrap();
assert_eq!(xml, "<Pair><item>1</item><item>one</item></Pair>");
}
#[test]
fn test_serialize_struct_variant() {
#[derive(Serialize)]
enum Value {
Point { x: i32, y: i32 },
}
let xml = to_string(&Value::Point { x: 1, y: 2 }).unwrap();
assert_eq!(xml, "<Point><x>1</x><y>2</y></Point>");
}
#[test]
fn test_serialize_newtype_variant_as_field() {
#[derive(Serialize)]
enum Value {
Count(u32),
}
#[derive(Serialize)]
struct Item {
status: Value,
}
let item = Item {
status: Value::Count(42),
};
let xml = to_string(&item).unwrap();
assert_eq!(xml, "<Item><status><Count>42</Count></status></Item>");
}
#[test]
fn test_serialize_tuple_variant_as_field() {
#[derive(Serialize)]
enum Value {
Pair(u32, String),
}
#[derive(Serialize)]
struct Item {
status: Value,
}
let item = Item {
status: Value::Pair(1, "one".to_string()),
};
let xml = to_string(&item).unwrap();
assert_eq!(
xml,
"<Item><status><Pair><item>1</item><item>one</item></Pair></status></Item>"
);
}
#[test]
fn test_serialize_struct_variant_as_field() {
#[derive(Serialize)]
enum Value {
Point { x: i32, y: i32 },
}
#[derive(Serialize)]
struct Item {
status: Value,
}
let item = Item {
status: Value::Point { x: 1, y: 2 },
};
let xml = to_string(&item).unwrap();
assert_eq!(xml, "<Item><status><Point><x>1</x><y>2</y></Point></status></Item>");
}
#[test]
fn test_exact_output_simple_struct() {
#[derive(Serialize)]
struct Person {
name: String,
age: u32,
}
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let xml = to_string(&person).unwrap();
assert_eq!(xml, "<Person><name>Alice</name><age>30</age></Person>");
}
#[test]
fn test_exact_output_attributes() {
#[derive(Serialize)]
struct Item {
#[serde(rename = "@id")]
id: String,
#[serde(rename = "@class")]
class: String,
name: String,
}
let item = Item {
id: "123".to_string(),
class: "product".to_string(),
name: "Widget".to_string(),
};
let xml = to_string(&item).unwrap();
assert_eq!(xml, r#"<Item id="123" class="product"><name>Widget</name></Item>"#);
}
#[test]
fn test_exact_output_text_content() {
#[derive(Serialize)]
struct Link {
#[serde(rename = "@href")]
href: String,
#[serde(rename = "$value")]
text: String,
}
let link = Link {
href: "https://example.com".to_string(),
text: "Click here".to_string(),
};
let xml = to_string(&link).unwrap();
assert_eq!(xml, r#"<Link href="https://example.com">Click here</Link>"#);
}
#[test]
fn test_with_indent() {
#[derive(Serialize)]
struct Address {
city: String,
}
#[derive(Serialize)]
struct Person {
name: String,
address: Address,
}
let person = Person {
name: "Alice".to_string(),
address: Address {
city: "Springfield".to_string(),
},
};
let mut serializer = Serializer::new().with_indent(" ");
person.serialize(&mut serializer).unwrap();
let xml = serializer.into_string();
assert_eq!(
xml,
"<Person>\n <name>Alice</name>\n <address>\n <city>Springfield</city>\n </address>\n</Person>"
);
}
#[test]
fn test_serialize_map_char_keys() {
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert('a', "1");
map.insert('b', "2");
let xml = to_string(&map).unwrap();
assert_eq!(xml, "<map><a>1</a><b>2</b></map>");
}
#[test]
fn test_serialize_map_negative_int_keys() {
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert(-1i64, "neg");
map.insert(2i64, "pos");
let xml = to_string(&map).unwrap();
assert_eq!(xml, "<map><-1>neg</-1><2>pos</2></map>");
}
#[test]
fn test_serialize_map_bool_keys() {
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert(false, "no");
map.insert(true, "yes");
let xml = to_string(&map).unwrap();
assert_eq!(xml, "<map><false>no</false><true>yes</true></map>");
}
#[test]
fn test_none_first_field_then_regular() {
#[derive(Serialize)]
struct S {
a: Option<String>,
b: String,
}
let s = S {
a: None,
b: "x".to_string(),
};
let xml = to_string(&s).unwrap();
assert_eq!(xml, "<S><b>x</b></S>");
}
#[test]
fn test_empty_vec_first_field_then_regular() {
#[derive(Serialize)]
struct S {
a: Vec<String>,
b: String,
}
let s = S {
a: vec![],
b: "x".to_string(),
};
let xml = to_string(&s).unwrap();
assert_eq!(xml, "<S><b>x</b></S>");
}
#[test]
fn test_all_none_fields_empty_element() {
#[derive(Serialize)]
struct S {
a: Option<String>,
b: Option<i32>,
}
let s = S { a: None, b: None };
let xml = to_string(&s).unwrap();
assert_eq!(xml, "<S/>");
}
#[test]
fn test_none_first_field_then_attribute() {
#[derive(Serialize)]
struct S {
a: Option<String>,
#[serde(rename = "@id")]
id: String,
b: String,
}
let s = S {
a: None,
id: "7".to_string(),
b: "x".to_string(),
};
let xml = to_string(&s).unwrap();
assert_eq!(xml, r#"<S id="7"><b>x</b></S>"#);
}
#[test]
fn test_multiple_attributes_after_regular_field() {
#[derive(Serialize)]
struct S {
name: String,
#[serde(rename = "@id")]
id: String,
#[serde(rename = "@cls")]
cls: String,
}
let s = S {
name: "w".to_string(),
id: "7".to_string(),
cls: "a".to_string(),
};
let xml = to_string(&s).unwrap();
assert_eq!(xml, r#"<S id="7" cls="a"><name>w</name></S>"#);
}
#[test]
fn test_attribute_after_nested_child() {
#[derive(Serialize)]
struct Inner {
v: String,
}
#[derive(Serialize)]
struct Outer {
child: Inner,
#[serde(rename = "@id")]
id: String,
}
let outer = Outer {
child: Inner {
v: "x".to_string(),
},
id: "7".to_string(),
};
let xml = to_string(&outer).unwrap();
assert_eq!(xml, r#"<Outer id="7"><child><v>x</v></child></Outer>"#);
}
}