// Copyright (c) 2024 Jonas Schäfer // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![deny( non_camel_case_types, non_snake_case, unsafe_code, unused_variables, unused_mut, dead_code )] mod helpers { // we isolate the helpers into a module, because we do not want to have // them in scope below. // this is to ensure that the macros do not have hidden dependencies on // any specific names being imported. use minidom::Element; use xso::{error::FromElementError, transform, try_from_element, AsXml, FromXml}; pub(super) fn roundtrip_full( s: &str, ) { let initial: Element = s.parse().unwrap(); let structural: T = match try_from_element(initial.clone()) { Ok(v) => v, Err(e) => panic!("failed to parse from {:?}: {}", s, e), }; let recovered = transform(structural.clone()).expect("roundtrip did not produce an element"); assert_eq!(initial, recovered); let structural2: T = match try_from_element(recovered) { Ok(v) => v, Err(e) => panic!("failed to parse from serialisation of {:?}: {}", s, e), }; assert_eq!(structural, structural2); } pub(super) fn parse_str(s: &str) -> Result { let initial: Element = s.parse().unwrap(); try_from_element(initial) } } use self::helpers::{parse_str, roundtrip_full}; use xso::{AsXml, FromXml}; // these are adverserial local names in order to trigger any issues with // unqualified names in the macro expansions. #[allow(dead_code, non_snake_case)] fn Err() {} #[allow(dead_code, non_snake_case)] fn Ok() {} #[allow(dead_code, non_snake_case)] fn Some() {} #[allow(dead_code, non_snake_case)] fn None() {} #[allow(dead_code)] type Option = ((),); #[allow(dead_code)] type Result = ((),); static NS1: &str = "urn:example:ns1"; static NS2: &str = "urn:example:ns2"; static FOO_NAME: &::xso::exports::rxml::strings::NcNameStr = { #[allow(unsafe_code)] unsafe { ::xso::exports::rxml::strings::NcNameStr::from_str_unchecked("foo") } }; static BAR_NAME: &::xso::exports::rxml::strings::NcNameStr = { #[allow(unsafe_code)] unsafe { ::xso::exports::rxml::strings::NcNameStr::from_str_unchecked("bar") } }; #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "foo")] struct Empty; #[test] fn empty_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[test] fn empty_name_mismatch() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; match parse_str::("") { Err(xso::error::FromElementError::Mismatch(..)) => (), other => panic!("unexpected result: {:?}", other), } } #[test] fn empty_namespace_mismatch() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; match parse_str::("") { Err(xso::error::FromElementError::Mismatch(..)) => (), other => panic!("unexpected result: {:?}", other), } } #[test] fn empty_unexpected_attribute() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; match parse_str::("") { Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e))) => { assert_eq!(e, "Unknown attribute in Empty element."); } other => panic!("unexpected result: {:?}", other), } } #[test] fn empty_unexpected_child() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; match parse_str::("") { Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e))) => { assert_eq!(e, "Unknown child in Empty element."); } other => panic!("unexpected result: {:?}", other), } } #[test] fn empty_qname_check_has_precedence_over_attr_check() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; match parse_str::("") { Err(xso::error::FromElementError::Mismatch(..)) => (), other => panic!("unexpected result: {:?}", other), } } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = BAR_NAME)] struct NamePath; #[test] fn name_path_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = "urn:example:ns2", name = "baz")] struct NamespaceLit; #[test] fn namespace_lit_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "attr")] struct RequiredAttribute { #[xml(attribute)] foo: String, } #[test] fn required_attribute_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[test] fn required_attribute_positive() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; let data = parse_str::("").unwrap(); assert_eq!(data.foo, "bar"); } #[test] fn required_attribute_missing() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; match parse_str::("") { Err(::xso::error::FromElementError::Invalid(::xso::error::Error::Other(e))) if e.contains("Required attribute field") && e.contains("missing") => { () } other => panic!("unexpected result: {:?}", other), } } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "attr")] struct RenamedAttribute { #[xml(attribute = "a1")] foo: String, #[xml(attribute = BAR_NAME)] bar: String, } #[test] fn renamed_attribute_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "attr")] struct NamespacedAttribute { #[xml(attribute(namespace = "urn:example:ns1", name = FOO_NAME))] foo_1: String, #[xml(attribute(namespace = NS2, name = "foo"))] foo_2: String, #[xml(attribute(namespace = NS1, name = BAR_NAME))] bar_1: String, #[xml(attribute(namespace = "urn:example:ns2", name = "bar"))] bar_2: String, } #[test] fn namespaced_attribute_roundtrip_a() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::( "", ); } #[test] fn namespaced_attribute_roundtrip_b() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::( "", ); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "attr")] struct PrefixedAttribute { #[xml(attribute = "xml:lang")] lang: String, } #[test] fn prefixed_attribute_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "attr")] struct RequiredNonStringAttribute { #[xml(attribute)] foo: i32, } #[test] fn required_non_string_attribute_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "attr")] struct DefaultAttribute { #[xml(attribute(default))] foo: std::option::Option, #[xml(attribute(default))] bar: std::option::Option, } #[test] fn default_attribute_roundtrip_aa() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[test] fn default_attribute_roundtrip_pa() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[test] fn default_attribute_roundtrip_ap() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[test] fn default_attribute_roundtrip_pp() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "text")] struct TextString { #[xml(text)] text: String, } #[test] fn text_string_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("hello world!"); } #[test] fn text_string_positive_preserves_whitespace() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; let el = parse_str::(" \t\n").unwrap(); assert_eq!(el.text, " \t\n"); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "text")] struct TextNonString { #[xml(text)] text: u32, } #[test] fn text_non_string_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("123456"); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "elem")] struct IgnoresWhitespaceWithoutTextConsumer; #[test] fn ignores_whitespace_without_text_consumer_positive() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; let _ = parse_str::( " \t\r\n", ) .unwrap(); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "elem")] struct FailsTextWithoutTextConsumer; #[test] fn fails_text_without_text_consumer_positive() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; match parse_str::(" quak ") { Err(::xso::error::FromElementError::Invalid(::xso::error::Error::Other(e))) if e.contains("Unexpected text") => { () } other => panic!("unexpected result: {:?}", other), } } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "text")] struct TextWithCodec { #[xml(text(codec = xso::text::EmptyAsNone))] text: std::option::Option, } #[test] fn text_with_codec_roundtrip_empty() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::(""); } #[test] fn text_with_codec_roundtrip_non_empty() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("hello"); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "parent")] struct Parent { #[xml(child)] child: RequiredAttribute, } #[test] fn parent_roundtrip() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("") } #[test] fn parent_positive() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; let v = parse_str::("") .unwrap(); assert_eq!(v.child.foo, "hello world!"); } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "parent")] struct OptionalChild { #[xml(child(default))] child: std::option::Option, } #[test] fn optional_child_roundtrip_present() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::( "", ) } #[test] fn optional_child_roundtrip_absent() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("") } #[derive(FromXml, AsXml, PartialEq, Debug, Clone)] #[xml(namespace = NS1, name = "elem")] struct BoxedChild { #[xml(child(default))] child: std::option::Option>, } #[test] fn boxed_child_roundtrip_absent() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("") } #[test] fn boxed_child_roundtrip_nested_1() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("") } #[test] fn boxed_child_roundtrip_nested_2() { #[allow(unused_imports)] use std::{ option::Option::{None, Some}, result::Result::{Err, Ok}, }; roundtrip_full::("") }