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}