use std::sync::Arc;
use fastxml::Parser;
use fastxml::ValidationErrorType;
use fastxml::schema::types::CompiledSchema;
use fastxml::schema::{Schema, Validator};
const NS: &str = "http://example.com/occurs";
fn schema(group_min: &str, group_max: &str, elem_min: &str, elem_max: &str) -> Arc<CompiledSchema> {
let xsd = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:t="{NS}"
targetNamespace="{NS}"
elementFormDefault="qualified">
<xs:element name="Container" type="t:ContainerType"/>
<xs:complexType name="ContainerType">
<xs:sequence>
<xs:group ref="t:ItemGroup" minOccurs="{group_min}" maxOccurs="{group_max}"/>
</xs:sequence>
</xs:complexType>
<!-- `item` is a LOCAL element declared inside the named group, reached only
via the <xs:group ref> above. -->
<xs:group name="ItemGroup">
<xs:sequence>
<xs:element name="item" type="xs:string" minOccurs="{elem_min}" maxOccurs="{elem_max}"/>
</xs:sequence>
</xs:group>
</xs:schema>"#
);
let compiled = Schema::from_xsd(xsd.as_bytes()).expect("Failed to compile occurs schema");
Arc::new(compiled)
}
fn container_with_items(n: usize) -> String {
let items: String = (0..n)
.map(|i| format!(" <t:item>v{i}</t:item>\n"))
.collect();
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<t:Container xmlns:t=\"{NS}\">\n{items}</t:Container>"
)
}
fn validate_dom(schema: &Arc<CompiledSchema>, xml: &str) -> Vec<fastxml::StructuredError> {
let doc = Parser::from(xml).parse().expect("Failed to parse XML");
Validator::from(&doc)
.schema(schema.clone())
.run()
.expect("DOM validation failed")
.into_entries()
}
fn validate_onepass(schema: &Arc<CompiledSchema>, xml: &str) -> Vec<fastxml::StructuredError> {
Validator::from(xml)
.schema(schema.clone())
.run()
.expect("OnePass validation failed")
.into_entries()
}
fn is_valid(errors: &[fastxml::StructuredError]) -> bool {
errors.iter().all(|e| !e.is_error())
}
fn has_error_type(errors: &[fastxml::StructuredError], ty: ValidationErrorType) -> bool {
errors
.iter()
.filter(|e| e.is_error())
.any(|e| e.error_type == ty)
}
#[derive(Clone, Copy, Debug)]
enum Expect {
Valid,
TooFew,
TooMany,
}
#[derive(Debug)]
struct Case {
group_min: &'static str,
group_max: &'static str,
elem_min: &'static str,
elem_max: &'static str,
n: usize,
expect: Expect,
}
impl Case {
fn new(
group: (&'static str, &'static str),
elem: (&'static str, &'static str),
n: usize,
expect: Expect,
) -> Self {
Self {
group_min: group.0,
group_max: group.1,
elem_min: elem.0,
elem_max: elem.1,
n,
expect,
}
}
}
fn check(index: usize, case: &Case) {
let schema = schema(case.group_min, case.group_max, case.elem_min, case.elem_max);
let xml = container_with_items(case.n);
for (who, errors) in [
("DOM", validate_dom(&schema, &xml)),
("OnePass", validate_onepass(&schema, &xml)),
] {
let ok = match case.expect {
Expect::Valid => is_valid(&errors),
Expect::TooFew => has_error_type(&errors, ValidationErrorType::TooFewOccurrences),
Expect::TooMany => has_error_type(&errors, ValidationErrorType::TooManyOccurrences),
};
assert!(
ok,
"case #{index} [{who}] failed: {case:?}\n errors: {errors:?}"
);
}
}
#[test]
fn group_ref_occurs_multiply_with_member_bounds() {
use Expect::*;
#[rustfmt::skip]
let cases = [
Case::new(("0", "unbounded"), ("1", "1"), 0, Valid), Case::new(("0", "unbounded"), ("1", "1"), 5, Valid), Case::new(("2", "unbounded"), ("0", "unbounded"), 0, Valid), Case::new(("2", "3"), ("2", "4"), 3, TooFew), Case::new(("2", "3"), ("2", "4"), 4, Valid), Case::new(("2", "3"), ("2", "4"), 12, Valid), Case::new(("2", "3"), ("2", "4"), 13, TooMany), ];
for (i, case) in cases.iter().enumerate() {
check(i, case);
}
}