fastxml 0.8.1

A fast, memory-efficient XML library with XPath and XSD validation support
Documentation
//! Streaming validation and unified validation tests.

mod common;

use std::sync::Arc;

use fastxml::event::{XmlEvent, XmlEventHandler};
use fastxml::schema::types::CompiledSchema;
use fastxml::schema::validator::OnePassSchemaValidator;
use fastxml::schema::xsd::{create_builtin_schema, parse_xsd};

// =============================================================================
// Helper Functions
// =============================================================================

fn create_test_schema() -> CompiledSchema {
    let xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <xs:element name="root" type="RootType"/>

        <xs:complexType name="RootType">
            <xs:sequence>
                <xs:element name="required" type="xs:string"/>
                <xs:element name="optional" type="xs:string" minOccurs="0"/>
                <xs:element name="bounded" type="xs:string" minOccurs="1" maxOccurs="3"/>
                <xs:element name="integer" type="xs:integer" minOccurs="0"/>
            </xs:sequence>
        </xs:complexType>

        <xs:simpleType name="RestrictedString">
            <xs:restriction base="xs:string">
                <xs:minLength value="3"/>
                <xs:maxLength value="10"/>
            </xs:restriction>
        </xs:simpleType>

        <xs:simpleType name="EnumType">
            <xs:restriction base="xs:string">
                <xs:enumeration value="A"/>
                <xs:enumeration value="B"/>
                <xs:enumeration value="C"/>
            </xs:restriction>
        </xs:simpleType>

        <xs:simpleType name="PatternType">
            <xs:restriction base="xs:string">
                <xs:pattern value="[A-Z]{3}-[0-9]{4}"/>
            </xs:restriction>
        </xs:simpleType>

        <xs:simpleType name="RangeType">
            <xs:restriction base="xs:integer">
                <xs:minInclusive value="0"/>
                <xs:maxInclusive value="100"/>
            </xs:restriction>
        </xs:simpleType>
    </xs:schema>"#;

    parse_xsd(xsd.as_bytes()).unwrap()
}

// =============================================================================
// Streaming Validator Integration Tests
// =============================================================================

#[test]
fn test_validator_with_builtin_types() {
    let schema = create_builtin_schema();
    let validator = OnePassSchemaValidator::new(Arc::new(schema));

    // Initial state should be valid
    assert!(validator.is_valid());
}

#[test]
fn test_validator_events() {
    let schema = create_builtin_schema();
    let mut validator = OnePassSchemaValidator::new(Arc::new(schema));

    // Start element
    validator
        .handle(&XmlEvent::StartElement {
            name: "root".into(),
            prefix: None,
            namespace: None,
            attributes: vec![],
            namespace_decls: vec![],
            line: Some(1),
            column: Some(1),
        })
        .unwrap();

    // Text content
    validator
        .handle(&XmlEvent::Text("some text".into()))
        .unwrap();

    // End element
    validator
        .handle(&XmlEvent::EndElement {
            name: "root".into(),
            prefix: None,
        })
        .unwrap();

    // Finish
    validator.handle(&XmlEvent::Eof).unwrap();
    validator.finish().unwrap();

    assert!(validator.is_valid());
}

#[test]
fn test_validator_collects_errors() {
    let schema = create_test_schema();
    let mut validator = OnePassSchemaValidator::new(Arc::new(schema));

    // This would collect validation errors as they occur
    // The actual validation logic depends on the schema definition
    validator
        .handle(&XmlEvent::StartElement {
            name: "root".into(),
            prefix: None,
            namespace: None,
            attributes: vec![],
            namespace_decls: vec![],
            line: Some(1),
            column: Some(1),
        })
        .unwrap();

    validator
        .handle(&XmlEvent::EndElement {
            name: "root".into(),
            prefix: None,
        })
        .unwrap();

    validator.handle(&XmlEvent::Eof).unwrap();

    // Check if errors were collected
    let errors = validator.errors();
    // Root element requires 'required' child, so there should be errors
    // (depending on implementation)
    let _ = errors;
}

// =============================================================================
// Unified Validation Tests (DOM, TwoPass, OnePass, libxml comparison)
// =============================================================================

// -------------------------------------------------------------------------
// Content Model: max/min occurs
// -------------------------------------------------------------------------

const XSD_MAX_OCCURS: &str = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="item" type="xs:string" maxOccurs="2"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>"#;

const XML_MAX_OCCURS_VALID: &str = r#"<?xml version="1.0"?>
<root>
  <item>first</item>
  <item>second</item>
</root>"#;

const XML_MAX_OCCURS_EXCEEDED: &str = r#"<?xml version="1.0"?>
<root>
  <item>first</item>
  <item>second</item>
  <item>third</item>
</root>"#;

test_validation!(max_occurs_valid, XML_MAX_OCCURS_VALID, XSD_MAX_OCCURS, true);
// Note: max_occurs error position differs by validator design:
// - DOM/TwoPass: report parent element position (line 2, <root>)
// - OnePass: report child element position (line 5, <item>)
// This is because DOM/TwoPass detect the violation when validating parent's content model,
// while OnePass detects it when processing the child element itself.
test_validation!(
    max_occurs_exceeded,
    XML_MAX_OCCURS_EXCEEDED,
    XSD_MAX_OCCURS,
    false
);

