mirror of https://gitlab.com/xmpp-rs/xmpp-rs.git
1251 lines
45 KiB
Rust
1251 lines
45 KiB
Rust
use std::borrow::Cow;
|
|
use std::collections::BTreeMap;
|
|
|
|
use crate::core::{
|
|
error::{DynNamespaceError, Error},
|
|
Base64, DynNamespace, DynNamespaceEnum, ElementCodec, FromXml, IntoXml,
|
|
};
|
|
use crate::Element;
|
|
|
|
static TEST_NS1: &'static str = "urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2";
|
|
static TEST_NS2: &'static str = "urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f";
|
|
static XHTML: &'static str = "http://www.w3.org/1999/xhtml";
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "req-attribute")]
|
|
pub struct ReqAttribute {
|
|
#[xml(attribute)]
|
|
pub required: String,
|
|
}
|
|
|
|
#[test]
|
|
fn req_attribute_positive() {
|
|
crate::util::test::roundtrip_full::<ReqAttribute>(
|
|
"<req-attribute xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' required='foo'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn req_attribute_missing() {
|
|
match crate::util::test::parse_str::<ReqAttribute>(
|
|
"<req-attribute xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
) {
|
|
Err(e) if e.to_string().find("required").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "opt-attribute")]
|
|
pub struct OptionalAttribute {
|
|
#[xml(attribute)]
|
|
pub optional: Option<String>,
|
|
}
|
|
|
|
#[test]
|
|
fn optional_attribute_absent() {
|
|
crate::util::test::roundtrip_full::<OptionalAttribute>(
|
|
"<opt-attribute xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn optional_attribute_present() {
|
|
crate::util::test::roundtrip_full::<OptionalAttribute>(
|
|
"<opt-attribute xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' optional='foo'/>",
|
|
);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "attribute-codec")]
|
|
pub struct AttributeCodec {
|
|
#[xml(attribute(codec = Base64))]
|
|
pub data: Vec<u8>,
|
|
#[xml(attribute(codec = Base64, name = "optional-1", default))]
|
|
pub optional_1: Option<Vec<u8>>,
|
|
#[xml(attribute(codec = Base64, name = "optional-2", default))]
|
|
pub optional_2: Option<Vec<u8>>,
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_codec_positive() {
|
|
crate::util::test::roundtrip_full::<AttributeCodec>(
|
|
"<attribute-codec xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' data='aGVsbG8gd29ybGQ=' optional-1='cXVhaw=='/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_codec_decoding() {
|
|
let v = match crate::util::test::parse_str::<AttributeCodec>(
|
|
"<attribute-codec xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' data='aGVsbG8gd29ybGQ=' optional-1='cXVhaw=='/>",
|
|
) {
|
|
Ok(v) => v,
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
assert_eq!(v.data, b"hello world");
|
|
assert_eq!(v.optional_1.as_ref().map(|x| &x[..]), Some(&b"quak"[..]));
|
|
assert_eq!(v.optional_2, None);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "collect-all-unknown")]
|
|
pub struct CollectAllUnknown {
|
|
#[xml(elements)]
|
|
pub unknown: Vec<Element>,
|
|
#[xml(children)]
|
|
pub known: Vec<ReqAttribute>,
|
|
}
|
|
|
|
#[test]
|
|
fn collect_all_unknown_positive() {
|
|
crate::util::test::roundtrip_full::<CollectAllUnknown>("<collect-all-unknown xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><other/><req-attribute required='a'/><req-attribute required='b'/></collect-all-unknown>");
|
|
}
|
|
|
|
#[test]
|
|
fn collect_all_unknown_collect_has_lower_priority() {
|
|
let v: CollectAllUnknown = crate::util::test::parse_str("<collect-all-unknown xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><req-attribute required='a'/><req-attribute required='b'/><other/></collect-all-unknown>").unwrap();
|
|
assert_eq!(v.unknown.len(), 1);
|
|
assert_eq!(v.known.len(), 2);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "collect-known-ns")]
|
|
pub struct CollectSpecificNamespace {
|
|
#[xml(elements(namespace = self::TEST_NS2))]
|
|
pub known_ns: Vec<Element>,
|
|
#[xml(elements)]
|
|
pub unknown: Vec<Element>,
|
|
}
|
|
|
|
#[test]
|
|
fn collect_specific_namespace_positive() {
|
|
crate::util::test::roundtrip_full::<CollectSpecificNamespace>("<collect-known-ns xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><known-ns xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><other-ns/></collect-known-ns>");
|
|
}
|
|
|
|
#[test]
|
|
fn collect_specific_namespace_distributes_according_to_namespace() {
|
|
let v: CollectSpecificNamespace = crate::util::test::parse_str("<collect-known-ns xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><known-ns xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><other-ns/></collect-known-ns>").unwrap();
|
|
assert_eq!(v.known_ns.len(), 1);
|
|
assert!(v.known_ns[0].is("known-ns", TEST_NS2));
|
|
assert_eq!(v.unknown.len(), 1);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "collect-grandchildren")]
|
|
pub struct CollectGrandchildren {
|
|
#[xml(child(namespace = self::TEST_NS1, name = "extensions", extract(elements)))]
|
|
pub extensions: Vec<Element>,
|
|
}
|
|
|
|
#[test]
|
|
fn collect_grandchildren_roundtrip() {
|
|
crate::util::test::roundtrip_full::<CollectGrandchildren>("<collect-grandchildren xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><extensions><ext1 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><some-child with-attr='foo'/></ext1><ext2><with/><more-children/></ext2><ext3 xmlns='urn:uuid:e3069e2e-0ea2-4d9a-8848-9f36d0e06585'><one-and-done/></ext3></extensions></collect-grandchildren>");
|
|
}
|
|
|
|
#[test]
|
|
fn collect_grandchildren_detail() {
|
|
let v: CollectGrandchildren = crate::util::test::parse_str("<collect-grandchildren xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><extensions><ext1 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><some-child with-attr='foo'/></ext1><ext2><with/><more-children/></ext2><ext3 xmlns='urn:uuid:e3069e2e-0ea2-4d9a-8848-9f36d0e06585'><one-and-done/></ext3></extensions></collect-grandchildren>").unwrap();
|
|
assert_eq!(v.extensions.len(), 3);
|
|
assert!(v.extensions[0].is("ext1", TEST_NS2));
|
|
assert!(v.extensions[1].is("ext2", TEST_NS1));
|
|
assert!(v.extensions[2].is("ext3", "urn:uuid:e3069e2e-0ea2-4d9a-8848-9f36d0e06585"));
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "collect-specific")]
|
|
pub struct CollectSpecificElements {
|
|
#[xml(elements(namespace = self::TEST_NS2, name = "foo"))]
|
|
pub known: Vec<Element>,
|
|
#[xml(elements)]
|
|
pub unknown: Vec<Element>,
|
|
}
|
|
|
|
#[test]
|
|
fn collect_specific_elements_positive() {
|
|
crate::util::test::roundtrip_full::<CollectSpecificElements>("<collect-specific xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><bar xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><other-ns/></collect-specific>");
|
|
}
|
|
|
|
#[test]
|
|
fn collect_specific_elements_distributes_according_to_qname() {
|
|
let v: CollectSpecificElements = crate::util::test::parse_str("<collect-specific xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><bar xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><other-ns/></collect-specific>").unwrap();
|
|
assert_eq!(v.known.len(), 1);
|
|
assert!(v.known[0].is("foo", TEST_NS2));
|
|
assert_eq!(v.unknown.len(), 2);
|
|
assert!(v.unknown[0].is("bar", TEST_NS2));
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "generic-child")]
|
|
pub struct GenericChild {
|
|
#[xml(element(namespace = self::XHTML, name = "html"))]
|
|
pub html: Element,
|
|
}
|
|
|
|
#[test]
|
|
fn generic_child_positive() {
|
|
crate::util::test::roundtrip_full::<GenericChild>("<generic-child xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><html xmlns='http://www.w3.org/1999/xhtml'><body><h1>Hello World!</h1></body></html></generic-child>");
|
|
}
|
|
|
|
#[test]
|
|
fn generic_child_error_if_absent() {
|
|
match crate::util::test::parse_str::<GenericChild>(
|
|
"<generic-child xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
) {
|
|
Err(e) if e.to_string().find("html").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "generic-optional-child")]
|
|
pub struct GenericOptionalChild {
|
|
#[xml(element(namespace = self::XHTML, name = "html", default))]
|
|
pub html: Option<Element>,
|
|
}
|
|
|
|
#[test]
|
|
fn generic_optional_child_positive_with_child() {
|
|
crate::util::test::roundtrip_full::<GenericOptionalChild>("<generic-optional-child xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><html xmlns='http://www.w3.org/1999/xhtml'><body><h1>Hello World!</h1></body></html></generic-optional-child>");
|
|
}
|
|
|
|
#[test]
|
|
fn generic_optional_child_positive_without_child() {
|
|
crate::util::test::roundtrip_full::<GenericOptionalChild>(
|
|
"<generic-optional-child xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn generic_child_default_if_absent() {
|
|
let v = match crate::util::test::parse_str::<GenericOptionalChild>(
|
|
"<generic-optional-child xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
) {
|
|
Ok(v) => v,
|
|
other => panic!("unexpected result: {:?}", other),
|
|
};
|
|
assert!(v.html.is_none());
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "child-a")]
|
|
pub struct DemoChildA {
|
|
// text data can be captured in a field, converted using FromText/IntoText
|
|
#[xml(text)]
|
|
pub value: u32,
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "child-b")]
|
|
pub struct DemoChildB {
|
|
// text data can be captured in a field, converted using FromText/IntoText
|
|
#[xml(text)]
|
|
pub value: u32,
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "showcase")]
|
|
pub struct ShowcaseStruct {
|
|
// === ATTRIBUTES ===
|
|
// attributes can be captured into fields, converted using
|
|
// FromOptionalXmlText/IntoOptionalXmlText. Any type that implements
|
|
// FromXmlText automatically implements FromOptionalXmlText.
|
|
|
|
// attribute names default to the field name
|
|
#[xml(attribute)]
|
|
pub foo: usize,
|
|
|
|
// attributes can be renamed (short form)
|
|
#[xml(attribute = "type")]
|
|
pub type_: String,
|
|
|
|
// attributes can be renamed (long form)
|
|
#[xml(attribute(name = "super"))]
|
|
pub super_: i32,
|
|
|
|
// === CHILD ELEMENTS, TYPED ===
|
|
// children implementing IntoXml/FromXml can be captured in fields
|
|
#[xml(child)]
|
|
pub demo_a: DemoChildA,
|
|
|
|
// they can also be captured into a Vec (or other type implementing
|
|
// XmlCollection)
|
|
#[xml(children)]
|
|
pub demo_b: Vec<DemoChildB>,
|
|
|
|
// === CHILD ELEMENTS, UNTYPED ===
|
|
// child elements which cannot or should not be expressed as rust structs,
|
|
// such as entire XHTML documents, can be captured into minidom elements:
|
|
#[xml(element(namespace = self::XHTML, name = "body"))]
|
|
pub html_body: Element,
|
|
|
|
// they can be made optional, but note that they aren't just optional
|
|
// because they are specified as `Option<..>`. It is required to also set
|
|
// as `default`.
|
|
// you can even `default` the field if it is not an `Option<..>`, provided
|
|
// the type implements From<Element>, Default and Into<Option<Element>>.
|
|
#[xml(element(namespace = self::XHTML, name = "title", default))]
|
|
pub html_title: Option<Element>,
|
|
|
|
// if you need to capture many elements in an unstructured manner, we got
|
|
// you covered. the type is strict though, you need to use `Vec<Element>`.
|
|
#[xml(elements(namespace = self::XHTML, name = "li"))]
|
|
pub keywords: Vec<Element>,
|
|
|
|
// if you only know the namespace, but not anything else, you can do that,
|
|
// too. again, the type is fixed.
|
|
#[xml(elements(namespace = self::XHTML))]
|
|
pub other_xhtml_litter: Vec<Element>,
|
|
|
|
// as a last resort, you can capture everything which isn't known and
|
|
// would otherwise cause an error:
|
|
#[xml(elements)]
|
|
pub garbage: Vec<Element>,
|
|
|
|
// === EXTRACTED DATA ===
|
|
// sometimes, you don't want to make a type, just to extract the text
|
|
// value from a child element. you can do that:
|
|
#[xml(child(namespace = self::TEST_NS2, name = "lucky-number", extract(text)))]
|
|
pub lucky_number: u32,
|
|
|
|
// or if you have more of these:
|
|
#[xml(children(namespace = self::TEST_NS2, name = "number", extract(text(type = u32))))]
|
|
pub numbers: Vec<u32>,
|
|
|
|
// you can't just capture text values, attributes are good, too:
|
|
#[xml(children(namespace = self::TEST_NS2, name = "image", extract(attribute = "alt")))]
|
|
pub image_alts: Vec<String>,
|
|
|
|
// and you can even capture a child's children into a vec:
|
|
#[xml(child(namespace = self::TEST_NS1, name = "extensions", extract(elements)))]
|
|
pub grandchildren: Vec<Element>,
|
|
|
|
// if you need more than one part of the child, you can do that, but it's
|
|
// restricted to cases where you collect the data into a collection which
|
|
// implements [`XmlDataCollection`]. That is currently only the case for
|
|
// BTreeMap.
|
|
// XXX: namespaced attributes don't yet properly work due to limitations
|
|
// in minidom -> you have to hope that the prefix is ok.
|
|
#[xml(children(namespace = self::TEST_NS1, name = "body", extract(attribute = "xml:lang", text)))]
|
|
pub plaintext_bodies: BTreeMap<String, String>,
|
|
|
|
// === TEXT ===
|
|
// last but not least: you can capture text content, obviously. this is
|
|
// all text inside the element (but not its children) concatenated:
|
|
// can use any type which implements FromText/IntoText.
|
|
#[xml(text)]
|
|
pub text: String,
|
|
}
|
|
|
|
#[test]
|
|
fn showcase_struct_roundtrip() {
|
|
crate::util::test::roundtrip_full::<ShowcaseStruct>("<showcase
|
|
xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'
|
|
foo='12'
|
|
type='fnord'
|
|
super='-13'
|
|
><child-a>16</child-a><child-b>0</child-b><child-b>1</child-b><body
|
|
xmlns='http://www.w3.org/1999/xhtml'><h1>Hello World!</h1>
|
|
<h2>Q: Is xmpp-parsers awesome?</h2>
|
|
<p>The answer is: yes!</p>
|
|
</body><title xmlns='http://www.w3.org/1999/xhtml'>FAQ</title><li xmlns='http://www.w3.org/1999/xhtml'>Keyword 1</li><li xmlns='http://www.w3.org/1999/xhtml'><b>Important keyword</b></li><style xmlns='http://www.w3.org/1999/xhtml'>haha nope</style><random-element xmlns='urn:uuid:e3069e2e-0ea2-4d9a-8848-9f36d0e06585'>
|
|
<is-collected-as-garbage/>
|
|
</random-element><lucky-number xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>13</lucky-number><number xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>42</number><number xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>23</number><image xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' alt='some text'/><image xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' alt='other text'/><extensions><a/><b xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><c/></extensions><body xml:lang='de'>Hallo!</body><body xml:lang='en'>Hello!</body><body xml:lang='fi'>Terve!</body>Hello World!</showcase>");
|
|
}
|
|
|
|
#[test]
|
|
fn showcase_struct_decomposition() {
|
|
let v: ShowcaseStruct = crate::util::test::parse_str("<showcase
|
|
xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'
|
|
foo='12'
|
|
type='fnord'
|
|
super='-13'
|
|
><child-a>16</child-a><child-b>0</child-b><child-b>1</child-b><body
|
|
xmlns='http://www.w3.org/1999/xhtml'><h1>Hello World!</h1>
|
|
<h2>Q: Is xmpp-parsers awesome?</h2>
|
|
<p>The answer is: yes!</p>
|
|
</body><title xmlns='http://www.w3.org/1999/xhtml'>FAQ</title><li xmlns='http://www.w3.org/1999/xhtml'>Keyword 1</li><li xmlns='http://www.w3.org/1999/xhtml'><b>Important keyword</b></li><style xmlns='http://www.w3.org/1999/xhtml'>haha nope</style><random-element xmlns='urn:uuid:e3069e2e-0ea2-4d9a-8848-9f36d0e06585'>
|
|
<is-collected-as-garbage/>
|
|
</random-element><lucky-number xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>13</lucky-number><number xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>42</number><number xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>23</number><image xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' alt='some text'/><image xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' alt='other text'/><extensions><a/><b xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/><c/></extensions><body xml:lang='de'>Hallo!</body><body xml:lang='en'>Hello!</body><body xml:lang='fi'>Terve!</body>Hello World!</showcase>").unwrap();
|
|
assert_eq!(v.foo, 12);
|
|
assert_eq!(v.type_, "fnord");
|
|
assert_eq!(v.super_, -13);
|
|
assert_eq!(v.demo_a.value, 16);
|
|
assert_eq!(v.demo_b[0].value, 0);
|
|
assert_eq!(v.demo_b[1].value, 1);
|
|
assert_eq!(
|
|
v.html_body.children().next().unwrap().text(),
|
|
"Hello World!"
|
|
);
|
|
assert_eq!(v.html_title.unwrap().text(), "FAQ");
|
|
assert_eq!(v.keywords[0].text(), "Keyword 1");
|
|
assert_eq!(
|
|
v.keywords[1].children().next().unwrap().text(),
|
|
"Important keyword"
|
|
);
|
|
assert_eq!(v.other_xhtml_litter[0].text(), "haha nope");
|
|
assert!(v.garbage[0].is(
|
|
"random-element",
|
|
"urn:uuid:e3069e2e-0ea2-4d9a-8848-9f36d0e06585"
|
|
));
|
|
assert_eq!(v.lucky_number, 13);
|
|
assert_eq!(v.numbers[0], 42);
|
|
assert_eq!(v.numbers[1], 23);
|
|
assert_eq!(v.image_alts[0], "some text");
|
|
assert_eq!(v.image_alts[1], "other text");
|
|
assert!(v.grandchildren[0].is("a", TEST_NS1));
|
|
assert!(v.grandchildren[1].is("b", TEST_NS2));
|
|
assert!(v.grandchildren[2].is("c", TEST_NS1));
|
|
assert_eq!(v.plaintext_bodies["de"], "Hallo!");
|
|
assert_eq!(v.plaintext_bodies["en"], "Hello!");
|
|
assert_eq!(v.plaintext_bodies["fi"], "Terve!");
|
|
assert_eq!(v.text, "Hello World!");
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1)]
|
|
pub enum EnumFallback {
|
|
// enum variants are basically treated as structs, with the difference
|
|
// that their `namespace` is defaulted to the enum's namespace, if it is
|
|
// set.
|
|
// variants are distinguished by their XML QName. It is (at this point in
|
|
// time) not possible to "switch" on anything else, though there are ideas
|
|
// floating around for matching on an attribute value instead.
|
|
#[xml(name = "variant-1")]
|
|
Variant1 {
|
|
#[xml(attribute)]
|
|
foo: String,
|
|
},
|
|
|
|
// unit variants can be marked as `fallback` (terms & conditions apply).
|
|
// that means that any element matching the namespace of the enum (which
|
|
// must be set) but none of the names of the enum variants will be treated
|
|
// as that fallback.
|
|
// this is obviously very lossy, because it must be (as pointed out) a
|
|
// unit variant.
|
|
// in the future, it may be possible to use a non-unit variant or so.
|
|
// probably, this isn't a particularly great thing to do in the first
|
|
// place except under very specific conditions, so maybe don't overuse
|
|
// this.
|
|
#[xml(name = "variant-2", fallback)]
|
|
Variant2,
|
|
}
|
|
|
|
#[test]
|
|
fn enum_fallback_roundtrip_non_fallback() {
|
|
crate::util::test::roundtrip_full::<EnumFallback>(
|
|
"<variant-1 xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_fallback_roundtrip_fallback() {
|
|
crate::util::test::roundtrip_full::<EnumFallback>(
|
|
"<variant-2 xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_fallback_parse_as_fallback() {
|
|
match crate::util::test::parse_str::<EnumFallback>(
|
|
"<variant-3 xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
) {
|
|
Ok(EnumFallback::Variant2) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn enum_fallback_matching_namespace() {
|
|
match crate::util::test::parse_str::<EnumFallback>(
|
|
"<variant-3 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/>",
|
|
) {
|
|
Err(Error::TypeMismatch(_, _, _)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "flag-container")]
|
|
pub struct Flag {
|
|
#[xml(flag(namespace = self::TEST_NS1, name = "flag"))]
|
|
pub is_set: bool,
|
|
}
|
|
|
|
#[test]
|
|
fn flag_roundtrip_set() {
|
|
crate::util::test::roundtrip_full::<Flag>(
|
|
"<flag-container xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><flag/></flag-container>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn flag_roundtrip_unset() {
|
|
crate::util::test::roundtrip_full::<Flag>(
|
|
"<flag-container xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, exhaustive)]
|
|
pub enum EnumExhaustive {
|
|
// enum variants are basically treated as structs, with the difference
|
|
// that their `namespace` is defaulted to the enum's namespace, if it is
|
|
// set.
|
|
// variants are distinguished by their XML QName. It is (at this point in
|
|
// time) not possible to "switch" on anything else, though there are ideas
|
|
// floating around for matching on an attribute value instead.
|
|
#[xml(name = "variant-1")]
|
|
Variant1,
|
|
|
|
#[xml(name = "variant-2")]
|
|
Variant2,
|
|
}
|
|
|
|
#[test]
|
|
fn enum_exhaustive_roundtrip() {
|
|
crate::util::test::roundtrip_full::<EnumExhaustive>(
|
|
"<variant-1 xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_exhaustive_parse_error() {
|
|
match crate::util::test::parse_str::<EnumExhaustive>(
|
|
"<variant-3 xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
) {
|
|
Err(Error::ParseError(string)) if string.find("EnumExhaustive").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "ignore-field")]
|
|
pub struct IgnoreField {
|
|
#[xml(attribute)]
|
|
pub foo: String,
|
|
#[xml(ignore)]
|
|
pub some_data: Option<std::sync::Arc<String>>, // <- some type which is definitely not (de-)serializable.
|
|
}
|
|
|
|
#[test]
|
|
fn ignore_field_positive() {
|
|
crate::util::test::roundtrip_full::<IgnoreField>(
|
|
"<ignore-field xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
|
);
|
|
}
|
|
|
|
fn validate_test(data: &mut ValidateAndPrepare) -> Result<(), Error> {
|
|
data.actual_foo = data.foo.take();
|
|
Ok(())
|
|
}
|
|
|
|
fn prepare_test(data: &mut ValidateAndPrepare) {
|
|
data.foo = data.actual_foo.take();
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "validate-and-prepare", validate = validate_test, prepare = prepare_test)]
|
|
pub struct ValidateAndPrepare {
|
|
#[xml(attribute)]
|
|
pub foo: Option<String>,
|
|
#[xml(ignore)]
|
|
pub actual_foo: Option<String>,
|
|
}
|
|
|
|
#[test]
|
|
fn validate_and_prepare_roundtrip() {
|
|
crate::util::test::roundtrip_full::<ValidateAndPrepare>(
|
|
"<validate-and-prepare xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn validate_and_prepare_test_effects() {
|
|
let v = crate::util::test::parse_str::<ValidateAndPrepare>(
|
|
"<validate-and-prepare xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
|
)
|
|
.unwrap();
|
|
assert_eq!(v.actual_foo.as_ref().map(|x| x.as_str()), Some("bar"));
|
|
assert_eq!(v.foo, None);
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum NamespaceEnum {
|
|
Ns1,
|
|
Ns2,
|
|
}
|
|
|
|
impl DynNamespaceEnum for NamespaceEnum {
|
|
fn from_xml_text(s: &str) -> Result<Self, DynNamespaceError> {
|
|
if s == TEST_NS1 {
|
|
Ok(Self::Ns1)
|
|
} else if s == TEST_NS2 {
|
|
Ok(Self::Ns2)
|
|
} else {
|
|
Err(DynNamespaceError::Mismatch)
|
|
}
|
|
}
|
|
|
|
fn into_xml_text(self) -> String {
|
|
match self {
|
|
Self::Ns1 => TEST_NS1.to_string(),
|
|
Self::Ns2 => TEST_NS2.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq<str> for NamespaceEnum {
|
|
fn eq(&self, rhs: &str) -> bool {
|
|
match self {
|
|
Self::Ns1 => rhs == TEST_NS1,
|
|
Self::Ns2 => rhs == TEST_NS2,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, DynNamespace, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = dyn, name = "dynamic-namespace")]
|
|
pub struct WithDynamicNamespace {
|
|
#[xml(namespace)]
|
|
pub namespace: NamespaceEnum,
|
|
}
|
|
|
|
#[test]
|
|
fn with_dynamic_namespace_roundtrip_1() {
|
|
crate::util::test::roundtrip_full::<WithDynamicNamespace>(
|
|
"<dynamic-namespace xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn with_dynamic_namespace_roundtrip_2() {
|
|
crate::util::test::roundtrip_full::<WithDynamicNamespace>(
|
|
"<dynamic-namespace xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn with_dynamic_namespace_negative_wrong_ns() {
|
|
match crate::util::test::parse_str::<WithDynamicNamespace>(
|
|
"<dynamic-namespace xmlns='urn:uuid:e3069e2e-0ea2-4d9a-8848-9f36d0e06585'/>",
|
|
) {
|
|
Err(Error::TypeMismatch(..)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn with_dynamic_namespace_negative_wrong_name() {
|
|
match crate::util::test::parse_str::<WithDynamicNamespace>(
|
|
"<quak xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
) {
|
|
Err(Error::TypeMismatch(..)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml()]
|
|
pub enum EnumWithDynamicNamespace {
|
|
#[xml(namespace = dyn, name = "variant-1")]
|
|
Variant1 {
|
|
#[xml(namespace)]
|
|
namespace: NamespaceEnum,
|
|
},
|
|
#[xml(namespace = dyn, name = "variant-2")]
|
|
Variant2 {
|
|
#[xml(namespace)]
|
|
namespace: NamespaceEnum,
|
|
},
|
|
}
|
|
|
|
#[test]
|
|
fn enum_with_dynamic_namespace_roundtrip_1_1() {
|
|
crate::util::test::roundtrip_full::<EnumWithDynamicNamespace>(
|
|
"<variant-1 xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_with_dynamic_namespace_roundtrip_1_2() {
|
|
crate::util::test::roundtrip_full::<EnumWithDynamicNamespace>(
|
|
"<variant-1 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_with_dynamic_namespace_roundtrip_2_1() {
|
|
crate::util::test::roundtrip_full::<EnumWithDynamicNamespace>(
|
|
"<variant-2 xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_with_dynamic_namespace_roundtrip_2_2() {
|
|
crate::util::test::roundtrip_full::<EnumWithDynamicNamespace>(
|
|
"<variant-2 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/>",
|
|
);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = dyn, name = "dynamic-extract")]
|
|
pub struct ExtractFieldWithSuper {
|
|
#[xml(namespace)]
|
|
pub namespace: NamespaceEnum,
|
|
|
|
#[xml(child(namespace = super, name = "contents", extract(text)))]
|
|
pub contents: String,
|
|
}
|
|
|
|
#[test]
|
|
fn extract_field_with_super_roundtrip_1() {
|
|
crate::util::test::roundtrip_full::<ExtractFieldWithSuper>(
|
|
"<dynamic-extract xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><contents>foo</contents></dynamic-extract>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn extract_field_with_super_roundtrip_2() {
|
|
crate::util::test::roundtrip_full::<ExtractFieldWithSuper>(
|
|
"<dynamic-extract xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><contents>foo</contents></dynamic-extract>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn extract_field_with_super_negative() {
|
|
match crate::util::test::parse_str::<ExtractFieldWithSuper>(
|
|
"<dynamic-extract xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><contents xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'>foo</contents></dynamic-extract>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn extract_field_with_super_negative_wrong_name() {
|
|
match crate::util::test::parse_str::<ExtractFieldWithSuper>(
|
|
"<dynamic-extract xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><quak>foo</quak></dynamic-extract>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "attr-switched-enum", attribute = "key", exhaustive)]
|
|
pub enum AttributeSwitchedEnum {
|
|
#[xml(value = "variant-1")]
|
|
Variant1 {
|
|
#[xml(attribute(name = "variant-1-attr"))]
|
|
data: String,
|
|
},
|
|
#[xml(value = "variant-2")]
|
|
Variant2 {
|
|
#[xml(attribute(name = "variant-2-attr"))]
|
|
data: String,
|
|
},
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_roundtrip_variant_1() {
|
|
crate::util::test::roundtrip_full::<AttributeSwitchedEnum>(
|
|
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-1' variant-1-attr='data'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_roundtrip_variant_2() {
|
|
crate::util::test::roundtrip_full::<AttributeSwitchedEnum>(
|
|
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-2' variant-2-attr='data'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_matches_namespace() {
|
|
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
|
"<attr-switched-enum xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' key='variant-2' variant-2-attr='data'/>",
|
|
) {
|
|
Err(Error::TypeMismatch(_, _, _)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_matches_name() {
|
|
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
|
"<other-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-2' variant-2-attr='data'/>",
|
|
) {
|
|
Err(Error::TypeMismatch(_, _, _)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_rejects_unknown_value() {
|
|
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
|
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='quak'/>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("This is not a").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_rejects_missing_attribute() {
|
|
match crate::util::test::parse_str::<AttributeSwitchedEnum>(
|
|
"<attr-switched-enum xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' fnord='variant-2'/>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("discriminator attribute").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "attr-switched-enum-fallback", attribute = "key")]
|
|
pub enum AttributeSwitchedEnumFallback {
|
|
#[xml(value = "variant-1")]
|
|
Variant1 {
|
|
#[xml(attribute(name = "variant-1-attr"))]
|
|
data: String,
|
|
},
|
|
#[xml(value = "variant-2", fallback)]
|
|
Variant2 {
|
|
#[xml(attribute(name = "variant-2-attr"))]
|
|
data: String,
|
|
},
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_fallback_roundtrip_variant_1() {
|
|
crate::util::test::roundtrip_full::<AttributeSwitchedEnumFallback>(
|
|
"<attr-switched-enum-fallback xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-1' variant-1-attr='data'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_fallback_roundtrip_variant_2() {
|
|
crate::util::test::roundtrip_full::<AttributeSwitchedEnumFallback>(
|
|
"<attr-switched-enum-fallback xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-2' variant-2-attr='data'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_fallback() {
|
|
match crate::util::test::parse_str::<AttributeSwitchedEnumFallback>(
|
|
"<attr-switched-enum-fallback xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='variant-3' variant-2-attr='data'/>",
|
|
) {
|
|
Ok(v) => assert_eq!(v, AttributeSwitchedEnumFallback::Variant2 { data: "data".to_string() }),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
fn to_lower(v: &str) -> Cow<'_, str> {
|
|
let mut v = v.to_owned();
|
|
v.make_ascii_lowercase();
|
|
v.into()
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "attr-switched-enum-norm", attribute = "key", exhaustive, normalize_with = to_lower)]
|
|
pub enum AttributeSwitchedEnumNormalized {
|
|
#[xml(value = "variant-1")]
|
|
Variant1,
|
|
#[xml(value = "variant-2")]
|
|
Variant2,
|
|
}
|
|
|
|
#[test]
|
|
fn attribute_switched_enum_normalized() {
|
|
match crate::util::test::parse_str::<AttributeSwitchedEnumNormalized>(
|
|
"<attr-switched-enum-norm xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' key='VarIaNT-2'/>",
|
|
) {
|
|
Ok(v) => assert_eq!(v, AttributeSwitchedEnumNormalized::Variant2),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(element(namespace = self::TEST_NS1))]
|
|
pub struct ElementByNamespace(Element);
|
|
|
|
#[test]
|
|
fn element_by_namespace_roundtrip_1() {
|
|
crate::util::test::roundtrip_full::<ElementByNamespace>(
|
|
"<quak xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn element_by_namespace_roundtrip_2() {
|
|
crate::util::test::roundtrip_full::<ElementByNamespace>(
|
|
"<fnord xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></fnord>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn element_by_namespace_negative() {
|
|
match crate::util::test::parse_str::<ElementByNamespace>(
|
|
"<quak xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
) {
|
|
Err(Error::TypeMismatch(_, _, _)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(element(name = "quak"))]
|
|
pub struct ElementByName(Element);
|
|
|
|
#[test]
|
|
fn element_by_name_roundtrip_1() {
|
|
crate::util::test::roundtrip_full::<ElementByName>(
|
|
"<quak xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn element_by_name_roundtrip_2() {
|
|
crate::util::test::roundtrip_full::<ElementByName>(
|
|
"<quak xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn element_by_name_negative() {
|
|
match crate::util::test::parse_str::<ElementByName>(
|
|
"<no-quak xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></no-quak>",
|
|
) {
|
|
Err(Error::TypeMismatch(_, _, _)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(element)]
|
|
pub struct ElementTransparent(Element);
|
|
|
|
#[test]
|
|
fn element_transparent_roundtrip_1() {
|
|
crate::util::test::roundtrip_full::<ElementTransparent>(
|
|
"<quak xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn element_transparent_roundtrip_2() {
|
|
crate::util::test::roundtrip_full::<ElementTransparent>(
|
|
"<quak xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn element_transparent_roundtrip_3() {
|
|
crate::util::test::roundtrip_full::<ElementTransparent>(
|
|
"<fnord xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></fnord>",
|
|
);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(element(namespace = self::TEST_NS1, name = "quak"))]
|
|
pub struct ElementQualified(Element);
|
|
|
|
#[test]
|
|
fn element_qualified_roundtrip() {
|
|
crate::util::test::roundtrip_full::<ElementQualified>(
|
|
"<quak xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn element_qualified_negative_namespace() {
|
|
match crate::util::test::parse_str::<ElementQualified>(
|
|
"<quak xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></quak>",
|
|
) {
|
|
Err(Error::TypeMismatch(_, _, _)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn element_qualified_negative_name() {
|
|
match crate::util::test::parse_str::<ElementQualified>(
|
|
"<no-quak xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' a1='v1' a2='v2'><foo/><bar xmlns='quak'/></no-quak>",
|
|
) {
|
|
Err(Error::TypeMismatch(_, _, _)) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
fn is_zero(v: &i8) -> bool {
|
|
*v == 0
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "skip-if")]
|
|
pub struct ChildExtractSkipIf {
|
|
#[xml(child(namespace = self::TEST_NS1, name = "foo", extract(text), default, skip_if = is_zero))]
|
|
pub value: i8,
|
|
}
|
|
|
|
#[test]
|
|
fn child_extract_skip_if_roundtrip_absent() {
|
|
crate::util::test::roundtrip_full::<ChildExtractSkipIf>(
|
|
"<skip-if xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn child_extract_skip_if_roundtrip_present() {
|
|
crate::util::test::roundtrip_full::<ChildExtractSkipIf>(
|
|
"<skip-if xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo>10</foo></skip-if>",
|
|
);
|
|
}
|
|
|
|
fn u8_255() -> u8 {
|
|
255
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "default-func")]
|
|
pub struct DefaultFunc {
|
|
#[xml(attribute(default = u8_255))]
|
|
pub attr: u8,
|
|
|
|
#[xml(child(namespace = self::TEST_NS1, name = "value", extract(text), default = u8_255))]
|
|
pub child: u8,
|
|
}
|
|
|
|
#[test]
|
|
fn default_func_generates_values() {
|
|
let v = crate::util::test::parse_str::<DefaultFunc>(
|
|
"<default-func xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'/>",
|
|
)
|
|
.expect("parse");
|
|
assert_eq!(v.attr, 255);
|
|
assert_eq!(v.child, 255);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, DynNamespace, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = dyn, name = "outer")]
|
|
pub struct NestedDynNamespace {
|
|
#[xml(namespace)]
|
|
pub namespace: NamespaceEnum,
|
|
|
|
#[xml(child(namespace = super))]
|
|
pub inner: WithDynamicNamespace,
|
|
}
|
|
|
|
#[test]
|
|
fn nested_dyn_namespace_roundtrip_1() {
|
|
crate::util::test::roundtrip_full::<NestedDynNamespace>(
|
|
"<outer xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><dynamic-namespace/></outer>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn nested_dyn_namespace_roundtrip_2() {
|
|
crate::util::test::roundtrip_full::<NestedDynNamespace>(
|
|
"<outer xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'><dynamic-namespace/></outer>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn nested_dyn_namespace_negative_inner_mismatch() {
|
|
match crate::util::test::parse_str::<NestedDynNamespace>(
|
|
"<outer xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><dynamic-namespace xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/></outer>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn nested_dyn_namespace_serializes_inner_with_correct_namespace() {
|
|
let mut v1 = crate::util::test::parse_str::<NestedDynNamespace>(
|
|
"<outer xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><dynamic-namespace/></outer>",
|
|
)
|
|
.unwrap();
|
|
assert_eq!(v1.namespace, NamespaceEnum::Ns1);
|
|
assert_eq!(v1.inner.namespace, NamespaceEnum::Ns1);
|
|
v1.set_namespace(NamespaceEnum::Ns2);
|
|
let el: Element = v1.into();
|
|
let v2: NestedDynNamespace = el.try_into().unwrap();
|
|
assert_eq!(v2.namespace, NamespaceEnum::Ns2);
|
|
assert_eq!(v2.inner.namespace, NamespaceEnum::Ns2);
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "ignore-unknown-children", on_unknown_child = Ignore)]
|
|
pub struct IgnoreUnknownChildren;
|
|
|
|
#[test]
|
|
fn ignore_unknown_children_ignores_children() {
|
|
match crate::util::test::parse_str::<IgnoreUnknownChildren>(
|
|
"<ignore-unknown-children xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo/></ignore-unknown-children>",
|
|
) {
|
|
Ok(_) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ignore_unknown_children_rejects_attributes() {
|
|
match crate::util::test::parse_str::<IgnoreUnknownChildren>(
|
|
"<ignore-unknown-children xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown attribute").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "ignore-unknown-attributes", on_unknown_attribute = Ignore)]
|
|
pub struct IgnoreUnknownAttributes;
|
|
|
|
#[test]
|
|
fn ignore_unknown_attributes_ignores_attributes() {
|
|
match crate::util::test::parse_str::<IgnoreUnknownAttributes>(
|
|
"<ignore-unknown-attributes xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2' foo='bar'/>",
|
|
) {
|
|
Ok(_) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ignore_unknown_attributes_rejects_children() {
|
|
match crate::util::test::parse_str::<IgnoreUnknownAttributes>(
|
|
"<ignore-unknown-attributes xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo/></ignore-unknown-attributes>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "single")]
|
|
pub struct SingleEl {
|
|
#[xml(text)]
|
|
pub value: String,
|
|
}
|
|
|
|
impl ElementCodec<String> for SingleEl {
|
|
fn decode(value: Self) -> String {
|
|
value.value
|
|
}
|
|
|
|
fn encode(value: String) -> Self {
|
|
Self { value }
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "multi")]
|
|
pub struct MultiEl {
|
|
#[xml(text)]
|
|
pub value: String,
|
|
}
|
|
|
|
impl ElementCodec<String> for MultiEl {
|
|
fn decode(value: Self) -> String {
|
|
value.value
|
|
}
|
|
|
|
fn encode(value: String) -> Self {
|
|
Self { value }
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS1, name = "element-codec")]
|
|
pub struct ElementCodecTest {
|
|
#[xml(child(codec = SingleEl))]
|
|
pub single: String,
|
|
|
|
#[xml(children(codec = MultiEl))]
|
|
pub multi: Vec<String>,
|
|
}
|
|
|
|
#[test]
|
|
fn element_codec_roundtrip() {
|
|
match crate::util::test::parse_str::<ElementCodecTest>(
|
|
"<element-codec xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><single>text 1</single><multi>text 2</multi><multi>text 3</multi></element-codec>",
|
|
) {
|
|
Ok(_) => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS2, wrapped_with(namespace = self::TEST_NS1, name = "child-switched"))]
|
|
pub enum WrappedEnum {
|
|
#[xml(name = "variant-1")]
|
|
Variant1 {
|
|
#[xml(text)]
|
|
data: String,
|
|
},
|
|
#[xml(name = "variant-2")]
|
|
Variant2 {
|
|
#[xml(attribute)]
|
|
data: String,
|
|
},
|
|
}
|
|
|
|
#[test]
|
|
fn wrapped_enum_roundtrip_1() {
|
|
crate::util::test::roundtrip_full::<WrappedEnum>(
|
|
"<child-switched xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><variant-1 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>some data</variant-1></child-switched>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn wrapped_enum_enum_roundtrip_2() {
|
|
crate::util::test::roundtrip_full::<WrappedEnum>(
|
|
"<child-switched xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><variant-2 xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f' data='other data'/></child-switched>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn wrapped_enum_does_not_leak_inner_type_mismatch_1() {
|
|
match crate::util::test::parse_str::<WrappedEnum>(
|
|
"<child-switched xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo/></child-switched>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn wrapped_enum_does_not_leak_inner_type_mismatch_2() {
|
|
match crate::util::test::parse_str::<WrappedEnum>(
|
|
"<child-switched xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'/></child-switched>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[derive(FromXml, IntoXml, PartialEq, Clone, Debug)]
|
|
#[xml(namespace = self::TEST_NS2, name = "inner", wrapped_with(namespace = self::TEST_NS1, name = "outer"))]
|
|
pub struct WrappedStruct {
|
|
#[xml(text)]
|
|
pub data: String,
|
|
}
|
|
|
|
#[test]
|
|
fn wrapped_struct_roundtrip() {
|
|
crate::util::test::roundtrip_full::<WrappedStruct>(
|
|
"<outer xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><inner xmlns='urn:uuid:9a1f4eab-1cfd-464c-a16a-282877cd516f'>some data</inner></outer>",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn wrapped_struct_does_not_leak_inner_type_mismatch() {
|
|
match crate::util::test::parse_str::<WrappedEnum>(
|
|
"<child-switched xmlns='urn:uuid:41854041-fa04-4e2b-94ae-ffaefb6b24e2'><foo/></child-switched>",
|
|
) {
|
|
Err(Error::ParseError(msg)) if msg.find("Unknown child").is_some() => (),
|
|
other => panic!("unexpected result: {:?}", other),
|
|
}
|
|
}
|