1use crate::builder::{DocumentBuilder, ElementBuilder};
9use crate::core::{Document, XmlResult};
10
11pub use crate::builder::{
12 fragment, FragmentBuilder as Fragment, IntoXmlFragment, XmlNode as FragmentNode,
13};
14
15pub type Children = Fragment;
17
18pub trait IntoXml {
20 fn into_xml(self) -> XmlResult<ElementBuilder>;
21}
22
23impl IntoXml for ElementBuilder {
24 fn into_xml(self) -> XmlResult<ElementBuilder> {
25 Ok(self)
26 }
27}
28
29impl IntoXml for XmlResult<ElementBuilder> {
30 fn into_xml(self) -> XmlResult<ElementBuilder> {
31 self
32 }
33}
34
35pub fn document(component: impl IntoXml) -> XmlResult<Document> {
37 DocumentBuilder::new().root(component.into_xml()?)?.build()
38}
39
40pub fn children(value: impl IntoXmlFragment) -> XmlResult<Children> {
42 value.into_xml_fragment()
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48 use crate::builder::{element, text};
49 use crate::core::ErrorKind;
50 use crate::writer::{to_string_compact, to_string_pretty, WriterConfig};
51
52 fn simple_component() -> XmlResult<ElementBuilder> {
53 element("Simple")?.text("value")
54 }
55
56 fn wrapper(children: Children) -> XmlResult<ElementBuilder> {
57 element("Wrapper")?.child(children)
58 }
59
60 fn layout(title: impl IntoXml, children: Children) -> XmlResult<ElementBuilder> {
61 element("Layout")?.child(title.into_xml()?)?.child(children)
62 }
63
64 fn pair_component() -> XmlResult<Fragment> {
65 fragment()
66 .child(element("First")?.text("one")?)?
67 .child(element("Second")?.text("two")?)
68 }
69
70 fn item_list_from_vec(items: Vec<ItemProps>) -> XmlResult<ElementBuilder> {
71 element("Items")?.child(
72 items
73 .into_iter()
74 .map(item_component)
75 .collect::<Vec<XmlResult<ElementBuilder>>>(),
76 )
77 }
78
79 #[derive(Debug, Clone)]
80 struct IdProps {
81 value: String,
82 }
83
84 fn id_component(props: IdProps) -> XmlResult<ElementBuilder> {
85 element("ID")?.text(props.value)
86 }
87
88 #[derive(Debug, Clone)]
89 struct ItemProps {
90 code: String,
91 name: String,
92 quantity: u32,
93 }
94
95 fn item_component(props: ItemProps) -> XmlResult<ElementBuilder> {
96 element("Item")?
97 .attr("code", props.code)?
98 .attr("quantity", props.quantity)?
99 .text(props.name)
100 }
101
102 #[derive(Debug, Clone)]
103 struct QualifiedElementProps {
104 prefix: String,
105 local_name: String,
106 namespace_uri: String,
107 text: String,
108 }
109
110 fn qualified_component(props: QualifiedElementProps) -> XmlResult<ElementBuilder> {
111 ElementBuilder::qualified(props.prefix, props.local_name, props.namespace_uri)?
112 .text(props.text)
113 }
114
115 #[test]
116 fn component_contracts_into_xml_accepts_element_builder() -> XmlResult<()> {
117 let root = element("Root")?.text("ok")?.into_xml()?;
118 let document = document(root)?;
119
120 assert_eq!(to_string_compact(&document)?, "<Root>ok</Root>");
121 Ok(())
122 }
123
124 #[test]
125 fn into_xml_accepts_component_result() -> XmlResult<()> {
126 let document = document(simple_component())?;
127
128 assert_eq!(to_string_compact(&document)?, "<Simple>value</Simple>");
129 Ok(())
130 }
131
132 #[test]
133 fn component_contracts_into_xml_fragment_is_available() -> XmlResult<()> {
134 let fragment = text("hello").into_xml_fragment()?;
135 let document = document(element("Root")?.child(fragment)?)?;
136
137 assert_eq!(to_string_compact(&document)?, "<Root>hello</Root>");
138 Ok(())
139 }
140
141 #[test]
142 fn component_contracts_children_are_explicit_fragments() -> XmlResult<()> {
143 let children = children(fragment().child(element("Child")?.text("value")?)?)?;
144 let document = document(wrapper(children)?)?;
145
146 assert_eq!(
147 to_string_compact(&document)?,
148 "<Wrapper><Child>value</Child></Wrapper>"
149 );
150 Ok(())
151 }
152
153 #[test]
154 fn component_contracts_materialize_with_builder_backend() -> XmlResult<()> {
155 let root = element("Root")?.child(simple_component())?;
156 let document = DocumentBuilder::new().root(root)?.build()?;
157
158 assert_eq!(
159 to_string_compact(&document)?,
160 "<Root><Simple>value</Simple></Root>"
161 );
162 Ok(())
163 }
164
165 #[test]
166 fn component_props_create_xml_with_typed_props() -> XmlResult<()> {
167 let props = ItemProps {
168 code: "A1".to_owned(),
169 name: "Widget".to_owned(),
170 quantity: 3,
171 };
172 let document = document(item_component(props)?)?;
173
174 assert_eq!(
175 to_string_compact(&document)?,
176 "<Item code=\"A1\" quantity=\"3\">Widget</Item>"
177 );
178 Ok(())
179 }
180
181 #[test]
182 fn component_props_can_build_text_from_props() -> XmlResult<()> {
183 let props = IdProps {
184 value: "INV-1".to_owned(),
185 };
186 let document = document(id_component(props)?)?;
187
188 assert_eq!(to_string_compact(&document)?, "<ID>INV-1</ID>");
189 Ok(())
190 }
191
192 #[test]
193 fn component_props_can_build_qualified_names() -> XmlResult<()> {
194 let props = QualifiedElementProps {
195 prefix: "doc".to_owned(),
196 local_name: "Title".to_owned(),
197 namespace_uri: "urn:doc".to_owned(),
198 text: "Report".to_owned(),
199 };
200 let document = document(qualified_component(props)?)?;
201
202 assert_eq!(
203 to_string_compact(&document)?,
204 "<doc:Title>Report</doc:Title>"
205 );
206 Ok(())
207 }
208
209 #[test]
210 fn component_props_invalid_names_return_xml_error() {
211 let props = QualifiedElementProps {
212 prefix: "doc".to_owned(),
213 local_name: "1Invalid".to_owned(),
214 namespace_uri: "urn:doc".to_owned(),
215 text: "Report".to_owned(),
216 };
217 let error = qualified_component(props).expect_err("invalid local name must fail");
218
219 assert_eq!(error.kind(), &ErrorKind::InvalidName);
220 }
221
222 #[test]
223 fn children_component_wraps_explicit_children() -> XmlResult<()> {
224 let content = children(
225 fragment()
226 .child(element("A")?.text("one")?)?
227 .child(element("B")?.text("two")?)?,
228 )?;
229 let document = document(wrapper(content)?)?;
230
231 assert_eq!(
232 to_string_compact(&document)?,
233 "<Wrapper><A>one</A><B>two</B></Wrapper>"
234 );
235 Ok(())
236 }
237
238 #[test]
239 fn children_fragment_component_returns_multiple_nodes() -> XmlResult<()> {
240 let document = document(element("Root")?.child(pair_component()?)?)?;
241
242 assert_eq!(
243 to_string_compact(&document)?,
244 "<Root><First>one</First><Second>two</Second></Root>"
245 );
246 Ok(())
247 }
248
249 #[test]
250 fn children_components_compose_from_plain_rust() -> XmlResult<()> {
251 let title = id_component(IdProps {
252 value: "T-1".to_owned(),
253 })?;
254 let content = children(pair_component()?)?;
255 let document = document(layout(title, content)?)?;
256
257 assert_eq!(
258 to_string_compact(&document)?,
259 "<Layout><ID>T-1</ID><First>one</First><Second>two</Second></Layout>"
260 );
261 Ok(())
262 }
263
264 #[test]
265 fn component_collections_option_can_include_content() -> XmlResult<()> {
266 let include = true;
267 let optional_child = include.then(simple_component);
268 let document = document(element("Root")?.child(optional_child.transpose()?)?)?;
269
270 assert_eq!(
271 to_string_compact(&document)?,
272 "<Root><Simple>value</Simple></Root>"
273 );
274 Ok(())
275 }
276
277 #[test]
278 fn component_collections_option_can_omit_content() -> XmlResult<()> {
279 let include = false;
280 let optional_child = include.then(simple_component);
281 let document = document(element("Root")?.child(optional_child.transpose()?)?)?;
282
283 assert_eq!(to_string_compact(&document)?, "<Root/>");
284 Ok(())
285 }
286
287 #[test]
288 fn component_collections_vec_represents_list_nodes() -> XmlResult<()> {
289 let items = vec![
290 ItemProps {
291 code: "a".to_owned(),
292 name: "Alpha".to_owned(),
293 quantity: 1,
294 },
295 ItemProps {
296 code: "b".to_owned(),
297 name: "Beta".to_owned(),
298 quantity: 2,
299 },
300 ];
301 let document = document(item_list_from_vec(items)?)?;
302
303 assert_eq!(
304 to_string_compact(&document)?,
305 "<Items><Item code=\"a\" quantity=\"1\">Alpha</Item><Item code=\"b\" quantity=\"2\">Beta</Item></Items>"
306 );
307 Ok(())
308 }
309
310 #[test]
311 fn component_collections_iterators_preserve_order() -> XmlResult<()> {
312 let document = document(
313 element("Items")?.children(
314 ["a", "b", "c"]
315 .into_iter()
316 .map(|code| element("Item")?.attr("code", code)?.text(code)),
317 )?,
318 )?;
319
320 assert_eq!(
321 to_string_compact(&document)?,
322 "<Items><Item code=\"a\">a</Item><Item code=\"b\">b</Item><Item code=\"c\">c</Item></Items>"
323 );
324 Ok(())
325 }
326
327 #[test]
328 fn component_writer_serializes_simple_component_compact() -> XmlResult<()> {
329 let document = document(simple_component())?;
330
331 assert_eq!(to_string_compact(&document)?, "<Simple>value</Simple>");
332 Ok(())
333 }
334
335 #[test]
336 fn component_writer_serializes_props_component_compact() -> XmlResult<()> {
337 let document = document(item_component(ItemProps {
338 code: "x".to_owned(),
339 name: "Xray".to_owned(),
340 quantity: 7,
341 })?)?;
342
343 assert_eq!(
344 to_string_compact(&document)?,
345 "<Item code=\"x\" quantity=\"7\">Xray</Item>"
346 );
347 Ok(())
348 }
349
350 #[test]
351 fn component_writer_serializes_children_component_compact() -> XmlResult<()> {
352 let content = children(pair_component()?)?;
353 let document = document(wrapper(content)?)?;
354
355 assert_eq!(
356 to_string_compact(&document)?,
357 "<Wrapper><First>one</First><Second>two</Second></Wrapper>"
358 );
359 Ok(())
360 }
361
362 #[test]
363 fn component_writer_serializes_list_in_stable_order() -> XmlResult<()> {
364 let document = document(
365 element("Items")?.children(
366 ["c", "a", "b"]
367 .into_iter()
368 .map(|code| element("Item")?.attr("code", code)?.text(code)),
369 )?,
370 )?;
371
372 let first = to_string_compact(&document)?;
373 let second = to_string_compact(&document)?;
374
375 assert_eq!(first, second);
376 assert_eq!(
377 first,
378 "<Items><Item code=\"c\">c</Item><Item code=\"a\">a</Item><Item code=\"b\">b</Item></Items>"
379 );
380 Ok(())
381 }
382
383 #[test]
384 fn component_writer_pretty_output_is_stable() -> XmlResult<()> {
385 let content = children(pair_component()?)?;
386 let document = document(wrapper(content)?)?;
387
388 let xml = to_string_pretty(&document, WriterConfig::pretty())?;
389
390 assert_eq!(
391 xml,
392 "<Wrapper>\n <First>one</First>\n <Second>two</Second>\n</Wrapper>"
393 );
394 Ok(())
395 }
396}