1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7mod helpers {
8 // we isolate the helpers into a module, because we do not want to have
9 // them in scope below.
10 // this is to ensure that the macros do not have hidden dependencies on
11 // any specific names being imported.
12 use minidom::Element;
13 use xso::{error::Error, transform, AsXml, FromXml};
14
15 pub(super) fn roundtrip_full<T: AsXml + FromXml + PartialEq + core::fmt::Debug + Clone>(
16 s: &str,
17 ) {
18 let initial: Element = s.parse().unwrap();
19 let structural: T = match transform(&initial) {
20 Ok(v) => v,
21 Err(e) => panic!("failed to parse from {:?}: {}", s, e),
22 };
23 let recovered = transform(&structural).expect("roundtrip did not produce an element");
24 assert_eq!(initial, recovered);
25 let structural2: T = match transform(&recovered) {
26 Ok(v) => v,
27 Err(e) => panic!("failed to parse from serialisation of {:?}: {}", s, e),
28 };
29 assert_eq!(structural, structural2);
30 }
31
32 pub(super) fn parse_str<T: FromXml>(s: &str) -> Result<T, Error> {
33 let initial: Element = s.parse().unwrap();
34 transform(&initial)
35 }
36}
37
38use self::helpers::{parse_str, roundtrip_full};
39
40use xso::exports::rxml;
41use xso::{AsXml, FromXml, PrintRawXml};
42
43// these are adversarial local names in order to trigger any issues with
44// unqualified names in the macro expansions.
45#[allow(dead_code, non_snake_case)]
46fn Err() {}
47#[allow(dead_code, non_snake_case)]
48fn Ok() {}
49#[allow(dead_code, non_snake_case)]
50fn Some() {}
51#[allow(dead_code, non_snake_case)]
52fn None() {}
53#[allow(dead_code)]
54type Option = ((),);
55#[allow(dead_code)]
56type Result = ((),);
57
58static NS1: &str = "urn:example:ns1";
59static NS2: &str = "urn:example:ns2";
60
61static FOO_NAME: &::xso::exports::rxml::strings::NcNameStr = {
62 #[allow(unsafe_code)]
63 unsafe {
64 ::xso::exports::rxml::strings::NcNameStr::from_str_unchecked("foo")
65 }
66};
67
68static BAR_NAME: &::xso::exports::rxml::strings::NcNameStr = {
69 #[allow(unsafe_code)]
70 unsafe {
71 ::xso::exports::rxml::strings::NcNameStr::from_str_unchecked("bar")
72 }
73};
74
75#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
76#[xml(namespace = NS1, name = "foo")]
77struct Empty;
78
79#[test]
80fn empty_roundtrip() {
81 #[allow(unused_imports)]
82 use core::{
83 option::Option::{None, Some},
84 result::Result::{Err, Ok},
85 };
86 roundtrip_full::<Empty>("<foo xmlns='urn:example:ns1'/>");
87}
88
89#[test]
90fn empty_xml_name_matcher_is_specific() {
91 assert_eq!(
92 Empty::xml_name_matcher(),
93 xso::fromxml::XmlNameMatcher::Specific(NS1, "foo")
94 );
95}
96
97#[test]
98fn empty_name_mismatch() {
99 #[allow(unused_imports)]
100 use core::{
101 option::Option::{None, Some},
102 result::Result::{Err, Ok},
103 };
104 match parse_str::<Empty>("<bar xmlns='urn:example:ns1'/>") {
105 Err(xso::error::Error::TypeMismatch) => (),
106 other => panic!("unexpected result: {:?}", other),
107 }
108}
109
110#[test]
111fn empty_namespace_mismatch() {
112 #[allow(unused_imports)]
113 use core::{
114 option::Option::{None, Some},
115 result::Result::{Err, Ok},
116 };
117 match parse_str::<Empty>("<foo xmlns='urn:example:ns2'/>") {
118 Err(xso::error::Error::TypeMismatch) => (),
119 other => panic!("unexpected result: {:?}", other),
120 }
121}
122
123#[test]
124#[cfg_attr(
125 feature = "disable-validation",
126 should_panic = "unexpected result: Ok("
127)]
128fn empty_unexpected_attribute() {
129 #[allow(unused_imports)]
130 use core::{
131 option::Option::{None, Some},
132 result::Result::{Err, Ok},
133 };
134 match parse_str::<Empty>("<foo xmlns='urn:example:ns1' fnord='bar'/>") {
135 Err(xso::error::Error::Other(e)) => {
136 assert_eq!(e, "Unknown attribute in Empty element.");
137 }
138 other => panic!("unexpected result: {:?}", other),
139 }
140}
141
142#[test]
143#[cfg_attr(
144 feature = "disable-validation",
145 should_panic = "unexpected result: Ok("
146)]
147fn empty_ignores_xml_lang() {
148 #[allow(unused_imports)]
149 use core::{
150 option::Option::{None, Some},
151 result::Result::{Err, Ok},
152 };
153 match parse_str::<Empty>("<foo xmlns='urn:example:ns1' xml:lang='bar'/>") {
154 Ok(Empty) => (),
155 other => panic!("unexpected result: {:?}", other),
156 }
157}
158
159#[test]
160#[cfg_attr(
161 feature = "disable-validation",
162 should_panic = "unexpected result: Ok("
163)]
164fn empty_unexpected_child() {
165 #[allow(unused_imports)]
166 use core::{
167 option::Option::{None, Some},
168 result::Result::{Err, Ok},
169 };
170 match parse_str::<Empty>("<foo xmlns='urn:example:ns1'><coucou/></foo>") {
171 Err(xso::error::Error::Other(e)) => {
172 assert_eq!(e, "Unknown child in Empty element.");
173 }
174 other => panic!("unexpected result: {:?}", other),
175 }
176}
177
178#[test]
179fn empty_qname_check_has_precedence_over_attr_check() {
180 #[allow(unused_imports)]
181 use core::{
182 option::Option::{None, Some},
183 result::Result::{Err, Ok},
184 };
185 match parse_str::<Empty>("<bar xmlns='urn:example:ns1' fnord='bar'/>") {
186 Err(xso::error::Error::TypeMismatch) => (),
187 other => panic!("unexpected result: {:?}", other),
188 }
189}
190
191#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
192#[xml(namespace = NS1, name = BAR_NAME)]
193struct NamePath;
194
195#[test]
196fn name_path_roundtrip() {
197 #[allow(unused_imports)]
198 use core::{
199 option::Option::{None, Some},
200 result::Result::{Err, Ok},
201 };
202 roundtrip_full::<NamePath>("<bar xmlns='urn:example:ns1'/>");
203}
204
205#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
206#[xml(namespace = "urn:example:ns2", name = "baz")]
207struct NamespaceLit;
208
209#[test]
210fn namespace_lit_roundtrip() {
211 #[allow(unused_imports)]
212 use core::{
213 option::Option::{None, Some},
214 result::Result::{Err, Ok},
215 };
216 roundtrip_full::<NamespaceLit>("<baz xmlns='urn:example:ns2'/>");
217}
218
219#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
220#[xml(namespace = NS1, name = "attr")]
221struct RequiredAttribute {
222 #[xml(attribute)]
223 foo: String,
224}
225
226#[test]
227fn required_attribute_roundtrip() {
228 #[allow(unused_imports)]
229 use core::{
230 option::Option::{None, Some},
231 result::Result::{Err, Ok},
232 };
233 roundtrip_full::<RequiredAttribute>("<attr xmlns='urn:example:ns1' foo='bar'/>");
234}
235
236#[test]
237fn required_attribute_positive() {
238 #[allow(unused_imports)]
239 use core::{
240 option::Option::{None, Some},
241 result::Result::{Err, Ok},
242 };
243 let data = parse_str::<RequiredAttribute>("<attr xmlns='urn:example:ns1' foo='bar'/>").unwrap();
244 assert_eq!(data.foo, "bar");
245}
246
247#[test]
248fn required_attribute_missing() {
249 #[allow(unused_imports)]
250 use core::{
251 option::Option::{None, Some},
252 result::Result::{Err, Ok},
253 };
254 match parse_str::<RequiredAttribute>("<attr xmlns='urn:example:ns1'/>") {
255 Err(::xso::error::Error::Other(e))
256 if e.contains("Required attribute field") && e.contains("missing") =>
257 {
258 ()
259 }
260 other => panic!("unexpected result: {:?}", other),
261 }
262}
263
264#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
265#[xml(namespace = NS1, name = "attr")]
266struct RenamedAttribute {
267 #[xml(attribute = "a1")]
268 foo: String,
269 #[xml(attribute = BAR_NAME)]
270 bar: String,
271}
272
273#[test]
274fn renamed_attribute_roundtrip() {
275 #[allow(unused_imports)]
276 use core::{
277 option::Option::{None, Some},
278 result::Result::{Err, Ok},
279 };
280 roundtrip_full::<RenamedAttribute>("<attr xmlns='urn:example:ns1' a1='bar' bar='baz'/>");
281}
282
283#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
284#[xml(namespace = NS1, name = "attr")]
285struct NamespacedAttribute {
286 #[xml(attribute(namespace = "urn:example:ns1", name = FOO_NAME))]
287 foo_1: String,
288 #[xml(attribute(namespace = NS2, name = "foo"))]
289 foo_2: String,
290 #[xml(attribute(namespace = NS1, name = BAR_NAME))]
291 bar_1: String,
292 #[xml(attribute(namespace = "urn:example:ns2", name = "bar"))]
293 bar_2: String,
294}
295
296#[test]
297fn namespaced_attribute_roundtrip_a() {
298 #[allow(unused_imports)]
299 use core::{
300 option::Option::{None, Some},
301 result::Result::{Err, Ok},
302 };
303 roundtrip_full::<NamespacedAttribute>(
304 "<attr xmlns='urn:example:ns1'
305 xmlns:tns0='urn:example:ns1' tns0:foo='a1' tns0:bar='a3'
306 xmlns:tns1='urn:example:ns2' tns1:foo='a2' tns1:bar='a4'/>",
307 );
308}
309
310#[test]
311fn namespaced_attribute_roundtrip_b() {
312 #[allow(unused_imports)]
313 use core::{
314 option::Option::{None, Some},
315 result::Result::{Err, Ok},
316 };
317 roundtrip_full::<NamespacedAttribute>(
318 "<tns0:attr
319 xmlns:tns0='urn:example:ns1' tns0:foo='a1' tns0:bar='a3'
320 xmlns:tns1='urn:example:ns2' tns1:foo='a2' tns1:bar='a4'/>",
321 );
322}
323
324#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
325#[xml(namespace = NS1, name = "attr")]
326struct PrefixedAttribute {
327 #[xml(attribute = "xml:lang")]
328 lang: String,
329}
330
331#[test]
332fn prefixed_attribute_roundtrip() {
333 #[allow(unused_imports)]
334 use core::{
335 option::Option::{None, Some},
336 result::Result::{Err, Ok},
337 };
338 roundtrip_full::<PrefixedAttribute>("<attr xmlns='urn:example:ns1' xml:lang='foo'/>");
339}
340
341#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
342#[xml(namespace = NS1, name = "attr")]
343struct RequiredNonStringAttribute {
344 #[xml(attribute)]
345 foo: i32,
346}
347
348#[test]
349fn required_non_string_attribute_roundtrip() {
350 #[allow(unused_imports)]
351 use core::{
352 option::Option::{None, Some},
353 result::Result::{Err, Ok},
354 };
355 roundtrip_full::<RequiredNonStringAttribute>("<attr xmlns='urn:example:ns1' foo='-16'/>");
356}
357
358#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
359#[xml(namespace = NS1, name = "attr")]
360struct DefaultAttribute {
361 #[xml(attribute(default))]
362 foo: core::option::Option<String>,
363
364 #[xml(attribute(default))]
365 bar: core::option::Option<u16>,
366}
367
368#[test]
369fn default_attribute_roundtrip_aa() {
370 #[allow(unused_imports)]
371 use core::{
372 option::Option::{None, Some},
373 result::Result::{Err, Ok},
374 };
375 roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1'/>");
376}
377
378#[test]
379fn default_attribute_roundtrip_pa() {
380 #[allow(unused_imports)]
381 use core::{
382 option::Option::{None, Some},
383 result::Result::{Err, Ok},
384 };
385 roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' foo='xyz'/>");
386}
387
388#[test]
389fn default_attribute_roundtrip_ap() {
390 #[allow(unused_imports)]
391 use core::{
392 option::Option::{None, Some},
393 result::Result::{Err, Ok},
394 };
395 roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' bar='16'/>");
396}
397
398#[test]
399fn default_attribute_roundtrip_pp() {
400 #[allow(unused_imports)]
401 use core::{
402 option::Option::{None, Some},
403 result::Result::{Err, Ok},
404 };
405 roundtrip_full::<DefaultAttribute>("<attr xmlns='urn:example:ns1' foo='xyz' bar='16'/>");
406}
407
408#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
409#[xml(namespace = NS1, name = "attr")]
410struct AttributeWithCodec {
411 #[xml(attribute(default, codec = xso::text::EmptyAsNone))]
412 foo: core::option::Option<String>,
413}
414
415#[test]
416fn attribute_with_codec_is_none() {
417 #[allow(unused_imports)]
418 use core::{
419 option::Option::{None, Some},
420 result::Result::{Err, Ok},
421 };
422 let el = parse_str::<AttributeWithCodec>("<attr xmlns='urn:example:ns1'/>").unwrap();
423 assert_eq!(el.foo, None);
424 let el = parse_str::<AttributeWithCodec>("<attr xmlns='urn:example:ns1' foo=''/>").unwrap();
425 assert_eq!(el.foo, None);
426 let el = parse_str::<AttributeWithCodec>("<attr xmlns='urn:example:ns1' foo='bar'/>").unwrap();
427 assert_eq!(el.foo, Some(String::from("bar")));
428}
429
430#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
431#[xml(namespace = NS1, name = "attr")]
432struct AttributeWithBase64Codec {
433 #[xml(attribute(codec = xso::text::Base64))]
434 foo: Vec<u8>,
435}
436
437#[test]
438fn attribute_with_base64_codec_roundtrip() {
439 #[allow(unused_imports)]
440 use core::{
441 option::Option::{None, Some},
442 result::Result::{Err, Ok},
443 };
444 roundtrip_full::<AttributeWithBase64Codec>("<attr xmlns='urn:example:ns1' foo='AAAA'/>");
445}
446
447#[test]
448fn attribute_with_base64_codec_decodes() {
449 #[allow(unused_imports)]
450 use core::{
451 option::Option::{None, Some},
452 result::Result::{Err, Ok},
453 };
454 let el = parse_str::<AttributeWithBase64Codec>("<attr xmlns='urn:example:ns1' foo='AAAA'/>")
455 .unwrap();
456 assert_eq!(el.foo, [0, 0, 0]);
457}
458
459#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
460#[xml(namespace = NS1, name = "text")]
461struct TextString {
462 #[xml(text)]
463 text: String,
464}
465
466#[test]
467fn text_string_roundtrip() {
468 #[allow(unused_imports)]
469 use core::{
470 option::Option::{None, Some},
471 result::Result::{Err, Ok},
472 };
473 roundtrip_full::<TextString>("<text xmlns='urn:example:ns1'>hello world!</text>");
474}
475
476#[test]
477fn text_string_positive_preserves_whitespace() {
478 #[allow(unused_imports)]
479 use core::{
480 option::Option::{None, Some},
481 result::Result::{Err, Ok},
482 };
483 let el = parse_str::<TextString>("<text xmlns='urn:example:ns1'> \t\n</text>").unwrap();
484 assert_eq!(el.text, " \t\n");
485}
486
487#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
488#[xml(namespace = NS1, name = "text")]
489struct TextNonString {
490 #[xml(text)]
491 text: u32,
492}
493
494#[test]
495fn text_non_string_roundtrip() {
496 #[allow(unused_imports)]
497 use core::{
498 option::Option::{None, Some},
499 result::Result::{Err, Ok},
500 };
501 roundtrip_full::<TextNonString>("<text xmlns='urn:example:ns1'>123456</text>");
502}
503
504#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
505#[xml(namespace = NS1, name = "elem")]
506struct IgnoresWhitespaceWithoutTextConsumer;
507
508#[test]
509fn ignores_whitespace_without_text_consumer_positive() {
510 #[allow(unused_imports)]
511 use core::{
512 option::Option::{None, Some},
513 result::Result::{Err, Ok},
514 };
515 let _ = parse_str::<IgnoresWhitespaceWithoutTextConsumer>(
516 "<elem xmlns='urn:example:ns1'> \t\r\n</elem>",
517 )
518 .unwrap();
519}
520
521#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
522#[xml(namespace = NS1, name = "elem")]
523struct FailsTextWithoutTextConsumer;
524
525#[test]
526fn fails_text_without_text_consumer_positive() {
527 #[allow(unused_imports)]
528 use core::{
529 option::Option::{None, Some},
530 result::Result::{Err, Ok},
531 };
532 match parse_str::<FailsTextWithoutTextConsumer>("<elem xmlns='urn:example:ns1'> quak </elem>")
533 {
534 Err(::xso::error::Error::Other(e)) if e.contains("Unexpected text") => (),
535 other => panic!("unexpected result: {:?}", other),
536 }
537}
538
539#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
540#[xml(namespace = NS1, name = "text")]
541struct TextWithCodec {
542 #[xml(text(codec = xso::text::EmptyAsNone))]
543 text: core::option::Option<String>,
544}
545
546#[test]
547fn text_with_codec_roundtrip_empty() {
548 #[allow(unused_imports)]
549 use core::{
550 option::Option::{None, Some},
551 result::Result::{Err, Ok},
552 };
553 roundtrip_full::<TextWithCodec>("<text xmlns='urn:example:ns1'/>");
554}
555
556#[test]
557fn text_with_codec_roundtrip_non_empty() {
558 #[allow(unused_imports)]
559 use core::{
560 option::Option::{None, Some},
561 result::Result::{Err, Ok},
562 };
563 roundtrip_full::<TextWithCodec>("<text xmlns='urn:example:ns1'>hello</text>");
564}
565
566#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
567#[xml(namespace = NS1, name = "parent")]
568struct Parent {
569 #[xml(child)]
570 child: RequiredAttribute,
571}
572
573#[test]
574fn parent_roundtrip() {
575 #[allow(unused_imports)]
576 use core::{
577 option::Option::{None, Some},
578 result::Result::{Err, Ok},
579 };
580 roundtrip_full::<Parent>("<parent xmlns='urn:example:ns1'><attr foo='hello world!'/></parent>")
581}
582
583#[test]
584fn parent_positive() {
585 #[allow(unused_imports)]
586 use core::{
587 option::Option::{None, Some},
588 result::Result::{Err, Ok},
589 };
590 let v =
591 parse_str::<Parent>("<parent xmlns='urn:example:ns1'><attr foo='hello world!'/></parent>")
592 .unwrap();
593 assert_eq!(v.child.foo, "hello world!");
594}
595
596#[test]
597fn parent_negative_duplicate_child() {
598 #[allow(unused_imports)]
599 use core::{
600 option::Option::{None, Some},
601 result::Result::{Err, Ok},
602 };
603 match parse_str::<Parent>("<parent xmlns='urn:example:ns1'><attr foo='hello world!'/><attr foo='hello world!'/></parent>") {
604 Err(::xso::error::Error::Other(e)) if e.contains("must not have more than one") => (),
605 other => panic!("unexpected result: {:?}", other),
606 }
607}
608
609#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
610#[xml(namespace = NS1, name = "parent")]
611struct OptionalChild {
612 #[xml(child(default))]
613 child: core::option::Option<RequiredAttribute>,
614}
615
616#[test]
617fn optional_child_roundtrip_present() {
618 #[allow(unused_imports)]
619 use core::{
620 option::Option::{None, Some},
621 result::Result::{Err, Ok},
622 };
623 roundtrip_full::<OptionalChild>(
624 "<parent xmlns='urn:example:ns1'><attr foo='hello world!'/></parent>",
625 )
626}
627
628#[test]
629fn optional_child_roundtrip_absent() {
630 #[allow(unused_imports)]
631 use core::{
632 option::Option::{None, Some},
633 result::Result::{Err, Ok},
634 };
635 roundtrip_full::<OptionalChild>("<parent xmlns='urn:example:ns1'/>")
636}
637
638#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
639#[xml(namespace = NS1, name = "elem")]
640struct BoxedChild {
641 #[xml(child(default))]
642 child: core::option::Option<Box<BoxedChild>>,
643}
644
645#[test]
646fn boxed_child_roundtrip_absent() {
647 #[allow(unused_imports)]
648 use core::{
649 option::Option::{None, Some},
650 result::Result::{Err, Ok},
651 };
652 roundtrip_full::<BoxedChild>("<elem xmlns='urn:example:ns1'/>")
653}
654
655#[test]
656fn boxed_child_roundtrip_nested_1() {
657 #[allow(unused_imports)]
658 use core::{
659 option::Option::{None, Some},
660 result::Result::{Err, Ok},
661 };
662 roundtrip_full::<BoxedChild>("<elem xmlns='urn:example:ns1'><elem/></elem>")
663}
664
665#[test]
666fn boxed_child_roundtrip_nested_2() {
667 #[allow(unused_imports)]
668 use core::{
669 option::Option::{None, Some},
670 result::Result::{Err, Ok},
671 };
672 roundtrip_full::<BoxedChild>("<elem xmlns='urn:example:ns1'><elem><elem/></elem></elem>")
673}
674
675#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
676#[xml(namespace = NS1, name = "elem", builder = RenamedBuilder, iterator = RenamedIter)]
677struct RenamedTypes;
678
679#[test]
680fn renamed_types_roundtrip() {
681 #[allow(unused_imports)]
682 use core::{
683 option::Option::{None, Some},
684 result::Result::{Err, Ok},
685 };
686 roundtrip_full::<RenamedTypes>("<elem xmlns='urn:example:ns1'/>")
687}
688
689#[test]
690#[allow(unused_comparisons)]
691fn renamed_types_get_renamed() {
692 // these merely serve as a test that the types are declared with the names
693 // given in the attributes.
694 assert!(core::mem::size_of::<RenamedBuilder>() >= 0);
695 assert!(core::mem::size_of::<RenamedIter>() >= 0);
696}
697
698// What is this, you may wonder?
699// This is a test that any generated type names won't trigger
700// the `non_camel_case_types` lint.
701#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
702#[xml(namespace = NS1, name = "elem")]
703struct LintTest_;
704
705#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
706#[xml(namespace = NS1)]
707enum NameSwitchedEnum {
708 #[xml(name = "a")]
709 Variant1 {
710 #[xml(attribute)]
711 foo: String,
712 },
713 #[xml(name = "b")]
714 Variant2 {
715 #[xml(text)]
716 foo: String,
717 },
718}
719
720#[test]
721fn name_switched_enum_matcher_is_in_namespace() {
722 assert_eq!(
723 NameSwitchedEnum::xml_name_matcher(),
724 xso::fromxml::XmlNameMatcher::InNamespace(NS1)
725 );
726}
727
728#[test]
729fn name_switched_enum_positive_variant_1() {
730 #[allow(unused_imports)]
731 use core::{
732 option::Option::{None, Some},
733 result::Result::{Err, Ok},
734 };
735 match parse_str::<NameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>") {
736 Ok(NameSwitchedEnum::Variant1 { foo }) => {
737 assert_eq!(foo, "hello");
738 }
739 other => panic!("unexpected result: {:?}", other),
740 }
741}
742
743#[test]
744fn name_switched_enum_positive_variant_2() {
745 #[allow(unused_imports)]
746 use core::{
747 option::Option::{None, Some},
748 result::Result::{Err, Ok},
749 };
750 match parse_str::<NameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>") {
751 Ok(NameSwitchedEnum::Variant2 { foo }) => {
752 assert_eq!(foo, "hello");
753 }
754 other => panic!("unexpected result: {:?}", other),
755 }
756}
757
758#[test]
759fn name_switched_enum_negative_name_mismatch() {
760 #[allow(unused_imports)]
761 use core::{
762 option::Option::{None, Some},
763 result::Result::{Err, Ok},
764 };
765 match parse_str::<NameSwitchedEnum>("<x xmlns='urn:example:ns1'>hello</x>") {
766 Err(xso::error::Error::TypeMismatch) => (),
767 other => panic!("unexpected result: {:?}", other),
768 }
769}
770
771#[test]
772fn name_switched_enum_negative_namespace_mismatch() {
773 #[allow(unused_imports)]
774 use core::{
775 option::Option::{None, Some},
776 result::Result::{Err, Ok},
777 };
778 match parse_str::<NameSwitchedEnum>("<b xmlns='urn:example:ns2'>hello</b>") {
779 Err(xso::error::Error::TypeMismatch) => (),
780 other => panic!("unexpected result: {:?}", other),
781 }
782}
783
784#[test]
785fn name_switched_enum_roundtrip_variant_1() {
786 #[allow(unused_imports)]
787 use core::{
788 option::Option::{None, Some},
789 result::Result::{Err, Ok},
790 };
791 roundtrip_full::<NameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>")
792}
793
794#[test]
795fn name_switched_enum_roundtrip_variant_2() {
796 #[allow(unused_imports)]
797 use core::{
798 option::Option::{None, Some},
799 result::Result::{Err, Ok},
800 };
801 roundtrip_full::<NameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>")
802}
803
804#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
805#[xml(namespace = NS1, builder = RenamedEnumBuilder, iterator = RenamedEnumIter)]
806enum RenamedEnumTypes {
807 #[xml(name = "elem")]
808 A,
809}
810
811#[test]
812fn renamed_enum_types_roundtrip() {
813 #[allow(unused_imports)]
814 use core::{
815 option::Option::{None, Some},
816 result::Result::{Err, Ok},
817 };
818 roundtrip_full::<RenamedEnumTypes>("<elem xmlns='urn:example:ns1'/>")
819}
820
821#[test]
822#[allow(unused_comparisons)]
823fn renamed_enum_types_get_renamed() {
824 // these merely serve as a test that the types are declared with the names
825 // given in the attributes.
826 assert!(core::mem::size_of::<RenamedEnumBuilder>() >= 0);
827 assert!(core::mem::size_of::<RenamedEnumIter>() >= 0);
828}
829
830#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
831#[xml(namespace = NS1, exhaustive)]
832enum ExhaustiveNameSwitchedEnum {
833 #[xml(name = "a")]
834 Variant1 {
835 #[xml(attribute)]
836 foo: String,
837 },
838 #[xml(name = "b")]
839 Variant2 {
840 #[xml(text)]
841 foo: String,
842 },
843}
844
845#[test]
846fn exhaustive_name_switched_enum_negative_name_mismatch() {
847 #[allow(unused_imports)]
848 use core::{
849 option::Option::{None, Some},
850 result::Result::{Err, Ok},
851 };
852 match parse_str::<ExhaustiveNameSwitchedEnum>("<x xmlns='urn:example:ns1'>hello</x>") {
853 Err(xso::error::Error::TypeMismatch) => {
854 panic!("unexpected result: {:?}", xso::error::Error::TypeMismatch)
855 }
856 Err(_) => (),
857 other => panic!("unexpected result: {:?}", other),
858 }
859}
860
861#[test]
862fn exhaustive_name_switched_enum_negative_namespace_mismatch() {
863 #[allow(unused_imports)]
864 use core::{
865 option::Option::{None, Some},
866 result::Result::{Err, Ok},
867 };
868 match parse_str::<ExhaustiveNameSwitchedEnum>("<b xmlns='urn:example:ns2'>hello</b>") {
869 Err(xso::error::Error::TypeMismatch) => (),
870 other => panic!("unexpected result: {:?}", other),
871 }
872}
873
874#[test]
875fn exhaustive_name_switched_enum_roundtrip_variant_1() {
876 #[allow(unused_imports)]
877 use core::{
878 option::Option::{None, Some},
879 result::Result::{Err, Ok},
880 };
881 roundtrip_full::<ExhaustiveNameSwitchedEnum>("<a xmlns='urn:example:ns1' foo='hello'/>")
882}
883
884#[test]
885fn exhaustive_name_switched_enum_roundtrip_variant_2() {
886 #[allow(unused_imports)]
887 use core::{
888 option::Option::{None, Some},
889 result::Result::{Err, Ok},
890 };
891 roundtrip_full::<ExhaustiveNameSwitchedEnum>("<b xmlns='urn:example:ns1'>hello</b>")
892}
893
894#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
895#[xml(namespace = NS1, name = "parent")]
896struct Children {
897 #[xml(child(n = ..))]
898 foo: Vec<RequiredAttribute>,
899}
900
901#[test]
902fn children_roundtrip() {
903 #[allow(unused_imports)]
904 use core::{
905 option::Option::{None, Some},
906 result::Result::{Err, Ok},
907 };
908 roundtrip_full::<Children>(
909 "<parent xmlns='urn:example:ns1'><attr foo='X'/><attr foo='Y'/><attr foo='Z'/></parent>",
910 )
911}
912
913#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
914#[xml(namespace = NS1, name = "parent")]
915struct TextExtract {
916 #[xml(extract(namespace = NS1, name = "child", fields(text)))]
917 contents: String,
918}
919
920#[test]
921fn text_extract_positive() {
922 #[allow(unused_imports)]
923 use core::{
924 option::Option::{None, Some},
925 result::Result::{Err, Ok},
926 };
927 match parse_str::<TextExtract>(
928 "<parent xmlns='urn:example:ns1'><child>hello world</child></parent>",
929 ) {
930 Ok(TextExtract { contents }) => {
931 assert_eq!(contents, "hello world");
932 }
933 other => panic!("unexpected result: {:?}", other),
934 }
935}
936
937#[test]
938fn text_extract_negative_absent_child() {
939 #[allow(unused_imports)]
940 use core::{
941 option::Option::{None, Some},
942 result::Result::{Err, Ok},
943 };
944 match parse_str::<TextExtract>("<parent xmlns='urn:example:ns1'/>") {
945 Err(xso::error::Error::Other(e)) if e.contains("Missing child field") => (),
946 other => panic!("unexpected result: {:?}", other),
947 }
948}
949
950#[test]
951#[cfg_attr(
952 feature = "disable-validation",
953 should_panic = "unexpected result: Ok("
954)]
955fn text_extract_negative_unexpected_attribute_in_child() {
956 #[allow(unused_imports)]
957 use core::{
958 option::Option::{None, Some},
959 result::Result::{Err, Ok},
960 };
961 match parse_str::<TextExtract>("<parent xmlns='urn:example:ns1'><child foo='bar'/></parent>") {
962 Err(xso::error::Error::Other(e)) if e.contains("Unknown attribute") => (),
963 other => panic!("unexpected result: {:?}", other),
964 }
965}
966
967#[test]
968#[cfg_attr(
969 feature = "disable-validation",
970 should_panic = "unexpected result: Ok("
971)]
972fn text_extract_negative_unexpected_child_in_child() {
973 #[allow(unused_imports)]
974 use core::{
975 option::Option::{None, Some},
976 result::Result::{Err, Ok},
977 };
978 match parse_str::<TextExtract>(
979 "<parent xmlns='urn:example:ns1'><child><quak/></child></parent>",
980 ) {
981 Err(xso::error::Error::Other(e)) if e.contains("Unknown child in extraction") => (),
982 other => panic!("unexpected result: {:?}", other),
983 }
984}
985
986#[test]
987fn text_extract_negative_duplicate_child() {
988 #[allow(unused_imports)]
989 use core::{
990 option::Option::{None, Some},
991 result::Result::{Err, Ok},
992 };
993 match parse_str::<TextExtract>(
994 "<parent xmlns='urn:example:ns1'><child>hello world</child><child>more</child></parent>",
995 ) {
996 Err(xso::error::Error::Other(e)) if e.contains("must not have more than one") => (),
997 other => panic!("unexpected result: {:?}", other),
998 }
999}
1000
1001#[test]
1002fn text_extract_roundtrip() {
1003 #[allow(unused_imports)]
1004 use core::{
1005 option::Option::{None, Some},
1006 result::Result::{Err, Ok},
1007 };
1008 roundtrip_full::<TextExtract>(
1009 "<parent xmlns='urn:example:ns1'><child>hello world!</child></parent>",
1010 )
1011}
1012
1013#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1014#[xml(namespace = NS1, name = "parent")]
1015struct AttributeExtract {
1016 #[xml(extract(namespace = NS1, name = "child", fields(attribute = "foo")))]
1017 contents: String,
1018}
1019
1020#[test]
1021fn attribute_extract_positive() {
1022 #[allow(unused_imports)]
1023 use core::{
1024 option::Option::{None, Some},
1025 result::Result::{Err, Ok},
1026 };
1027 match parse_str::<AttributeExtract>(
1028 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1029 ) {
1030 Ok(AttributeExtract { contents }) => {
1031 assert_eq!(contents, "hello world");
1032 }
1033 other => panic!("unexpected result: {:?}", other),
1034 }
1035}
1036
1037#[test]
1038fn attribute_extract_negative_absent_attribute() {
1039 #[allow(unused_imports)]
1040 use core::{
1041 option::Option::{None, Some},
1042 result::Result::{Err, Ok},
1043 };
1044 match parse_str::<AttributeExtract>("<parent xmlns='urn:example:ns1'><child/></parent>") {
1045 Err(xso::error::Error::Other(e)) if e.contains("Required attribute") => (),
1046 other => panic!("unexpected result: {:?}", other),
1047 }
1048}
1049
1050#[test]
1051fn attribute_extract_negative_unexpected_text_in_child() {
1052 #[allow(unused_imports)]
1053 use core::{
1054 option::Option::{None, Some},
1055 result::Result::{Err, Ok},
1056 };
1057 match parse_str::<AttributeExtract>(
1058 "<parent xmlns='urn:example:ns1'><child foo='hello world'>fnord</child></parent>",
1059 ) {
1060 Err(xso::error::Error::Other(e)) if e.contains("Unexpected text") => (),
1061 other => panic!("unexpected result: {:?}", other),
1062 }
1063}
1064
1065#[test]
1066fn attribute_extract_roundtrip() {
1067 #[allow(unused_imports)]
1068 use core::{
1069 option::Option::{None, Some},
1070 result::Result::{Err, Ok},
1071 };
1072 roundtrip_full::<AttributeExtract>(
1073 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1074 )
1075}
1076
1077#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1078#[xml(namespace = NS1, name = "parent")]
1079struct OptionalAttributeExtract {
1080 #[xml(extract(namespace = NS1, name = "child", fields(attribute(name = "foo", default))))]
1081 contents: ::core::option::Option<String>,
1082}
1083
1084#[test]
1085fn optional_attribute_extract_positive_present() {
1086 #[allow(unused_imports)]
1087 use core::{
1088 option::Option::{None, Some},
1089 result::Result::{Err, Ok},
1090 };
1091 match parse_str::<OptionalAttributeExtract>(
1092 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1093 ) {
1094 Ok(OptionalAttributeExtract {
1095 contents: Some(contents),
1096 }) => {
1097 assert_eq!(contents, "hello world");
1098 }
1099 other => panic!("unexpected result: {:?}", other),
1100 }
1101}
1102
1103#[test]
1104fn optional_attribute_extract_positive_present_empty() {
1105 #[allow(unused_imports)]
1106 use core::{
1107 option::Option::{None, Some},
1108 result::Result::{Err, Ok},
1109 };
1110 match parse_str::<OptionalAttributeExtract>(
1111 "<parent xmlns='urn:example:ns1'><child foo=''/></parent>",
1112 ) {
1113 Ok(OptionalAttributeExtract {
1114 contents: Some(contents),
1115 }) => {
1116 assert_eq!(contents, "");
1117 }
1118 other => panic!("unexpected result: {:?}", other),
1119 }
1120}
1121
1122#[test]
1123fn optional_attribute_extract_positive_absent() {
1124 #[allow(unused_imports)]
1125 use core::{
1126 option::Option::{None, Some},
1127 result::Result::{Err, Ok},
1128 };
1129 match parse_str::<OptionalAttributeExtract>("<parent xmlns='urn:example:ns1'><child/></parent>")
1130 {
1131 Ok(OptionalAttributeExtract { contents: None }) => (),
1132 other => panic!("unexpected result: {:?}", other),
1133 }
1134}
1135
1136#[test]
1137fn optional_attribute_extract_roundtrip_present() {
1138 #[allow(unused_imports)]
1139 use core::{
1140 option::Option::{None, Some},
1141 result::Result::{Err, Ok},
1142 };
1143 roundtrip_full::<OptionalAttributeExtract>(
1144 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1145 )
1146}
1147
1148#[test]
1149fn optional_attribute_extract_roundtrip_present_empty() {
1150 #[allow(unused_imports)]
1151 use core::{
1152 option::Option::{None, Some},
1153 result::Result::{Err, Ok},
1154 };
1155 roundtrip_full::<OptionalAttributeExtract>(
1156 "<parent xmlns='urn:example:ns1'><child foo=''/></parent>",
1157 )
1158}
1159
1160#[test]
1161fn optional_attribute_extract_roundtrip_absent() {
1162 #[allow(unused_imports)]
1163 use core::{
1164 option::Option::{None, Some},
1165 result::Result::{Err, Ok},
1166 };
1167 roundtrip_full::<OptionalAttributeExtract>("<parent xmlns='urn:example:ns1'><child/></parent>")
1168}
1169
1170#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1171#[xml(namespace = NS1, name = "parent")]
1172struct ChildExtract {
1173 #[xml(extract(namespace = NS1, name = "child", fields(child)))]
1174 contents: RequiredAttribute,
1175}
1176
1177#[test]
1178fn child_extract_roundtrip() {
1179 #[allow(unused_imports)]
1180 use core::{
1181 option::Option::{None, Some},
1182 result::Result::{Err, Ok},
1183 };
1184 roundtrip_full::<ChildExtract>(
1185 "<parent xmlns='urn:example:ns1'><child><attr foo='hello world!'/></child></parent>",
1186 )
1187}
1188
1189#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1190#[xml(namespace = NS1, name = "parent")]
1191struct NestedExtract {
1192 #[xml(extract(namespace = NS1, name = "child", fields(
1193 extract(namespace = NS1, name = "grandchild", fields(text))
1194 )))]
1195 contents: String,
1196}
1197
1198#[test]
1199fn nested_extract_positive() {
1200 #[allow(unused_imports)]
1201 use core::{
1202 option::Option::{None, Some},
1203 result::Result::{Err, Ok},
1204 };
1205 match parse_str::<NestedExtract>(
1206 "<parent xmlns='urn:example:ns1'><child><grandchild>hello world</grandchild></child></parent>",
1207 ) {
1208 Ok(NestedExtract { contents }) => {
1209 assert_eq!(contents, "hello world");
1210 }
1211 other => panic!("unexpected result: {:?}", other),
1212 }
1213}
1214
1215#[test]
1216fn nested_extract_roundtrip() {
1217 #[allow(unused_imports)]
1218 use core::{
1219 option::Option::{None, Some},
1220 result::Result::{Err, Ok},
1221 };
1222 roundtrip_full::<NestedExtract>("<parent xmlns='urn:example:ns1'><child><grandchild>hello world</grandchild></child></parent>")
1223}
1224
1225#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1226#[xml(namespace = NS1, name = "parent")]
1227struct ExtractOmitNamespace {
1228 #[xml(extract(name = "child", fields(text)))]
1229 contents: String,
1230}
1231
1232#[test]
1233fn extract_omit_namespace_roundtrip() {
1234 #[allow(unused_imports)]
1235 use core::{
1236 option::Option::{None, Some},
1237 result::Result::{Err, Ok},
1238 };
1239 roundtrip_full::<ExtractOmitNamespace>(
1240 "<parent xmlns='urn:example:ns1'><child>hello world!</child></parent>",
1241 )
1242}
1243
1244#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1245#[xml(namespace = NS1, name = "parent")]
1246struct ExtractOmitName {
1247 #[xml(extract(namespace = NS1, fields(text)))]
1248 contents: String,
1249}
1250
1251#[test]
1252fn extract_omit_name_roundtrip() {
1253 #[allow(unused_imports)]
1254 use core::{
1255 option::Option::{None, Some},
1256 result::Result::{Err, Ok},
1257 };
1258 roundtrip_full::<ExtractOmitName>(
1259 "<parent xmlns='urn:example:ns1'><contents>hello world!</contents></parent>",
1260 )
1261}
1262
1263#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1264#[xml(namespace = NS1, name = "parent")]
1265struct ExtractOmitNameAndNamespace {
1266 #[xml(extract(fields(text)))]
1267 contents: String,
1268}
1269
1270#[test]
1271fn extract_omit_name_and_namespace_roundtrip() {
1272 #[allow(unused_imports)]
1273 use core::{
1274 option::Option::{None, Some},
1275 result::Result::{Err, Ok},
1276 };
1277 roundtrip_full::<ExtractOmitNameAndNamespace>(
1278 "<parent xmlns='urn:example:ns1'><contents>hello world!</contents></parent>",
1279 )
1280}
1281
1282#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1283#[xml(namespace = NS1, name = "parent")]
1284struct TextExtractVec {
1285 #[xml(extract(n = .., namespace = NS1, name = "child", fields(text(type_ = String))))]
1286 contents: Vec<String>,
1287}
1288
1289#[test]
1290fn text_extract_vec_positive_nonempty() {
1291 #[allow(unused_imports)]
1292 use core::{
1293 option::Option::{None, Some},
1294 result::Result::{Err, Ok},
1295 };
1296 match parse_str::<TextExtractVec>(
1297 "<parent xmlns='urn:example:ns1'><child>hello</child><child>world</child></parent>",
1298 ) {
1299 Ok(TextExtractVec { contents }) => {
1300 assert_eq!(contents[0], "hello");
1301 assert_eq!(contents[1], "world");
1302 assert_eq!(contents.len(), 2);
1303 }
1304 other => panic!("unexpected result: {:?}", other),
1305 }
1306}
1307
1308#[test]
1309fn text_extract_vec_positive_empty() {
1310 #[allow(unused_imports)]
1311 use core::{
1312 option::Option::{None, Some},
1313 result::Result::{Err, Ok},
1314 };
1315 match parse_str::<TextExtractVec>("<parent xmlns='urn:example:ns1'/>") {
1316 Ok(TextExtractVec { contents }) => {
1317 assert_eq!(contents.len(), 0);
1318 }
1319 other => panic!("unexpected result: {:?}", other),
1320 }
1321}
1322
1323#[test]
1324fn text_extract_vec_roundtrip() {
1325 #[allow(unused_imports)]
1326 use core::{
1327 option::Option::{None, Some},
1328 result::Result::{Err, Ok},
1329 };
1330 roundtrip_full::<TextExtractVec>(
1331 "<parent xmlns='urn:example:ns1'><child>hello</child><child>world</child></parent>",
1332 )
1333}
1334
1335#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1336#[xml(namespace = NS1, name = "parent")]
1337struct AttributeExtractVec {
1338 #[xml(extract(n = .., namespace = NS1, name = "child", fields(attribute(type_ = String, name = "attr"))))]
1339 contents: Vec<String>,
1340}
1341
1342#[test]
1343fn text_extract_attribute_vec_positive_nonempty() {
1344 #[allow(unused_imports)]
1345 use core::{
1346 option::Option::{None, Some},
1347 result::Result::{Err, Ok},
1348 };
1349 match parse_str::<AttributeExtractVec>(
1350 "<parent xmlns='urn:example:ns1'><child attr='hello'/><child attr='world'/></parent>",
1351 ) {
1352 Ok(AttributeExtractVec { contents }) => {
1353 assert_eq!(contents[0], "hello");
1354 assert_eq!(contents[1], "world");
1355 assert_eq!(contents.len(), 2);
1356 }
1357 other => panic!("unexpected result: {:?}", other),
1358 }
1359}
1360
1361#[test]
1362fn text_extract_attribute_vec_positive_empty() {
1363 #[allow(unused_imports)]
1364 use core::{
1365 option::Option::{None, Some},
1366 result::Result::{Err, Ok},
1367 };
1368 match parse_str::<AttributeExtractVec>("<parent xmlns='urn:example:ns1'/>") {
1369 Ok(AttributeExtractVec { contents }) => {
1370 assert_eq!(contents.len(), 0);
1371 }
1372 other => panic!("unexpected result: {:?}", other),
1373 }
1374}
1375
1376#[test]
1377fn text_extract_attribute_vec_roundtrip() {
1378 #[allow(unused_imports)]
1379 use core::{
1380 option::Option::{None, Some},
1381 result::Result::{Err, Ok},
1382 };
1383 roundtrip_full::<AttributeExtractVec>(
1384 "<parent xmlns='urn:example:ns1'><child attr='hello'/><child attr='world'/></parent>",
1385 )
1386}
1387
1388#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1389#[xml(namespace = NS1, name = "parent")]
1390struct TextOptionalExtract {
1391 #[xml(extract(namespace = NS1, name = "child", default, fields(text(type_ = String))))]
1392 contents: ::core::option::Option<String>,
1393}
1394
1395#[test]
1396fn text_optional_extract_positive_present() {
1397 #[allow(unused_imports)]
1398 use core::{
1399 option::Option::{None, Some},
1400 result::Result::{Err, Ok},
1401 };
1402 match parse_str::<TextOptionalExtract>(
1403 "<parent xmlns='urn:example:ns1'><child>hello world</child></parent>",
1404 ) {
1405 Ok(TextOptionalExtract {
1406 contents: Some(contents),
1407 }) => {
1408 assert_eq!(contents, "hello world");
1409 }
1410 other => panic!("unexpected result: {:?}", other),
1411 }
1412}
1413
1414#[test]
1415fn text_optional_extract_positive_absent_child() {
1416 #[allow(unused_imports)]
1417 use core::{
1418 option::Option::{None, Some},
1419 result::Result::{Err, Ok},
1420 };
1421 match parse_str::<TextOptionalExtract>("<parent xmlns='urn:example:ns1'/>") {
1422 Ok(TextOptionalExtract { contents: None }) => (),
1423 other => panic!("unexpected result: {:?}", other),
1424 }
1425}
1426
1427#[test]
1428fn text_optional_extract_roundtrip_present() {
1429 #[allow(unused_imports)]
1430 use core::{
1431 option::Option::{None, Some},
1432 result::Result::{Err, Ok},
1433 };
1434 roundtrip_full::<TextOptionalExtract>(
1435 "<parent xmlns='urn:example:ns1'><child>hello world!</child></parent>",
1436 )
1437}
1438
1439#[test]
1440fn text_optional_extract_roundtrip_absent() {
1441 #[allow(unused_imports)]
1442 use core::{
1443 option::Option::{None, Some},
1444 result::Result::{Err, Ok},
1445 };
1446 roundtrip_full::<TextOptionalExtract>("<parent xmlns='urn:example:ns1'/>")
1447}
1448
1449#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1450#[xml(namespace = NS1, name = "parent")]
1451struct OptionalAttributeOptionalExtract {
1452 #[xml(extract(namespace = NS1, name = "child", default, fields(attribute(name = "foo", default))))]
1453 contents: ::core::option::Option<String>,
1454}
1455
1456#[test]
1457fn optional_attribute_optional_extract_positive_present() {
1458 #[allow(unused_imports)]
1459 use core::{
1460 option::Option::{None, Some},
1461 result::Result::{Err, Ok},
1462 };
1463 match parse_str::<OptionalAttributeOptionalExtract>(
1464 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1465 ) {
1466 Ok(OptionalAttributeOptionalExtract {
1467 contents: Some(contents),
1468 }) => {
1469 assert_eq!(contents, "hello world");
1470 }
1471 other => panic!("unexpected result: {:?}", other),
1472 }
1473}
1474
1475#[test]
1476fn optional_attribute_optional_extract_positive_absent_attribute() {
1477 #[allow(unused_imports)]
1478 use core::{
1479 option::Option::{None, Some},
1480 result::Result::{Err, Ok},
1481 };
1482 match parse_str::<OptionalAttributeOptionalExtract>(
1483 "<parent xmlns='urn:example:ns1'><child/></parent>",
1484 ) {
1485 Ok(OptionalAttributeOptionalExtract { contents: None }) => (),
1486 other => panic!("unexpected result: {:?}", other),
1487 }
1488}
1489
1490#[test]
1491fn optional_attribute_optional_extract_positive_absent_element() {
1492 #[allow(unused_imports)]
1493 use core::{
1494 option::Option::{None, Some},
1495 result::Result::{Err, Ok},
1496 };
1497 match parse_str::<OptionalAttributeOptionalExtract>("<parent xmlns='urn:example:ns1'/>") {
1498 Ok(OptionalAttributeOptionalExtract { contents: None }) => (),
1499 other => panic!("unexpected result: {:?}", other),
1500 }
1501}
1502
1503#[test]
1504fn optional_attribute_optional_extract_roundtrip_present() {
1505 #[allow(unused_imports)]
1506 use core::{
1507 option::Option::{None, Some},
1508 result::Result::{Err, Ok},
1509 };
1510 roundtrip_full::<OptionalAttributeOptionalExtract>(
1511 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1512 )
1513}
1514
1515#[test]
1516fn optional_attribute_optional_extract_roundtrip_absent_attribute() {
1517 #[allow(unused_imports)]
1518 use core::{
1519 option::Option::{None, Some},
1520 result::Result::{Err, Ok},
1521 };
1522 roundtrip_full::<OptionalAttributeOptionalExtract>(
1523 "<parent xmlns='urn:example:ns1'><child/></parent>",
1524 )
1525}
1526
1527#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1528#[xml(namespace = NS1, name = "parent")]
1529struct OptionalAttributeOptionalExtractDoubleOption {
1530 #[xml(extract(namespace = NS1, name = "child", default, fields(attribute(name = "foo", type_ = ::core::option::Option<String>, default))))]
1531 contents: ::core::option::Option<::core::option::Option<String>>,
1532}
1533
1534#[test]
1535fn optional_attribute_optional_extract_double_option_positive_present() {
1536 #[allow(unused_imports)]
1537 use core::{
1538 option::Option::{None, Some},
1539 result::Result::{Err, Ok},
1540 };
1541 match parse_str::<OptionalAttributeOptionalExtractDoubleOption>(
1542 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1543 ) {
1544 Ok(OptionalAttributeOptionalExtractDoubleOption {
1545 contents: Some(Some(contents)),
1546 }) => {
1547 assert_eq!(contents, "hello world");
1548 }
1549 other => panic!("unexpected result: {:?}", other),
1550 }
1551}
1552
1553#[test]
1554fn optional_attribute_optional_extract_double_option_positive_absent_attribute() {
1555 #[allow(unused_imports)]
1556 use core::{
1557 option::Option::{None, Some},
1558 result::Result::{Err, Ok},
1559 };
1560 match parse_str::<OptionalAttributeOptionalExtractDoubleOption>(
1561 "<parent xmlns='urn:example:ns1'><child/></parent>",
1562 ) {
1563 Ok(OptionalAttributeOptionalExtractDoubleOption {
1564 contents: Some(None),
1565 }) => (),
1566 other => panic!("unexpected result: {:?}", other),
1567 }
1568}
1569
1570#[test]
1571fn optional_attribute_optional_extract_double_option_positive_absent_element() {
1572 #[allow(unused_imports)]
1573 use core::{
1574 option::Option::{None, Some},
1575 result::Result::{Err, Ok},
1576 };
1577 match parse_str::<OptionalAttributeOptionalExtractDoubleOption>(
1578 "<parent xmlns='urn:example:ns1'/>",
1579 ) {
1580 Ok(OptionalAttributeOptionalExtractDoubleOption { contents: None }) => (),
1581 other => panic!("unexpected result: {:?}", other),
1582 }
1583}
1584
1585#[test]
1586fn optional_attribute_optional_extract_double_option_roundtrip_present() {
1587 #[allow(unused_imports)]
1588 use core::{
1589 option::Option::{None, Some},
1590 result::Result::{Err, Ok},
1591 };
1592 roundtrip_full::<OptionalAttributeOptionalExtractDoubleOption>(
1593 "<parent xmlns='urn:example:ns1'><child foo='hello world'/></parent>",
1594 )
1595}
1596
1597#[test]
1598fn optional_attribute_optional_extract_double_option_roundtrip_absent_attribute() {
1599 #[allow(unused_imports)]
1600 use core::{
1601 option::Option::{None, Some},
1602 result::Result::{Err, Ok},
1603 };
1604 roundtrip_full::<OptionalAttributeOptionalExtractDoubleOption>(
1605 "<parent xmlns='urn:example:ns1'><child/></parent>",
1606 )
1607}
1608
1609#[test]
1610fn optional_attribute_optional_extract_double_option_roundtrip_absent_child() {
1611 #[allow(unused_imports)]
1612 use core::{
1613 option::Option::{None, Some},
1614 result::Result::{Err, Ok},
1615 };
1616 roundtrip_full::<OptionalAttributeOptionalExtractDoubleOption>(
1617 "<parent xmlns='urn:example:ns1'/>",
1618 )
1619}
1620
1621#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1622#[xml(namespace = NS1, name = "parent")]
1623struct ElementCatchOne {
1624 #[xml(element)]
1625 child: ::minidom::Element,
1626}
1627
1628#[test]
1629fn element_catch_one_roundtrip() {
1630 #[allow(unused_imports)]
1631 use core::{
1632 option::Option::{None, Some},
1633 result::Result::{Err, Ok},
1634 };
1635 roundtrip_full::<ElementCatchOne>(
1636 "<parent xmlns='urn:example:ns1'><child><deeper/></child></parent>",
1637 )
1638}
1639
1640#[test]
1641fn element_catch_one_negative_none() {
1642 #[allow(unused_imports)]
1643 use core::{
1644 option::Option::{None, Some},
1645 result::Result::{Err, Ok},
1646 };
1647 match parse_str::<ElementCatchOne>("<parent xmlns='urn:example:ns1'/>") {
1648 Err(::xso::error::Error::Other(e)) if e.contains("Missing child field") => (),
1649 other => panic!("unexpected result: {:?}", other),
1650 }
1651}
1652
1653#[test]
1654fn element_catch_one_negative_more_than_one_child() {
1655 #[allow(unused_imports)]
1656 use core::{
1657 option::Option::{None, Some},
1658 result::Result::{Err, Ok},
1659 };
1660 match parse_str::<ElementCatchOne>("<parent xmlns='urn:example:ns1'><child><deeper/></child><child xmlns='urn:example:ns2'/></parent>") {
1661 Err(::xso::error::Error::Other(e)) if e == "Unknown child in ElementCatchOne element." => (),
1662 other => panic!("unexpected result: {:?}", other),
1663 }
1664}
1665
1666#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1667#[xml(namespace = NS1, name = "parent")]
1668struct ElementCatchMaybeOne {
1669 #[xml(element(default))]
1670 maybe_child: core::option::Option<::minidom::Element>,
1671}
1672
1673#[test]
1674fn element_catch_maybe_one_roundtrip_none() {
1675 #[allow(unused_imports)]
1676 use core::{
1677 option::Option::{None, Some},
1678 result::Result::{Err, Ok},
1679 };
1680 roundtrip_full::<ElementCatchMaybeOne>("<parent xmlns='urn:example:ns1'/>")
1681}
1682
1683#[test]
1684fn element_catch_maybe_one_roundtrip_some() {
1685 #[allow(unused_imports)]
1686 use core::{
1687 option::Option::{None, Some},
1688 result::Result::{Err, Ok},
1689 };
1690 roundtrip_full::<ElementCatchMaybeOne>(
1691 "<parent xmlns='urn:example:ns1'><child><deeper/></child></parent>",
1692 )
1693}
1694
1695#[test]
1696fn element_catch_maybe_one_negative_more_than_one_child() {
1697 #[allow(unused_imports)]
1698 use core::{
1699 option::Option::{None, Some},
1700 result::Result::{Err, Ok},
1701 };
1702 match parse_str::<ElementCatchMaybeOne>("<parent xmlns='urn:example:ns1'><child><deeper/></child><child xmlns='urn:example:ns2'/></parent>") {
1703 Err(::xso::error::Error::Other(e)) if e == "Unknown child in ElementCatchMaybeOne element." => (),
1704 other => panic!("unexpected result: {:?}", other),
1705 }
1706}
1707
1708#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1709#[xml(namespace = NS1, name = "parent")]
1710struct ElementCatchChildAndOne {
1711 #[xml(child)]
1712 child: Empty,
1713
1714 #[xml(element)]
1715 element: ::minidom::Element,
1716}
1717
1718#[test]
1719fn element_catch_child_and_one_roundtrip() {
1720 #[allow(unused_imports)]
1721 use core::{
1722 option::Option::{None, Some},
1723 result::Result::{Err, Ok},
1724 };
1725 roundtrip_full::<ElementCatchChildAndOne>(
1726 "<parent xmlns='urn:example:ns1'><foo/><element/></parent>",
1727 )
1728}
1729
1730#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1731#[xml(namespace = NS1, name = "parent")]
1732struct ElementCatchChildAndMaybeOne {
1733 #[xml(child)]
1734 child: Empty,
1735
1736 #[xml(element(default))]
1737 element: ::core::option::Option<::minidom::Element>,
1738}
1739
1740#[test]
1741fn element_catch_child_and_maybe_one_roundtrip() {
1742 #[allow(unused_imports)]
1743 use core::{
1744 option::Option::{None, Some},
1745 result::Result::{Err, Ok},
1746 };
1747 roundtrip_full::<ElementCatchChildAndMaybeOne>(
1748 "<parent xmlns='urn:example:ns1'><foo/></parent>",
1749 )
1750}
1751
1752#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1753#[xml(namespace = NS1, name = "parent")]
1754struct ElementCatchOneAndMany {
1755 #[xml(element)]
1756 child: ::minidom::Element,
1757
1758 #[xml(element(n = ..))]
1759 children: Vec<::minidom::Element>,
1760}
1761
1762#[test]
1763fn element_catch_one_and_many_roundtrip() {
1764 #[allow(unused_imports)]
1765 use core::{
1766 option::Option::{None, Some},
1767 result::Result::{Err, Ok},
1768 };
1769 roundtrip_full::<ElementCatchOneAndMany>(
1770 "<parent xmlns='urn:example:ns1'><child num='0'><deeper/></child><child num='1'><deeper/></child></parent>",
1771 )
1772}
1773
1774#[test]
1775fn element_catch_one_and_many_parse_in_order() {
1776 #[allow(unused_imports)]
1777 use core::{
1778 option::Option::{None, Some},
1779 result::Result::{Err, Ok},
1780 };
1781 match parse_str::<ElementCatchOneAndMany>(
1782 "<parent xmlns='urn:example:ns1'><child num='0'/><child num='1'/></parent>",
1783 ) {
1784 Ok(ElementCatchOneAndMany { child, children }) => {
1785 assert_eq!(child.attr(rxml::xml_ncname!("num")), Some("0"));
1786 assert_eq!(children.len(), 1);
1787 assert_eq!(children[0].attr(rxml::xml_ncname!("num")), Some("1"));
1788 }
1789 other => panic!("unexpected result: {:?}", other),
1790 }
1791}
1792
1793#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1794#[xml(namespace = NS1, name = "parent")]
1795struct ElementCatchall {
1796 #[xml(element(n = ..))]
1797 children: Vec<::minidom::Element>,
1798}
1799
1800#[test]
1801fn element_catchall_roundtrip() {
1802 #[allow(unused_imports)]
1803 use core::{
1804 option::Option::{None, Some},
1805 result::Result::{Err, Ok},
1806 };
1807 roundtrip_full::<ElementCatchall>(
1808 "<parent xmlns='urn:example:ns1'><child><deeper/></child><child xmlns='urn:example:ns2'/><more-children/><yet-another-child/><child/></parent>",
1809 )
1810}
1811
1812#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1813#[xml(transparent)]
1814struct TransparentStruct(RequiredAttribute);
1815
1816#[test]
1817fn transparent_struct_roundtrip() {
1818 #[allow(unused_imports)]
1819 use core::{
1820 option::Option::{None, Some},
1821 result::Result::{Err, Ok},
1822 };
1823 roundtrip_full::<TransparentStruct>("<attr xmlns='urn:example:ns1' foo='bar'/>");
1824}
1825
1826#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1827#[xml(transparent)]
1828struct TransparentStructNamed {
1829 foo: RequiredAttribute,
1830}
1831
1832#[test]
1833fn transparent_struct_named_roundtrip() {
1834 #[allow(unused_imports)]
1835 use core::{
1836 option::Option::{None, Some},
1837 result::Result::{Err, Ok},
1838 };
1839 roundtrip_full::<TransparentStructNamed>("<attr xmlns='urn:example:ns1' foo='bar'/>");
1840}
1841
1842#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1843#[xml()]
1844enum DynamicEnum {
1845 #[xml(transparent)]
1846 A(RequiredAttribute),
1847
1848 #[xml(namespace = NS2, name = "b")]
1849 B {
1850 #[xml(text)]
1851 contents: String,
1852 },
1853}
1854
1855#[test]
1856fn dynamic_enum_matcher_is_any() {
1857 assert_eq!(
1858 DynamicEnum::xml_name_matcher(),
1859 xso::fromxml::XmlNameMatcher::Any
1860 );
1861}
1862
1863#[test]
1864fn dynamic_enum_roundtrip_a() {
1865 #[allow(unused_imports)]
1866 use core::{
1867 option::Option::{None, Some},
1868 result::Result::{Err, Ok},
1869 };
1870 roundtrip_full::<DynamicEnum>("<attr xmlns='urn:example:ns1' foo='bar'/>");
1871}
1872
1873#[test]
1874fn dynamic_enum_roundtrip_b() {
1875 #[allow(unused_imports)]
1876 use core::{
1877 option::Option::{None, Some},
1878 result::Result::{Err, Ok},
1879 };
1880 roundtrip_full::<DynamicEnum>("<b xmlns='urn:example:ns2'>hello world</b>");
1881}
1882
1883#[derive(FromXml, Debug)]
1884#[xml(namespace = NS1, name = "parent")]
1885struct FallibleParse {
1886 #[xml(child)]
1887 child: ::core::result::Result<RequiredAttribute, ::xso::error::Error>,
1888}
1889
1890#[test]
1891fn fallible_parse_positive_ok() {
1892 #[allow(unused_imports)]
1893 use core::{
1894 option::Option::{None, Some},
1895 result::Result::{Err, Ok},
1896 };
1897 match parse_str::<FallibleParse>("<parent xmlns='urn:example:ns1'><attr foo='bar'/></parent>") {
1898 Ok(FallibleParse {
1899 child: Ok(RequiredAttribute { foo }),
1900 }) => {
1901 assert_eq!(foo, "bar");
1902 }
1903 other => panic!("unexpected result: {:?}", other),
1904 }
1905}
1906
1907#[test]
1908fn fallible_parse_positive_err() {
1909 #[allow(unused_imports)]
1910 use core::{
1911 option::Option::{None, Some},
1912 result::Result::{Err, Ok},
1913 };
1914 match parse_str::<FallibleParse>("<parent xmlns='urn:example:ns1'><attr/></parent>") {
1915 Ok(FallibleParse { child: Err(e) }) => {
1916 assert!(e.to_string().contains("attribute"));
1917 }
1918 other => panic!("unexpected result: {:?}", other),
1919 }
1920}
1921
1922#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1923#[xml()]
1924enum DynamicEnumWithSharedNamespace {
1925 #[xml(transparent)]
1926 A(RequiredAttribute),
1927
1928 #[xml(namespace = NS1, name = "b")]
1929 B {
1930 #[xml(text)]
1931 contents: String,
1932 },
1933}
1934
1935#[test]
1936fn dynamic_enum_with_shared_namespace_matcher_is_in_namespace() {
1937 assert_eq!(
1938 DynamicEnumWithSharedNamespace::xml_name_matcher(),
1939 xso::fromxml::XmlNameMatcher::InNamespace(NS1)
1940 );
1941}
1942
1943#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1944#[xml(namespace = NS1, name = "parent")]
1945struct ExtractTupleToCollection {
1946 #[xml(extract(n = .., namespace = NS1, name = "text", fields(
1947 attribute(name = "xml:lang", type_ = ::core::option::Option<String>, default),
1948 text(type_ = String),
1949 )))]
1950 contents: Vec<(::core::option::Option<String>, String)>,
1951}
1952
1953#[test]
1954fn extract_tuple_to_collection_roundtrip() {
1955 #[allow(unused_imports)]
1956 use core::{
1957 option::Option::{None, Some},
1958 result::Result::{Err, Ok},
1959 };
1960 roundtrip_full::<ExtractTupleToCollection>(
1961 "<parent xmlns='urn:example:ns1'><text>hello world</text><text xml:lang='de'>hallo welt</text></parent>",
1962 );
1963}
1964
1965#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1966#[xml(namespace = NS1, name = "parent")]
1967struct ExtractTuple {
1968 #[xml(extract(namespace = NS1, name = "text", fields(
1969 attribute(name = "xml:lang", type_ = ::core::option::Option<String>, default),
1970 text(type_ = String),
1971 )))]
1972 contents: (::core::option::Option<String>, String),
1973}
1974
1975#[test]
1976fn extract_tuple_roundtrip() {
1977 #[allow(unused_imports)]
1978 use core::{
1979 option::Option::{None, Some},
1980 result::Result::{Err, Ok},
1981 };
1982 roundtrip_full::<ExtractTuple>(
1983 "<parent xmlns='urn:example:ns1'><text xml:lang='de'>hallo welt</text></parent>",
1984 );
1985}
1986
1987#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
1988#[xml(namespace = NS1, name = "parent")]
1989struct ExtractOptionalTuple {
1990 #[xml(extract(namespace = NS1, name = "text", default, fields(
1991 attribute(name = "xml:lang", type_ = ::core::option::Option<String>, default),
1992 text(type_ = String),
1993 )))]
1994 contents: ::core::option::Option<(::core::option::Option<String>, String)>,
1995}
1996
1997#[test]
1998fn extract_optional_tuple_roundtrip_present() {
1999 #[allow(unused_imports)]
2000 use core::{
2001 option::Option::{None, Some},
2002 result::Result::{Err, Ok},
2003 };
2004 roundtrip_full::<ExtractOptionalTuple>(
2005 "<parent xmlns='urn:example:ns1'><text xml:lang='de'>hallo welt</text></parent>",
2006 );
2007 // Cannot test without text body here right now due to a limitation in the
2008 // `#[xml(text)]` serialiser: It will create an empty text node in the
2009 // minidom output, which will then fail the comparison.
2010 roundtrip_full::<ExtractOptionalTuple>(
2011 "<parent xmlns='urn:example:ns1'><text xml:lang='de'>x</text></parent>",
2012 );
2013 roundtrip_full::<ExtractOptionalTuple>(
2014 "<parent xmlns='urn:example:ns1'><text xml:lang=''>x</text></parent>",
2015 );
2016}
2017
2018#[test]
2019fn extract_optional_tuple_roundtrip_absent() {
2020 #[allow(unused_imports)]
2021 use core::{
2022 option::Option::{None, Some},
2023 result::Result::{Err, Ok},
2024 };
2025 roundtrip_full::<ExtractOptionalTuple>("<parent xmlns='urn:example:ns1'/>");
2026}
2027
2028#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2029#[xml(namespace = NS1, name = "parent")]
2030struct ExtractTupleToMap {
2031 #[xml(extract(n = .., namespace = NS1, name = "text", fields(
2032 attribute(name = "xml:lang", type_ = ::core::option::Option<String>, default),
2033 text(type_ = String),
2034 )))]
2035 contents: alloc::collections::BTreeMap<::core::option::Option<String>, String>,
2036}
2037
2038#[test]
2039fn extract_tuple_to_map_roundtrip() {
2040 #[allow(unused_imports)]
2041 use core::{
2042 option::Option::{None, Some},
2043 result::Result::{Err, Ok},
2044 };
2045 roundtrip_full::<ExtractTupleToMap>(
2046 "<parent xmlns='urn:example:ns1'><text>hello world</text><text xml:lang='de'>hallo welt</text></parent>",
2047 );
2048}
2049
2050#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2051#[xml(namespace = NS1, name = "foo", on_unknown_attribute = Discard)]
2052struct IgnoreUnknownAttributes;
2053
2054#[test]
2055fn ignore_unknown_attributes_empty_roundtrip() {
2056 #[allow(unused_imports)]
2057 use core::{
2058 option::Option::{None, Some},
2059 result::Result::{Err, Ok},
2060 };
2061 roundtrip_full::<IgnoreUnknownAttributes>("<foo xmlns='urn:example:ns1'/>");
2062}
2063
2064#[test]
2065fn ignore_unknown_attributes_positive() {
2066 #[allow(unused_imports)]
2067 use core::{
2068 option::Option::{None, Some},
2069 result::Result::{Err, Ok},
2070 };
2071 match parse_str::<IgnoreUnknownAttributes>("<foo xmlns='urn:example:ns1' fnord='bar'/>") {
2072 Ok(IgnoreUnknownAttributes) => (),
2073 other => panic!("unexpected result: {:?}", other),
2074 }
2075}
2076
2077#[test]
2078#[cfg_attr(
2079 feature = "disable-validation",
2080 should_panic = "unexpected result: Ok("
2081)]
2082fn ignore_unknown_attributes_negative_unexpected_child() {
2083 #[allow(unused_imports)]
2084 use core::{
2085 option::Option::{None, Some},
2086 result::Result::{Err, Ok},
2087 };
2088 match parse_str::<IgnoreUnknownAttributes>("<foo xmlns='urn:example:ns1'><coucou/></foo>") {
2089 Err(xso::error::Error::Other(e)) => {
2090 assert_eq!(e, "Unknown child in IgnoreUnknownAttributes element.");
2091 }
2092 other => panic!("unexpected result: {:?}", other),
2093 }
2094}
2095
2096#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2097#[xml(namespace = NS1, name = "foo", on_unknown_child = Discard)]
2098struct IgnoreUnknownChildren;
2099
2100#[test]
2101fn ignore_unknown_children_empty_roundtrip() {
2102 #[allow(unused_imports)]
2103 use core::{
2104 option::Option::{None, Some},
2105 result::Result::{Err, Ok},
2106 };
2107 roundtrip_full::<IgnoreUnknownChildren>("<foo xmlns='urn:example:ns1'/>");
2108}
2109
2110#[test]
2111fn ignore_unknown_children_positive() {
2112 #[allow(unused_imports)]
2113 use core::{
2114 option::Option::{None, Some},
2115 result::Result::{Err, Ok},
2116 };
2117 match parse_str::<IgnoreUnknownChildren>("<foo xmlns='urn:example:ns1'><coucou/></foo>") {
2118 Ok(IgnoreUnknownChildren) => (),
2119 other => panic!("unexpected result: {:?}", other),
2120 }
2121}
2122
2123#[test]
2124#[cfg_attr(
2125 feature = "disable-validation",
2126 should_panic = "unexpected result: Ok("
2127)]
2128fn ignore_unknown_children_negative_unexpected_attribute() {
2129 #[allow(unused_imports)]
2130 use core::{
2131 option::Option::{None, Some},
2132 result::Result::{Err, Ok},
2133 };
2134 match parse_str::<IgnoreUnknownChildren>("<foo xmlns='urn:example:ns1' fnord='bar'/>") {
2135 Err(xso::error::Error::Other(e)) => {
2136 assert_eq!(e, "Unknown attribute in IgnoreUnknownChildren element.");
2137 }
2138 other => panic!("unexpected result: {:?}", other),
2139 }
2140}
2141
2142#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2143#[xml(namespace = NS1, name = "parent")]
2144struct ExtractIgnoreUnknownStuff {
2145 #[xml(extract(namespace = NS1, name = "child", on_unknown_attribute = Discard, on_unknown_child = Discard, fields(
2146 extract(namespace = NS1, name = "grandchild", fields(text))
2147 )))]
2148 contents: String,
2149}
2150
2151#[test]
2152fn extract_ignore_unknown_stuff_positive() {
2153 #[allow(unused_imports)]
2154 use core::{
2155 option::Option::{None, Some},
2156 result::Result::{Err, Ok},
2157 };
2158 match parse_str::<ExtractIgnoreUnknownStuff>(
2159 "<parent xmlns='urn:example:ns1'><child foo='bar'><quak/><grandchild>hello world</grandchild></child></parent>",
2160 ) {
2161 Ok(ExtractIgnoreUnknownStuff { contents }) => {
2162 assert_eq!(contents, "hello world");
2163 }
2164 other => panic!("unexpected result: {:?}", other),
2165 }
2166}
2167
2168#[test]
2169fn extract_ignore_unknown_stuff_roundtrip() {
2170 #[allow(unused_imports)]
2171 use core::{
2172 option::Option::{None, Some},
2173 result::Result::{Err, Ok},
2174 };
2175 roundtrip_full::<ExtractIgnoreUnknownStuff>("<parent xmlns='urn:example:ns1'><child><grandchild>hello world</grandchild></child></parent>")
2176}
2177
2178#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2179#[xml(namespace = NS1, name = "foo")]
2180struct Flag {
2181 #[xml(flag)]
2182 flag: bool,
2183}
2184
2185#[test]
2186fn flag_parse_present_as_true() {
2187 #[allow(unused_imports)]
2188 use core::{
2189 option::Option::{None, Some},
2190 result::Result::{Err, Ok},
2191 };
2192 match parse_str::<Flag>("<foo xmlns='urn:example:ns1'><flag/></foo>") {
2193 Ok(Flag { flag }) => {
2194 assert!(flag);
2195 }
2196 other => panic!("unexpected result: {:?}", other),
2197 }
2198}
2199
2200#[test]
2201fn flag_present_roundtrip() {
2202 #[allow(unused_imports)]
2203 use core::{
2204 option::Option::{None, Some},
2205 result::Result::{Err, Ok},
2206 };
2207 roundtrip_full::<Flag>("<foo xmlns='urn:example:ns1'><flag/></foo>");
2208}
2209
2210#[test]
2211fn flag_absent_roundtrip() {
2212 #[allow(unused_imports)]
2213 use core::{
2214 option::Option::{None, Some},
2215 result::Result::{Err, Ok},
2216 };
2217 roundtrip_full::<Flag>("<foo xmlns='urn:example:ns1'/>");
2218}
2219
2220#[test]
2221fn printrawxml() {
2222 let text = TextString {
2223 text: String::from("hello world"),
2224 };
2225 let display = format!("{}", PrintRawXml(&text));
2226 assert_eq!(display, "<text xmlns='urn:example:ns1'>hello world</text>");
2227}
2228
2229#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2230#[xml(namespace = NS1, name = "foo", discard(attribute = "bar"))]
2231struct DiscardAttribute;
2232
2233#[test]
2234fn discard_attribute_ignore_if_present() {
2235 #[allow(unused_imports)]
2236 use core::{
2237 option::Option::{None, Some},
2238 result::Result::{Err, Ok},
2239 };
2240 match parse_str::<DiscardAttribute>("<foo xmlns='urn:example:ns1' bar='baz'/>") {
2241 Ok(DiscardAttribute) => (),
2242 other => panic!("unexpected result: {:?}", other),
2243 }
2244}
2245
2246#[test]
2247fn discard_attribute_ignore_if_absent() {
2248 #[allow(unused_imports)]
2249 use core::{
2250 option::Option::{None, Some},
2251 result::Result::{Err, Ok},
2252 };
2253 match parse_str::<DiscardAttribute>("<foo xmlns='urn:example:ns1'/>") {
2254 Ok(DiscardAttribute) => (),
2255 other => panic!("unexpected result: {:?}", other),
2256 }
2257}
2258
2259#[test]
2260fn discard_attribute_absent_roundtrip() {
2261 #[allow(unused_imports)]
2262 use core::{
2263 option::Option::{None, Some},
2264 result::Result::{Err, Ok},
2265 };
2266 roundtrip_full::<DiscardAttribute>("<foo xmlns='urn:example:ns1'/>");
2267}
2268
2269#[test]
2270#[cfg_attr(
2271 feature = "disable-validation",
2272 should_panic = "unexpected result: Ok("
2273)]
2274fn discard_attribute_fails_on_other_unexpected_attributes() {
2275 #[allow(unused_imports)]
2276 use core::{
2277 option::Option::{None, Some},
2278 result::Result::{Err, Ok},
2279 };
2280 match parse_str::<DiscardAttribute>("<foo xmlns='urn:example:ns1' fnord='bar'/>") {
2281 Err(xso::error::Error::Other(e)) => {
2282 assert_eq!(e, "Unknown attribute in DiscardAttribute element.");
2283 }
2284 other => panic!("unexpected result: {:?}", other),
2285 }
2286}
2287
2288#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2289#[xml(namespace = NS1, name = "foo", discard(text))]
2290struct DiscardText;
2291
2292#[test]
2293fn discard_text_ignore_if_present() {
2294 #[allow(unused_imports)]
2295 use core::{
2296 option::Option::{None, Some},
2297 result::Result::{Err, Ok},
2298 };
2299 match parse_str::<DiscardText>("<foo xmlns='urn:example:ns1'>quak</foo>") {
2300 Ok(DiscardText) => (),
2301 other => panic!("unexpected result: {:?}", other),
2302 }
2303}
2304
2305#[test]
2306fn discard_text_ignore_if_absent() {
2307 #[allow(unused_imports)]
2308 use core::{
2309 option::Option::{None, Some},
2310 result::Result::{Err, Ok},
2311 };
2312 match parse_str::<DiscardText>("<foo xmlns='urn:example:ns1'/>") {
2313 Ok(DiscardText) => (),
2314 other => panic!("unexpected result: {:?}", other),
2315 }
2316}
2317
2318#[test]
2319fn discard_text_absent_roundtrip() {
2320 #[allow(unused_imports)]
2321 use core::{
2322 option::Option::{None, Some},
2323 result::Result::{Err, Ok},
2324 };
2325 roundtrip_full::<DiscardText>("<foo xmlns='urn:example:ns1'/>");
2326}
2327
2328fn transform_test_struct(
2329 v: &mut DeserializeCallback,
2330) -> ::core::result::Result<(), ::xso::error::Error> {
2331 #[allow(unused_imports)]
2332 use core::{
2333 option::Option::{None, Some},
2334 result::Result::{Err, Ok},
2335 };
2336 use xso::error::Error;
2337 if v.outcome == 0 {
2338 return Err(Error::Other("saw outcome == 0"));
2339 }
2340 if v.outcome == 1 {
2341 v.outcome = 0;
2342 }
2343 Ok(())
2344}
2345
2346#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2347#[xml(namespace = NS1, name = "foo", deserialize_callback = transform_test_struct)]
2348struct DeserializeCallback {
2349 #[xml(attribute)]
2350 outcome: u32,
2351}
2352
2353#[test]
2354fn deserialize_callback_roundtrip() {
2355 #[allow(unused_imports)]
2356 use core::{
2357 option::Option::{None, Some},
2358 result::Result::{Err, Ok},
2359 };
2360 roundtrip_full::<DeserializeCallback>("<foo xmlns='urn:example:ns1' outcome='2'/>");
2361}
2362
2363#[test]
2364fn deserialize_callback_can_mutate() {
2365 #[allow(unused_imports)]
2366 use core::{
2367 option::Option::{None, Some},
2368 result::Result::{Err, Ok},
2369 };
2370 match parse_str::<DeserializeCallback>("<foo xmlns='urn:example:ns1' outcome='1'/>") {
2371 Ok(DeserializeCallback { outcome }) => {
2372 assert_eq!(outcome, 0);
2373 }
2374 other => panic!("unexpected result: {:?}", other),
2375 }
2376}
2377
2378#[test]
2379fn deserialize_callback_can_fail() {
2380 #[allow(unused_imports)]
2381 use core::{
2382 option::Option::{None, Some},
2383 result::Result::{Err, Ok},
2384 };
2385 match parse_str::<DeserializeCallback>("<foo xmlns='urn:example:ns1' outcome='0'/>") {
2386 Err(xso::error::Error::Other(e)) => {
2387 assert_eq!(e, "saw outcome == 0");
2388 }
2389 other => panic!("unexpected result: {:?}", other),
2390 }
2391}
2392
2393fn transform_test_enum(
2394 v: &mut DeserializeCallbackEnum,
2395) -> ::core::result::Result<(), ::xso::error::Error> {
2396 #[allow(unused_imports)]
2397 use core::{
2398 option::Option::{None, Some},
2399 result::Result::{Err, Ok},
2400 };
2401 use xso::error::Error;
2402 match v {
2403 DeserializeCallbackEnum::Foo { ref mut outcome } => {
2404 if *outcome == 0 {
2405 return Err(Error::Other("saw outcome == 0"));
2406 }
2407 if *outcome == 1 {
2408 *outcome = 0;
2409 }
2410 Ok(())
2411 }
2412 }
2413}
2414
2415#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2416#[xml(namespace = NS1, deserialize_callback = transform_test_enum)]
2417enum DeserializeCallbackEnum {
2418 #[xml(name = "foo")]
2419 Foo {
2420 #[xml(attribute)]
2421 outcome: u32,
2422 },
2423}
2424
2425#[test]
2426fn enum_deserialize_callback_roundtrip() {
2427 #[allow(unused_imports)]
2428 use core::{
2429 option::Option::{None, Some},
2430 result::Result::{Err, Ok},
2431 };
2432 roundtrip_full::<DeserializeCallbackEnum>("<foo xmlns='urn:example:ns1' outcome='2'/>");
2433}
2434
2435#[test]
2436fn enum_deserialize_callback_can_mutate() {
2437 #[allow(unused_imports)]
2438 use core::{
2439 option::Option::{None, Some},
2440 result::Result::{Err, Ok},
2441 };
2442 match parse_str::<DeserializeCallbackEnum>("<foo xmlns='urn:example:ns1' outcome='1'/>") {
2443 Ok(DeserializeCallbackEnum::Foo { outcome }) => {
2444 assert_eq!(outcome, 0);
2445 }
2446 other => panic!("unexpected result: {:?}", other),
2447 }
2448}
2449
2450#[test]
2451fn enum_deserialize_callback_can_fail() {
2452 #[allow(unused_imports)]
2453 use core::{
2454 option::Option::{None, Some},
2455 result::Result::{Err, Ok},
2456 };
2457 match parse_str::<DeserializeCallbackEnum>("<foo xmlns='urn:example:ns1' outcome='0'/>") {
2458 Err(xso::error::Error::Other(e)) => {
2459 assert_eq!(e, "saw outcome == 0");
2460 }
2461 other => panic!("unexpected result: {:?}", other),
2462 }
2463}
2464
2465/// This struct failed to compile at some point, failing to find the
2466/// (internally generated) identifier `fid`.
2467#[derive(AsXml, FromXml, PartialEq, Debug, Clone)]
2468#[xml(namespace = NS1, name = "thread")]
2469struct TextVsAttributeOrderingCompileBug {
2470 #[xml(text)]
2471 id: String,
2472
2473 #[xml(attribute(default))]
2474 parent: ::core::option::Option<String>,
2475}
2476
2477#[test]
2478fn text_vs_attribute_ordering_compile_bug_roundtrip() {
2479 #[allow(unused_imports)]
2480 use core::{
2481 option::Option::{None, Some},
2482 result::Result::{Err, Ok},
2483 };
2484 roundtrip_full::<TextVsAttributeOrderingCompileBug>(
2485 "<thread xmlns='urn:example:ns1'>foo</thread>",
2486 );
2487}
2488
2489#[test]
2490fn text_vs_attribute_ordering_compile_bug_roundtrip_with_parent() {
2491 #[allow(unused_imports)]
2492 use core::{
2493 option::Option::{None, Some},
2494 result::Result::{Err, Ok},
2495 };
2496 roundtrip_full::<TextVsAttributeOrderingCompileBug>(
2497 "<thread xmlns='urn:example:ns1' parent='bar'>foo</thread>",
2498 );
2499}
2500
2501#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
2502#[xml(namespace = NS1, name = "elem")]
2503struct Language {
2504 #[xml(child(default))]
2505 child: core::option::Option<Box<Language>>,
2506
2507 #[xml(lang(default))]
2508 lang: core::option::Option<String>,
2509}
2510
2511#[test]
2512fn language_roundtrip_absent() {
2513 #[allow(unused_imports)]
2514 use core::{
2515 option::Option::{None, Some},
2516 result::Result::{Err, Ok},
2517 };
2518 roundtrip_full::<Language>("<elem xmlns='urn:example:ns1'/>");
2519}
2520
2521#[test]
2522fn language_roundtrip_present() {
2523 #[allow(unused_imports)]
2524 use core::{
2525 option::Option::{None, Some},
2526 result::Result::{Err, Ok},
2527 };
2528 roundtrip_full::<Language>("<elem xmlns='urn:example:ns1' xml:lang='foo'/>");
2529}
2530
2531#[test]
2532fn language_roundtrip_nested_parse() {
2533 // cannot write a round-trip test for this, because on emission,
2534 // `#[xml(language)]` fields with a non-None value will always emit
2535 // the `xml:lang` attribute to ensure semantic correctness.
2536 #[allow(unused_imports)]
2537 use core::{
2538 option::Option::{None, Some},
2539 result::Result::{Err, Ok},
2540 };
2541 match parse_str::<Language>(
2542 "<elem xmlns='urn:example:ns1' xml:lang='foo'><elem><elem xml:lang='bar'/></elem></elem>",
2543 ) {
2544 Ok(Language { child, lang }) => {
2545 assert_eq!(lang.as_deref(), Some("foo"));
2546
2547 let Some(Language { child, lang }) = child.map(|x| *x) else {
2548 panic!("missing child");
2549 };
2550 assert_eq!(lang.as_deref(), Some("foo"));
2551
2552 let Some(Language { child, lang }) = child.map(|x| *x) else {
2553 panic!("missing grand child");
2554 };
2555 assert_eq!(lang.as_deref(), Some("bar"));
2556
2557 assert!(child.is_none());
2558 }
2559 other => panic!("unexpected parse result: {:?}", other),
2560 }
2561}
2562
2563#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
2564#[xml(namespace = NS1, name = "foo", attribute = "bar", exhaustive)]
2565enum AttributeSwitchedEnum {
2566 #[xml(value = "a")]
2567 A {
2568 #[xml(attribute = "baz")]
2569 baz: String,
2570 },
2571
2572 #[xml(value = "b")]
2573 B {
2574 #[xml(text)]
2575 content: String,
2576 },
2577}
2578
2579#[test]
2580fn attribute_switched_enum_roundtrip_a() {
2581 #[allow(unused_imports)]
2582 use core::{
2583 option::Option::{None, Some},
2584 result::Result::{Err, Ok},
2585 };
2586 roundtrip_full::<AttributeSwitchedEnum>("<foo xmlns='urn:example:ns1' bar='a' baz='abc'/>");
2587}
2588
2589#[test]
2590fn attribute_switched_enum_roundtrip_b() {
2591 #[allow(unused_imports)]
2592 use core::{
2593 option::Option::{None, Some},
2594 result::Result::{Err, Ok},
2595 };
2596 roundtrip_full::<AttributeSwitchedEnum>("<foo xmlns='urn:example:ns1' bar='b'>abc</foo>");
2597}
2598
2599#[test]
2600fn attribute_switched_enum_negative_namespace_mismatch() {
2601 #[allow(unused_imports)]
2602 use core::{
2603 option::Option::{None, Some},
2604 result::Result::{Err, Ok},
2605 };
2606 match parse_str::<AttributeSwitchedEnum>("<foo xmlns='urn:example:ns2' bar='b'>abc</foo>") {
2607 Err(xso::error::Error::TypeMismatch) => (),
2608 other => panic!("unexpected result: {:?}", other),
2609 }
2610}
2611
2612#[test]
2613fn attribute_switched_enum_negative_name_mismatch() {
2614 #[allow(unused_imports)]
2615 use core::{
2616 option::Option::{None, Some},
2617 result::Result::{Err, Ok},
2618 };
2619 match parse_str::<AttributeSwitchedEnum>("<quux xmlns='urn:example:ns1' bar='b'>abc</quux>") {
2620 Err(xso::error::Error::TypeMismatch) => (),
2621 other => panic!("unexpected result: {:?}", other),
2622 }
2623}
2624
2625#[test]
2626fn attribute_switched_enum_negative_attribute_missing() {
2627 #[allow(unused_imports)]
2628 use core::{
2629 option::Option::{None, Some},
2630 result::Result::{Err, Ok},
2631 };
2632 match parse_str::<AttributeSwitchedEnum>("<foo xmlns='urn:example:ns1'/>") {
2633 Err(xso::error::Error::Other(e)) => {
2634 assert_eq!(e, "Missing discriminator attribute.");
2635 }
2636 other => panic!("unexpected result: {:?}", other),
2637 }
2638}
2639
2640#[test]
2641fn attribute_switched_enum_negative_attribute_mismatch() {
2642 #[allow(unused_imports)]
2643 use core::{
2644 option::Option::{None, Some},
2645 result::Result::{Err, Ok},
2646 };
2647 match parse_str::<AttributeSwitchedEnum>("<foo xmlns='urn:example:ns1' bar='quux'/>") {
2648 Err(xso::error::Error::Other(e)) => {
2649 assert_eq!(e, "Unknown value for discriminator attribute.");
2650 }
2651 other => panic!("unexpected result: {:?}", other),
2652 }
2653}
2654
2655#[test]
2656fn attribute_switched_enum_positive_attribute_mismatch() {
2657 #[allow(unused_imports)]
2658 use core::{
2659 option::Option::{None, Some},
2660 result::Result::{Err, Ok},
2661 };
2662 match parse_str::<AttributeSwitchedEnum>("<foo xmlns='urn:example:ns1' bar='b'>abc</foo>") {
2663 Ok(AttributeSwitchedEnum::B { content }) => {
2664 assert_eq!(content, "abc");
2665 }
2666 other => panic!("unexpected result: {:?}", other),
2667 }
2668}