use crate::builder::{DocumentBuilder, ElementBuilder};
use crate::core::{Document, XmlResult};
pub use crate::builder::{
fragment, FragmentBuilder as Fragment, IntoXmlFragment, XmlNode as FragmentNode,
};
pub type Children = Fragment;
pub trait IntoXml {
fn into_xml(self) -> XmlResult<ElementBuilder>;
}
impl IntoXml for ElementBuilder {
fn into_xml(self) -> XmlResult<ElementBuilder> {
Ok(self)
}
}
impl IntoXml for XmlResult<ElementBuilder> {
fn into_xml(self) -> XmlResult<ElementBuilder> {
self
}
}
pub fn document(component: impl IntoXml) -> XmlResult<Document> {
DocumentBuilder::new().root(component.into_xml()?)?.build()
}
pub fn children(value: impl IntoXmlFragment) -> XmlResult<Children> {
value.into_xml_fragment()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builder::{element, text};
use crate::core::ErrorKind;
use crate::writer::{to_string_compact, to_string_pretty, WriterConfig};
fn simple_component() -> XmlResult<ElementBuilder> {
element("Simple")?.text("value")
}
fn wrapper(children: Children) -> XmlResult<ElementBuilder> {
element("Wrapper")?.child(children)
}
fn layout(title: impl IntoXml, children: Children) -> XmlResult<ElementBuilder> {
element("Layout")?.child(title.into_xml()?)?.child(children)
}
fn pair_component() -> XmlResult<Fragment> {
fragment()
.child(element("First")?.text("one")?)?
.child(element("Second")?.text("two")?)
}
fn item_list_from_vec(items: Vec<ItemProps>) -> XmlResult<ElementBuilder> {
element("Items")?.child(
items
.into_iter()
.map(item_component)
.collect::<Vec<XmlResult<ElementBuilder>>>(),
)
}
#[derive(Debug, Clone)]
struct IdProps {
value: String,
}
fn id_component(props: IdProps) -> XmlResult<ElementBuilder> {
element("ID")?.text(props.value)
}
#[derive(Debug, Clone)]
struct ItemProps {
code: String,
name: String,
quantity: u32,
}
fn item_component(props: ItemProps) -> XmlResult<ElementBuilder> {
element("Item")?
.attr("code", props.code)?
.attr("quantity", props.quantity)?
.text(props.name)
}
#[derive(Debug, Clone)]
struct QualifiedElementProps {
prefix: String,
local_name: String,
namespace_uri: String,
text: String,
}
fn qualified_component(props: QualifiedElementProps) -> XmlResult<ElementBuilder> {
ElementBuilder::qualified(props.prefix, props.local_name, props.namespace_uri)?
.text(props.text)
}
#[test]
fn component_contracts_into_xml_accepts_element_builder() -> XmlResult<()> {
let root = element("Root")?.text("ok")?.into_xml()?;
let document = document(root)?;
assert_eq!(to_string_compact(&document)?, "<Root>ok</Root>");
Ok(())
}
#[test]
fn into_xml_accepts_component_result() -> XmlResult<()> {
let document = document(simple_component())?;
assert_eq!(to_string_compact(&document)?, "<Simple>value</Simple>");
Ok(())
}
#[test]
fn component_contracts_into_xml_fragment_is_available() -> XmlResult<()> {
let fragment = text("hello").into_xml_fragment()?;
let document = document(element("Root")?.child(fragment)?)?;
assert_eq!(to_string_compact(&document)?, "<Root>hello</Root>");
Ok(())
}
#[test]
fn component_contracts_children_are_explicit_fragments() -> XmlResult<()> {
let children = children(fragment().child(element("Child")?.text("value")?)?)?;
let document = document(wrapper(children)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Wrapper><Child>value</Child></Wrapper>"
);
Ok(())
}
#[test]
fn component_contracts_materialize_with_builder_backend() -> XmlResult<()> {
let root = element("Root")?.child(simple_component())?;
let document = DocumentBuilder::new().root(root)?.build()?;
assert_eq!(
to_string_compact(&document)?,
"<Root><Simple>value</Simple></Root>"
);
Ok(())
}
#[test]
fn component_props_create_xml_with_typed_props() -> XmlResult<()> {
let props = ItemProps {
code: "A1".to_owned(),
name: "Widget".to_owned(),
quantity: 3,
};
let document = document(item_component(props)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Item code=\"A1\" quantity=\"3\">Widget</Item>"
);
Ok(())
}
#[test]
fn component_props_can_build_text_from_props() -> XmlResult<()> {
let props = IdProps {
value: "INV-1".to_owned(),
};
let document = document(id_component(props)?)?;
assert_eq!(to_string_compact(&document)?, "<ID>INV-1</ID>");
Ok(())
}
#[test]
fn component_props_can_build_qualified_names() -> XmlResult<()> {
let props = QualifiedElementProps {
prefix: "doc".to_owned(),
local_name: "Title".to_owned(),
namespace_uri: "urn:doc".to_owned(),
text: "Report".to_owned(),
};
let document = document(qualified_component(props)?)?;
assert_eq!(
to_string_compact(&document)?,
"<doc:Title>Report</doc:Title>"
);
Ok(())
}
#[test]
fn component_props_invalid_names_return_xml_error() {
let props = QualifiedElementProps {
prefix: "doc".to_owned(),
local_name: "1Invalid".to_owned(),
namespace_uri: "urn:doc".to_owned(),
text: "Report".to_owned(),
};
let error = qualified_component(props).expect_err("invalid local name must fail");
assert_eq!(error.kind(), &ErrorKind::InvalidName);
}
#[test]
fn children_component_wraps_explicit_children() -> XmlResult<()> {
let content = children(
fragment()
.child(element("A")?.text("one")?)?
.child(element("B")?.text("two")?)?,
)?;
let document = document(wrapper(content)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Wrapper><A>one</A><B>two</B></Wrapper>"
);
Ok(())
}
#[test]
fn children_fragment_component_returns_multiple_nodes() -> XmlResult<()> {
let document = document(element("Root")?.child(pair_component()?)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Root><First>one</First><Second>two</Second></Root>"
);
Ok(())
}
#[test]
fn children_components_compose_from_plain_rust() -> XmlResult<()> {
let title = id_component(IdProps {
value: "T-1".to_owned(),
})?;
let content = children(pair_component()?)?;
let document = document(layout(title, content)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Layout><ID>T-1</ID><First>one</First><Second>two</Second></Layout>"
);
Ok(())
}
#[test]
fn component_collections_option_can_include_content() -> XmlResult<()> {
let include = true;
let optional_child = include.then(simple_component);
let document = document(element("Root")?.child(optional_child.transpose()?)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Root><Simple>value</Simple></Root>"
);
Ok(())
}
#[test]
fn component_collections_option_can_omit_content() -> XmlResult<()> {
let include = false;
let optional_child = include.then(simple_component);
let document = document(element("Root")?.child(optional_child.transpose()?)?)?;
assert_eq!(to_string_compact(&document)?, "<Root/>");
Ok(())
}
#[test]
fn component_collections_vec_represents_list_nodes() -> XmlResult<()> {
let items = vec![
ItemProps {
code: "a".to_owned(),
name: "Alpha".to_owned(),
quantity: 1,
},
ItemProps {
code: "b".to_owned(),
name: "Beta".to_owned(),
quantity: 2,
},
];
let document = document(item_list_from_vec(items)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Items><Item code=\"a\" quantity=\"1\">Alpha</Item><Item code=\"b\" quantity=\"2\">Beta</Item></Items>"
);
Ok(())
}
#[test]
fn component_collections_iterators_preserve_order() -> XmlResult<()> {
let document = document(
element("Items")?.children(
["a", "b", "c"]
.into_iter()
.map(|code| element("Item")?.attr("code", code)?.text(code)),
)?,
)?;
assert_eq!(
to_string_compact(&document)?,
"<Items><Item code=\"a\">a</Item><Item code=\"b\">b</Item><Item code=\"c\">c</Item></Items>"
);
Ok(())
}
#[test]
fn component_writer_serializes_simple_component_compact() -> XmlResult<()> {
let document = document(simple_component())?;
assert_eq!(to_string_compact(&document)?, "<Simple>value</Simple>");
Ok(())
}
#[test]
fn component_writer_serializes_props_component_compact() -> XmlResult<()> {
let document = document(item_component(ItemProps {
code: "x".to_owned(),
name: "Xray".to_owned(),
quantity: 7,
})?)?;
assert_eq!(
to_string_compact(&document)?,
"<Item code=\"x\" quantity=\"7\">Xray</Item>"
);
Ok(())
}
#[test]
fn component_writer_serializes_children_component_compact() -> XmlResult<()> {
let content = children(pair_component()?)?;
let document = document(wrapper(content)?)?;
assert_eq!(
to_string_compact(&document)?,
"<Wrapper><First>one</First><Second>two</Second></Wrapper>"
);
Ok(())
}
#[test]
fn component_writer_serializes_list_in_stable_order() -> XmlResult<()> {
let document = document(
element("Items")?.children(
["c", "a", "b"]
.into_iter()
.map(|code| element("Item")?.attr("code", code)?.text(code)),
)?,
)?;
let first = to_string_compact(&document)?;
let second = to_string_compact(&document)?;
assert_eq!(first, second);
assert_eq!(
first,
"<Items><Item code=\"c\">c</Item><Item code=\"a\">a</Item><Item code=\"b\">b</Item></Items>"
);
Ok(())
}
#[test]
fn component_writer_pretty_output_is_stable() -> XmlResult<()> {
let content = children(pair_component()?)?;
let document = document(wrapper(content)?)?;
let xml = to_string_pretty(&document, WriterConfig::pretty())?;
assert_eq!(
xml,
"<Wrapper>\n <First>one</First>\n <Second>two</Second>\n</Wrapper>"
);
Ok(())
}
}