macro_tests.rs

   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}