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
  7#![deny(
  8    non_camel_case_types,
  9    non_snake_case,
 10    unsafe_code,
 11    unused_variables,
 12    unused_mut,
 13    dead_code
 14)]
 15
 16mod helpers {
 17    // we isolate the helpers into a module, because we do not want to have
 18    // them in scope below.
 19    // this is to ensure that the macros do not have hidden dependencies on
 20    // any specific names being imported.
 21    use minidom::Element;
 22    use xso::{error::FromElementError, transform, try_from_element, FromXml, IntoXml};
 23
 24    pub(super) fn roundtrip_full<T: IntoXml + FromXml + PartialEq + std::fmt::Debug + Clone>(
 25        s: &str,
 26    ) {
 27        let initial: Element = s.parse().unwrap();
 28        let structural: T = match try_from_element(initial.clone()) {
 29            Ok(v) => v,
 30            Err(e) => panic!("failed to parse from {:?}: {}", s, e),
 31        };
 32        let recovered =
 33            transform(structural.clone()).expect("roundtrip did not produce an element");
 34        assert_eq!(initial, recovered);
 35        let structural2: T = match try_from_element(recovered) {
 36            Ok(v) => v,
 37            Err(e) => panic!("failed to parse from serialisation of {:?}: {}", s, e),
 38        };
 39        assert_eq!(structural, structural2);
 40    }
 41
 42    pub(super) fn parse_str<T: FromXml>(s: &str) -> Result<T, FromElementError> {
 43        let initial: Element = s.parse().unwrap();
 44        try_from_element(initial)
 45    }
 46}
 47
 48use self::helpers::{parse_str, roundtrip_full};
 49
 50use xso::{FromXml, IntoXml};
 51
 52// these are adverserial local names in order to trigger any issues with
 53// unqualified names in the macro expansions.
 54#[allow(dead_code, non_snake_case)]
 55fn Err() {}
 56#[allow(dead_code, non_snake_case)]
 57fn Ok() {}
 58#[allow(dead_code, non_snake_case)]
 59fn Some() {}
 60#[allow(dead_code, non_snake_case)]
 61fn None() {}
 62#[allow(dead_code)]
 63type Option = ((),);
 64#[allow(dead_code)]
 65type Result = ((),);
 66
 67static NS1: &str = "urn:example:ns1";
 68
 69#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
 70#[xml(namespace = NS1, name = "foo")]
 71struct Empty;
 72
 73#[test]
 74fn empty_roundtrip() {
 75    #[allow(unused_imports)]
 76    use std::{
 77        option::Option::{None, Some},
 78        result::Result::{Err, Ok},
 79    };
 80    roundtrip_full::<Empty>("<foo xmlns='urn:example:ns1'/>");
 81}
 82
 83#[test]
 84fn empty_name_mismatch() {
 85    #[allow(unused_imports)]
 86    use std::{
 87        option::Option::{None, Some},
 88        result::Result::{Err, Ok},
 89    };
 90    match parse_str::<Empty>("<bar xmlns='urn:example:ns1'/>") {
 91        Err(xso::error::FromElementError::Mismatch(..)) => (),
 92        other => panic!("unexpected result: {:?}", other),
 93    }
 94}
 95
 96#[test]
 97fn empty_namespace_mismatch() {
 98    #[allow(unused_imports)]
 99    use std::{
100        option::Option::{None, Some},
101        result::Result::{Err, Ok},
102    };
103    match parse_str::<Empty>("<foo xmlns='urn:example:ns2'/>") {
104        Err(xso::error::FromElementError::Mismatch(..)) => (),
105        other => panic!("unexpected result: {:?}", other),
106    }
107}
108
109#[test]
110fn empty_unexpected_attribute() {
111    #[allow(unused_imports)]
112    use std::{
113        option::Option::{None, Some},
114        result::Result::{Err, Ok},
115    };
116    match parse_str::<Empty>("<foo xmlns='urn:example:ns1' fnord='bar'/>") {
117        Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e))) => {
118            assert_eq!(e, "Unknown attribute in foo element.");
119        }
120        other => panic!("unexpected result: {:?}", other),
121    }
122}
123
124#[test]
125fn empty_unexpected_child() {
126    #[allow(unused_imports)]
127    use std::{
128        option::Option::{None, Some},
129        result::Result::{Err, Ok},
130    };
131    match parse_str::<Empty>("<foo xmlns='urn:example:ns1'><coucou/></foo>") {
132        Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e))) => {
133            assert_eq!(e, "Unknown child in foo element.");
134        }
135        other => panic!("unexpected result: {:?}", other),
136    }
137}
138
139#[test]
140fn empty_qname_check_has_precedence_over_attr_check() {
141    #[allow(unused_imports)]
142    use std::{
143        option::Option::{None, Some},
144        result::Result::{Err, Ok},
145    };
146    match parse_str::<Empty>("<bar xmlns='urn:example:ns1' fnord='bar'/>") {
147        Err(xso::error::FromElementError::Mismatch(..)) => (),
148        other => panic!("unexpected result: {:?}", other),
149    }
150}
151
152static SOME_NAME: &::xso::exports::rxml::strings::NcNameStr = {
153    #[allow(unsafe_code)]
154    unsafe {
155        ::xso::exports::rxml::strings::NcNameStr::from_str_unchecked("bar")
156    }
157};
158
159#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
160#[xml(namespace = NS1, name = SOME_NAME)]
161struct NamePath;
162
163#[test]
164fn name_path_roundtrip() {
165    #[allow(unused_imports)]
166    use std::{
167        option::Option::{None, Some},
168        result::Result::{Err, Ok},
169    };
170    roundtrip_full::<NamePath>("<bar xmlns='urn:example:ns1'/>");
171}
172
173#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
174#[xml(namespace = "urn:example:ns2", name = "baz")]
175struct NamespaceLit;
176
177#[test]
178fn namespace_lit_roundtrip() {
179    #[allow(unused_imports)]
180    use std::{
181        option::Option::{None, Some},
182        result::Result::{Err, Ok},
183    };
184    roundtrip_full::<NamespaceLit>("<baz xmlns='urn:example:ns2'/>");
185}