// -------------------------------------------------------------------------
// Content Model: min occurs
// -------------------------------------------------------------------------

const XSD_MIN_OCCURS: &str = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="required" type="xs:string" minOccurs="1"/>
        <xs:element name="optional" type="xs:string" minOccurs="0"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>"#;

const XML_MIN_OCCURS_VALID: &str = r#"<?xml version="1.0"?>
<root>
  <required>value</required>
</root>"#;

const XML_MIN_OCCURS_MISSING: &str = r#"<?xml version="1.0"?>
<root>
  <optional>value</optional>
</root>"#;

test_validation!(min_occurs_valid, XML_MIN_OCCURS_VALID, XSD_MIN_OCCURS, true);
test_validation!(
    min_occurs_missing,
    XML_MIN_OCCURS_MISSING,
    XSD_MIN_OCCURS,
    false
);

// -------------------------------------------------------------------------
// Content Model: sequence order
// -------------------------------------------------------------------------

const XSD_SEQUENCE: &str = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="first" type="xs:string"/>
        <xs:element name="second" type="xs:string"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>"#;

const XML_SEQUENCE_VALID: &str = r#"<?xml version="1.0"?>
<root>
  <first>1</first>
  <second>2</second>
</root>"#;

const XML_SEQUENCE_WRONG_ORDER: &str = r#"<?xml version="1.0"?>
<root>
  <second>2</second>
  <first>1</first>
</root>"#;

test_validation!(sequence_order_valid, XML_SEQUENCE_VALID, XSD_SEQUENCE, true);
test_validation!(
    sequence_order_invalid,
    XML_SEQUENCE_WRONG_ORDER,
    XSD_SEQUENCE,
    false
);

// -------------------------------------------------------------------------
// Content Model: unknown element
// -------------------------------------------------------------------------

const XSD_KNOWN_ELEMENTS: &str = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="known" type="xs:string"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>"#;

const XML_UNKNOWN_ELEMENT: &str = r#"<?xml version="1.0"?>
<root>
  <known>ok</known>
  <unknown>bad</unknown>
</root>"#;

test_validation!(
    unknown_element,
    XML_UNKNOWN_ELEMENT,
    XSD_KNOWN_ELEMENTS,
    false
);

// -------------------------------------------------------------------------
// Facets: pattern
// -------------------------------------------------------------------------

const XSD_PATTERN: &str = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="code">
    <xs:simpleType>
      <xs:restriction base="xs:string">
        <xs:pattern value="[A-Z]{3}-[0-9]{4}"/>
      </xs:restriction>
    </xs:simpleType>
  </xs:element>
</xs:schema>"#;

const XML_PATTERN_VALID: &str = r#"<?xml version="1.0"?><code>ABC-1234</code>"#;
const XML_PATTERN_INVALID: &str = r#"<?xml version="1.0"?><code>abc-1234</code>"#;

test_validation!(pattern_valid, XML_PATTERN_VALID, XSD_PATTERN, true);
test_validation!(pattern_invalid, XML_PATTERN_INVALID, XSD_PATTERN, false);

// -------------------------------------------------------------------------
// Facets: enumeration
// -------------------------------------------------------------------------

const XSD_ENUM: &str = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="status">
    <xs:simpleType>
      <xs:restriction base="xs:string">
        <xs:enumeration value="active"/>
        <xs:enumeration value="inactive"/>
        <xs:enumeration value="pending"/>
      </xs:restriction>
    </xs:simpleType>
  </xs:element>
</xs:schema>"#;

const XML_ENUM_VALID: &str = r#"<?xml version="1.0"?><status>active</status>"#;
const XML_ENUM_INVALID: &str = r#"<?xml version="1.0"?><status>unknown</status>"#;

test_validation!(enumeration_valid, XML_ENUM_VALID, XSD_ENUM, true);
test_validation!(enumeration_invalid, XML_ENUM_INVALID, XSD_ENUM, false);

// -------------------------------------------------------------------------
// Facets: min/max length
// -------------------------------------------------------------------------

const XSD_LENGTH: &str = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="name">
    <xs:simpleType>
      <xs:restriction base="xs:string">
        <xs:minLength value="3"/>
        <xs:maxLength value="10"/>
      </xs:restriction>
    </xs:simpleType>
  </xs:element>
</xs:schema>"#;

const XML_LENGTH_VALID: &str = r#"<?xml version="1.0"?><name>hello</name>"#;
const XML_LENGTH_TOO_SHORT: &str = r#"<?xml version="1.0"?><name>ab</name>"#;
const XML_LENGTH_TOO_LONG: &str = r#"<?xml version="1.0"?><name>this is way too long</name>"#;

test_validation!(length_valid, XML_LENGTH_VALID, XSD_LENGTH, true);
test_validation!(length_too_short, XML_LENGTH_TOO_SHORT, XSD_LENGTH, false);
test_validation!(length_too_long, XML_LENGTH_TOO_LONG, XSD_LENGTH, false);