Skip to main content

xsd_schema/validation/
simple.rs

1//! Simple type value validation
2//!
3//! Validates text content against XSD simple types (atomic, list, union).
4//! Reuses `VALIDATOR_REGISTRY` for lexical validation and `FacetSet` for
5//! facet constraint checking.
6
7use crate::ids::{SimpleTypeKey, TypeKey};
8use crate::parser::frames::{ComplexContentResult, DerivationMethod, SimpleTypeVariety};
9use crate::schema::SchemaSet;
10use crate::types::facets::{normalize_whitespace, FacetSet, WhitespaceMode};
11use crate::types::validators::VALIDATOR_REGISTRY;
12use crate::types::value::{XmlValue, XmlValueKind};
13use crate::types::XmlTypeCode;
14
15use super::errors::{self, ValidationError};
16
17/// Result of simple type validation
18#[derive(Debug)]
19pub struct SimpleTypeResult {
20    /// The typed value produced by validation
21    pub typed_value: XmlValue,
22    /// For union types: the member type that matched
23    pub member_type: Option<TypeKey>,
24    /// The whitespace-normalized value (PSVI `[schema normalized value]`)
25    pub normalized_value: Option<String>,
26}
27
28/// Validate a string value against a simple type.
29///
30/// Dispatches to atomic, list, or union validation depending on the type's variety.
31/// For complex types with simpleContent, walks the base type chain to find the
32/// underlying simple type.
33pub fn validate_simple_type(
34    value: &str,
35    type_key: TypeKey,
36    schema_set: &SchemaSet,
37) -> Result<SimpleTypeResult, ValidationError> {
38    validate_simple_type_inner(value, type_key, schema_set)
39}
40
41/// Value-space equality for fixed/default values (§3.3.4.3 clause 5.2.2.2.2, §3.2.4.4).
42///
43/// Fast-path lexical match; otherwise parses both strings against `type_key`
44/// and compares their typed values. Returns `false` when `type_key` is `None`
45/// or when either parse fails.
46pub fn fixed_values_equal(
47    a: &str,
48    b: &str,
49    type_key: Option<TypeKey>,
50    schema_set: &SchemaSet,
51) -> bool {
52    if a == b {
53        return true;
54    }
55    let Some(tk) = type_key else {
56        return false;
57    };
58    let Ok(a_result) = validate_simple_type(a, tk, schema_set) else {
59        return false;
60    };
61    let Ok(b_result) = validate_simple_type(b, tk, schema_set) else {
62        return false;
63    };
64    a_result.typed_value == b_result.typed_value
65}
66
67/// Value-space equality when the instance's typed value is already computed.
68///
69/// Avoids re-parsing `instance_raw`; only parses `fixed` on lexical mismatch.
70/// Returns `false` when `type_key` is `None` or when parsing `fixed` fails.
71pub fn fixed_matches_typed(
72    instance_raw: &str,
73    instance_typed: &XmlValue,
74    fixed: &str,
75    type_key: Option<TypeKey>,
76    schema_set: &SchemaSet,
77) -> bool {
78    if instance_raw == fixed {
79        return true;
80    }
81    let Some(tk) = type_key else {
82        return false;
83    };
84    let Ok(fixed_result) = validate_simple_type(fixed, tk, schema_set) else {
85        return false;
86    };
87    *instance_typed == fixed_result.typed_value
88}
89
90fn validate_simple_type_inner(
91    value: &str,
92    type_key: TypeKey,
93    schema_set: &SchemaSet,
94) -> Result<SimpleTypeResult, ValidationError> {
95    match type_key {
96        TypeKey::Simple(sk) => {
97            let st_data = match schema_set.arenas.simple_types.get(sk) {
98                Some(d) => d,
99                None => {
100                    return Err(errors::error(
101                        "cvc-simple-type",
102                        "Simple type definition not found",
103                        None,
104                    ));
105                }
106            };
107            match st_data.variety {
108                SimpleTypeVariety::Atomic => validate_atomic_type(value, sk, schema_set),
109                SimpleTypeVariety::List => validate_list_type(value, sk, schema_set),
110                SimpleTypeVariety::Union => validate_union_type(value, sk, schema_set),
111            }
112        }
113        TypeKey::Complex(ck) => {
114            // §3.4.2.2 clause 1.1: the inline <xs:simpleType> inside
115            // simpleContent/restriction is the content type when present;
116            // otherwise inherit by walking resolved_base_type.
117            let ct_data = match schema_set.arenas.complex_types.get(ck) {
118                Some(d) => d,
119                None => {
120                    return Err(errors::error(
121                        "cvc-simple-type",
122                        "Complex type definition not found",
123                        None,
124                    ));
125                }
126            };
127            let result = if let Some(inline_st) = ct_data.resolved_simple_content_type {
128                validate_simple_type_inner(value, inline_st, schema_set)?
129            } else if let Some(base_key) = ct_data.resolved_base_type {
130                validate_simple_type_inner(value, base_key, schema_set)?
131            } else {
132                // No base type — treat as anySimpleType (accept any value)
133                SimpleTypeResult {
134                    typed_value: XmlValue::untyped(value),
135                    member_type: None,
136                    normalized_value: None,
137                }
138            };
139            // §3.4.2.2 clause 1.2: a simpleContent restriction may declare
140            // local string-applicable facets (length / pattern / enumeration)
141            // that further constrain the recursively validated value. The
142            // base/inline call already applied the base type's facets; here we
143            // apply only the restriction's local set. Extension cannot add
144            // facets, so it is excluded.
145            if let ComplexContentResult::Simple(sc) = &ct_data.content {
146                if sc.derivation == DerivationMethod::Restriction && !sc.facets.is_empty() {
147                    let lex = result.normalized_value.as_deref().unwrap_or(value);
148                    sc.facets.validate_string(lex).map_err(|e| {
149                        let code = errors::facet_constraint_code(&e);
150                        errors::from_facet_error(code, e, None)
151                    })?;
152                }
153            }
154            Ok(result)
155        }
156    }
157}
158
159// ---------------------------------------------------------------------------
160// Helpers
161// ---------------------------------------------------------------------------
162
163/// Walk the resolved_base_type chain from `sk` until we find a built-in type code.
164/// Returns `None` if no built-in ancestor is found (cycle guard at depth 100).
165fn resolve_type_code(sk: SimpleTypeKey, schema_set: &SchemaSet) -> Option<XmlTypeCode> {
166    let mut current = sk;
167    for _ in 0..100 {
168        if let Some(code) = schema_set.get_type_code(current) {
169            return Some(code);
170        }
171        let st_data = schema_set.arenas.simple_types.get(current)?;
172        match st_data.resolved_base_type {
173            Some(TypeKey::Simple(base)) => current = base,
174            _ => return None,
175        }
176    }
177    None
178}
179
180/// Walk the simple-type derivation chain and return `true` if any
181/// ancestor (including `sk` itself) declares an `enumeration` facet.
182/// `FacetSet::inherit_from` does not propagate enumeration through base
183/// types, so [`collect_facets`] alone cannot answer this question.
184fn has_enumeration_in_chain(sk: SimpleTypeKey, schema_set: &SchemaSet) -> bool {
185    let mut current = sk;
186    for _ in 0..100 {
187        let Some(st_data) = schema_set.arenas.simple_types.get(current) else {
188            return false;
189        };
190        if st_data.facets.enumeration.is_some() {
191            return true;
192        }
193        match st_data.resolved_base_type {
194            Some(TypeKey::Simple(base)) => current = base,
195            _ => return false,
196        }
197    }
198    false
199}
200
201/// Collect the effective facet set for a simple type by walking the derivation chain.
202///
203/// Starts from the most-derived type's facets, then inherits from each base type.
204/// `inherit_from` only fills missing values, so derived facets take priority.
205fn collect_facets(sk: SimpleTypeKey, schema_set: &SchemaSet) -> FacetSet {
206    let st_data = match schema_set.arenas.simple_types.get(sk) {
207        Some(d) => d,
208        None => return FacetSet::new(),
209    };
210    let mut facets = st_data.facets.clone();
211
212    // Walk the base type chain
213    let mut current_base = st_data.resolved_base_type;
214    for _ in 0..100 {
215        match current_base {
216            Some(TypeKey::Simple(base_sk)) => {
217                if let Some(base_data) = schema_set.arenas.simple_types.get(base_sk) {
218                    facets.inherit_from(&base_data.facets);
219                    current_base = base_data.resolved_base_type;
220                } else {
221                    break;
222                }
223            }
224            _ => break,
225        }
226    }
227    facets
228}
229
230// ---------------------------------------------------------------------------
231// Atomic
232// ---------------------------------------------------------------------------
233
234fn validate_atomic_type(
235    value: &str,
236    sk: SimpleTypeKey,
237    schema_set: &SchemaSet,
238) -> Result<SimpleTypeResult, ValidationError> {
239    let type_code = resolve_type_code(sk, schema_set);
240
241    // Short-circuit for abstract base types — accept as untyped
242    match type_code {
243        Some(XmlTypeCode::AnySimpleType) | Some(XmlTypeCode::AnyAtomicType) | None => {
244            return Ok(SimpleTypeResult {
245                typed_value: XmlValue::untyped(value),
246                member_type: None,
247                normalized_value: None,
248            });
249        }
250        _ => {}
251    }
252    let type_code = type_code.unwrap();
253
254    let validator = match VALIDATOR_REGISTRY.get_by_code(type_code) {
255        Some(v) => v,
256        None => {
257            return Err(errors::error(
258                "cvc-datatype-valid",
259                format!("No validator for type code {:?}", type_code),
260                None,
261            ));
262        }
263    };
264
265    let facets = collect_facets(sk, schema_set);
266
267    // XSD Part 2 §3.2 / W3C bug 14388: `xs:NOTATION` is only usable via
268    // restriction with an `enumeration` facet binding the value to a
269    // declared notation. The schema may legally reference `xs:NOTATION`
270    // directly, but any instance value must fail because there is no
271    // enumeration to match against.
272    if type_code == XmlTypeCode::Notation && !has_enumeration_in_chain(sk, schema_set) {
273        return Err(errors::error(
274            "cvc-datatype-valid",
275            "xs:NOTATION cannot be used directly for instance validation; \
276             only datatypes derived from NOTATION by restriction with an \
277             enumeration facet are permitted (§3.2)",
278            None,
279        ));
280    }
281
282    // Compute normalized value (PSVI [schema normalized value])
283    let effective_ws = facets
284        .whitespace
285        .as_ref()
286        .map(|w| w.value)
287        .unwrap_or_else(|| validator.whitespace());
288    let normalized = normalize_whitespace(value, effective_ws);
289
290    let typed_value = if facets.is_empty() {
291        validator.validate(value)
292    } else {
293        validator.validate_with_facets(value, &facets)
294    };
295
296    match typed_value {
297        Ok(mut val) => {
298            // Propagate schema type so XPath2 sequence type matching works
299            if val.schema_type.is_none() {
300                val.schema_type = Some(sk);
301            }
302            // XSD 1.0 §3.2.7.2: year zero ("0000") is not allowed in dateTime,
303            // date, gYear, or gYearMonth lexical forms. XSD 1.1 explicitly
304            // permits 0000 as the representation of 1 BCE.
305            // XSD 1.0 §3.2.4 / §3.2.5 also omit `+INF` from the special
306            // float / double lexicals enumerated by XSD 1.1 §3.3.16/17.
307            if schema_set.is_xsd10() {
308                check_xsd10_year_zero(&val, value)?;
309                check_xsd10_plus_inf(&val, value)?;
310            }
311            // XSD 1.1: evaluate assertion facets
312            #[cfg(feature = "xsd11")]
313            evaluate_assertion_facets(&val, &facets, schema_set, Some(sk))?;
314            Ok(SimpleTypeResult {
315                typed_value: val,
316                member_type: None,
317                normalized_value: Some(normalized),
318            })
319        }
320        Err(type_err) => {
321            let code = errors::value_error_constraint_code(&type_err);
322            Err(errors::from_value_error(code, type_err, None))
323        }
324    }
325}
326
327/// XSD 1.0 §3.2.4 / §3.2.5: `+INF` is not in the lexical space (added in
328/// Datatypes 1.1). The collapsed value is checked against the original
329/// lexical form (after whitespace collapse, since `xs:float` / `xs:double`
330/// declare `whiteSpace = collapse`).
331fn check_xsd10_plus_inf(val: &XmlValue, raw: &str) -> Result<(), ValidationError> {
332    use crate::types::value::XmlAtomicValue;
333    let is_float_or_double = matches!(
334        val.value,
335        XmlValueKind::Atomic(XmlAtomicValue::Float(_)) | XmlValueKind::Atomic(XmlAtomicValue::Double(_))
336    );
337    if !is_float_or_double {
338        return Ok(());
339    }
340    let collapsed =
341        crate::types::facets::normalize_whitespace(raw, crate::types::facets::WhitespaceMode::Collapse);
342    if collapsed == "+INF" {
343        Err(errors::error(
344            "cvc-datatype-valid",
345            format!("'+INF' is not a valid XSD 1.0 float/double lexical form (value '{}')", raw),
346            None,
347        ))
348    } else {
349        Ok(())
350    }
351}
352
353/// XSD 1.0 §3.2.7.2: reject year 0000 in dateTime / date / gYear / gYearMonth.
354fn check_xsd10_year_zero(val: &XmlValue, raw: &str) -> Result<(), ValidationError> {
355    use crate::types::value::XmlAtomicValue;
356    let year_zero = match &val.value {
357        XmlValueKind::Atomic(XmlAtomicValue::DateTime(v)) => v.year == 0,
358        XmlValueKind::Atomic(XmlAtomicValue::Date(v)) => v.year == 0,
359        XmlValueKind::Atomic(XmlAtomicValue::GYear(v)) => v.year == 0,
360        XmlValueKind::Atomic(XmlAtomicValue::GYearMonth(v)) => v.year == 0,
361        _ => false,
362    };
363    if year_zero {
364        Err(errors::error(
365            "cvc-datatype-valid",
366            format!(
367                "Year 0000 is not a valid XSD 1.0 lexical form (value '{}'); use '-0001' for 1 BCE",
368                raw
369            ),
370            None,
371        ))
372    } else {
373        Ok(())
374    }
375}
376
377// ---------------------------------------------------------------------------
378// List
379// ---------------------------------------------------------------------------
380
381fn validate_list_type(
382    value: &str,
383    sk: SimpleTypeKey,
384    schema_set: &SchemaSet,
385) -> Result<SimpleTypeResult, ValidationError> {
386    let st_data = match schema_set.arenas.simple_types.get(sk) {
387        Some(d) => d,
388        None => {
389            return Err(errors::error(
390                "cvc-simple-type",
391                "List type definition not found",
392                None,
393            ));
394        }
395    };
396
397    let item_type_key = match st_data.resolved_item_type {
398        Some(tk) => tk,
399        None => {
400            if let Some(deferred) = &st_data.deferred_item_type_error {
401                return Err(errors::error(
402                    "src-resolve",
403                    deferred.message.clone(),
404                    None,
405                ));
406            }
407            // No item type resolved — cannot validate list items, accept as untyped
408            return Ok(SimpleTypeResult {
409                typed_value: XmlValue::untyped(value),
410                member_type: None,
411                normalized_value: None,
412            });
413        }
414    };
415
416    // Normalize whitespace (collapse) and split
417    let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
418    let items_str: Vec<&str> = if normalized.is_empty() {
419        Vec::new()
420    } else {
421        normalized.split(' ').collect()
422    };
423
424    // Validate each item
425    let mut items = Vec::with_capacity(items_str.len());
426    let mut item_type_code = XmlTypeCode::UntypedAtomic;
427    for item_str in &items_str {
428        let result = validate_simple_type_inner(item_str, item_type_key, schema_set)?;
429        item_type_code = result.typed_value.type_code;
430        match result.typed_value.value {
431            XmlValueKind::Atomic(atom) => items.push(atom),
432            XmlValueKind::UntypedAtomic(s) => {
433                items.push(crate::types::value::XmlAtomicValue::String(s));
434            }
435            _ => {
436                items.push(crate::types::value::XmlAtomicValue::String(
437                    result.typed_value.to_string_value(),
438                ));
439            }
440        }
441    }
442
443    // Check list-level facets (length, minLength, maxLength, pattern, enumeration)
444    let facets = collect_facets(sk, schema_set);
445    if !facets.is_empty() {
446        // Length constraints on list are item count
447        facets
448            .validate_list_length(items_str.len() as u64)
449            .map_err(|e| {
450                let code = errors::facet_constraint_code(&e);
451                errors::from_facet_error(code, e, None)
452            })?;
453
454        // Pattern/enumeration on the normalized string representation
455        facets
456            .validate_string_patterns_enums(&normalized)
457            .map_err(|e| {
458                let code = errors::facet_constraint_code(&e);
459                errors::from_facet_error(code, e, None)
460            })?;
461    }
462
463    // Use the list type's own code (e.g. IdRefs, NmTokens, Entities) when it
464    // has a built-in one, so that downstream consumers like collect_id_idref
465    // can distinguish list types from their item types.
466    let list_type_code = resolve_type_code(sk, schema_set)
467        .filter(|code| code.is_list())
468        .unwrap_or(item_type_code);
469
470    let typed_value = XmlValue::with_schema_type(
471        list_type_code,
472        sk,
473        XmlValueKind::List {
474            item_type: item_type_code,
475            items,
476        },
477    );
478
479    // XSD 1.1: evaluate assertion facets ($value is the sequence of list items)
480    #[cfg(feature = "xsd11")]
481    {
482        let item_sk = item_type_key.as_simple();
483        evaluate_assertion_facets(&typed_value, &facets, schema_set, item_sk)?;
484    }
485
486    Ok(SimpleTypeResult {
487        typed_value,
488        member_type: None,
489        normalized_value: Some(normalized),
490    })
491}
492
493// ---------------------------------------------------------------------------
494// Union
495// ---------------------------------------------------------------------------
496
497fn validate_union_type(
498    value: &str,
499    sk: SimpleTypeKey,
500    schema_set: &SchemaSet,
501) -> Result<SimpleTypeResult, ValidationError> {
502    let st_data = match schema_set.arenas.simple_types.get(sk) {
503        Some(d) => d,
504        None => {
505            return Err(errors::error(
506                "cvc-simple-type",
507                "Union type definition not found",
508                None,
509            ));
510        }
511    };
512
513    // Try each member type in order
514    for &member_key in &st_data.resolved_member_types {
515        if let Ok(result) = validate_simple_type_inner(value, member_key, schema_set) {
516            // Match found — check union-level facets (pattern, enumeration)
517            let facets = collect_facets(sk, schema_set);
518            if !facets.is_empty() {
519                let check_value = result.typed_value.to_string_value();
520                // §cvc-pattern-valid: patterns apply to the LEXICAL form (original value),
521                // not the canonical/parsed form (e.g. "-.5" must match "\-\.\d+" not "-0.5")
522                let lex_value = normalize_whitespace(value, WhitespaceMode::Collapse);
523                facets.validate_patterns_only(&lex_value).map_err(|e| {
524                    let code = errors::facet_constraint_code(&e);
525                    errors::from_facet_error(code, e, None)
526                })?;
527                // Enumeration checked in value space: re-parse each enum value as the
528                // matched primitive type and compare canonical forms (handles float/
529                // double rounding and duration leading-zero differences)
530                let type_code = result.typed_value.type_code;
531                facets
532                    .validate_enum_value_space(
533                        |s| {
534                            VALIDATOR_REGISTRY
535                                .validate(type_code, s)
536                                .map(|v| v.to_string_value() == check_value)
537                                .unwrap_or(false)
538                        },
539                        &check_value,
540                    )
541                    .map_err(|e| {
542                        let code = errors::facet_constraint_code(&e);
543                        errors::from_facet_error(code, e, None)
544                    })?;
545            }
546
547            // Propagate schema type on inner value from the matched member type
548            let mut inner = result.typed_value;
549            if inner.schema_type.is_none() {
550                inner.schema_type = member_key.as_simple();
551            }
552
553            // XSD 1.1: evaluate assertion facets ($value is the member-validated value)
554            #[cfg(feature = "xsd11")]
555            {
556                let item_sk = member_key.as_simple().and_then(|sk| {
557                    crate::types::sequence::resolve_list_item_schema_type(sk, schema_set)
558                });
559                evaluate_assertion_facets(&inner, &facets, schema_set, item_sk)?;
560            }
561
562            return Ok(SimpleTypeResult {
563                normalized_value: result.normalized_value,
564                typed_value: XmlValue::with_schema_type(
565                    inner.type_code,
566                    sk,
567                    XmlValueKind::Union(Box::new(inner)),
568                ),
569                member_type: Some(member_key),
570            });
571        }
572    }
573
574    // No member matched
575    Err(errors::error(
576        "cvc-simple-type",
577        format!(
578            "Value '{}' does not match any member type of the union",
579            value
580        ),
581        None,
582    ))
583}
584
585// ---------------------------------------------------------------------------
586// XSD 1.1: Assertion facet evaluation
587// ---------------------------------------------------------------------------
588
589#[cfg(feature = "xsd11")]
590fn resolve_assertion_default_ns(
591    raw: Option<&str>,
592    source: Option<&crate::parser::location::SourceRef>,
593    ns_snapshot: &crate::namespace::context::NamespaceContextSnapshot,
594    schema_set: &SchemaSet,
595) -> Option<crate::ids::NameId> {
596    // Look up the schema document that defines this assertion
597    let doc = source.and_then(|s| schema_set.documents.get(s.doc_id as usize));
598
599    // Cascade: facet-level > schema-level (of the defining document)
600    let effective = if let Some(raw) = raw {
601        Some(raw.to_string())
602    } else {
603        doc.and_then(|d| d.xpath_default_namespace)
604            .map(|id| schema_set.name_table.resolve(id))
605    };
606
607    match effective.as_deref() {
608        Some("##defaultNamespace") => ns_snapshot.default_ns,
609        Some("##targetNamespace") => doc.and_then(|d| d.target_namespace),
610        Some("##local") | None => None,
611        Some(uri) => Some(schema_set.name_table.add(uri)),
612    }
613}
614
615/// Evaluate assertion facets against a typed value (XSD 1.1).
616///
617/// Assertion facets on simpleType restrictions receive only the `$value`
618/// variable (the typed value being validated). No context node or DOM
619/// access is needed, so this evaluates inline during streaming validation.
620#[cfg(feature = "xsd11")]
621fn evaluate_assertion_facets(
622    typed_value: &XmlValue,
623    facets: &FacetSet,
624    schema_set: &SchemaSet,
625    item_schema_type: Option<SimpleTypeKey>,
626) -> Result<(), ValidationError> {
627    use crate::xpath::api::XPathExpr;
628    use crate::xpath::functions::effective_boolean_value;
629    use crate::xpath::{RoXmlNavigator, XPathContext};
630
631    if facets.assertions.is_empty() {
632        return Ok(());
633    }
634
635    for assertion in &facets.assertions {
636        if assertion.test.is_empty() {
637            continue;
638        }
639
640        // Resolve assertion source location for error reporting
641        let location = assertion
642            .source
643            .as_ref()
644            .and_then(|s| schema_set.source_maps.locate(s));
645
646        // Build XPath static context
647        let ctx = XPathContext::new(&schema_set.name_table)
648            .with_namespaces(assertion.ns_snapshot.clone())
649            .with_schema_set(schema_set);
650
651        // Set default element namespace from xpathDefaultNamespace cascade
652        let ctx = if let Some(default_ns) = resolve_assertion_default_ns(
653            assertion.xpath_default_namespace.as_deref(),
654            assertion.source.as_ref(),
655            &assertion.ns_snapshot,
656            schema_set,
657        ) {
658            ctx.with_default_element_ns(default_ns)
659        } else {
660            ctx
661        };
662
663        // Compile the XPath expression with $value declared
664        let expr =
665            XPathExpr::compile_with_vars(&assertion.test, &ctx, &["value"]).map_err(|e| {
666                errors::error(
667                    "cvc-assertion",
668                    format!(
669                        "Failed to compile assertion test '{}': {}",
670                        assertion.test, e
671                    ),
672                    location.clone(),
673                )
674            })?;
675
676        // Convert typed value to XPathValue
677        let xpath_value = typed_value.to_xpath_value::<RoXmlNavigator<'static>>(item_schema_type);
678
679        // Evaluate with $value bound
680        let result = expr
681            .evaluator(&ctx)
682            .run_with::<RoXmlNavigator<'static>, _>(|eval| {
683                eval.set_variable_by_name("value", xpath_value).unwrap();
684            })
685            .map_err(|e| {
686                errors::error(
687                    "cvc-assertion",
688                    format!(
689                        "Failed to evaluate assertion test '{}': {}",
690                        assertion.test, e
691                    ),
692                    location.clone(),
693                )
694            })?;
695
696        // Effective boolean value must be true
697        let ebv = effective_boolean_value(&result).map_err(|e| {
698            errors::error(
699                "cvc-assertion",
700                format!(
701                    "Failed to compute boolean value for assertion '{}': {}",
702                    assertion.test, e
703                ),
704                location.clone(),
705            )
706        })?;
707
708        if !ebv {
709            return Err(errors::error(
710                "cvc-assertion",
711                format!(
712                    "Assertion '{}' failed for value '{}'",
713                    assertion.test,
714                    typed_value.to_string_value()
715                ),
716                location,
717            ));
718        }
719    }
720
721    Ok(())
722}
723
724// ---------------------------------------------------------------------------
725// Tests
726// ---------------------------------------------------------------------------
727
728#[cfg(test)]
729mod tests {
730    use super::*;
731    use crate::pipeline::load_and_process_schema;
732
733    fn load_schema(xsd: &str) -> SchemaSet {
734        let mut schema_set = SchemaSet::new();
735        load_and_process_schema(xsd.as_bytes(), "test.xsd", &mut schema_set, None)
736            .expect("failed to load schema");
737        schema_set
738    }
739
740    /// Helper: get the TypeKey for a global element's type
741    fn element_type(schema_set: &SchemaSet, local_name: &str) -> TypeKey {
742        let name_id = schema_set.name_table.add(local_name);
743        let elem_key = schema_set
744            .lookup_element(None, name_id)
745            .expect("element not found");
746        schema_set.arenas.elements[elem_key]
747            .resolved_type
748            .expect("element has no resolved type")
749    }
750
751    #[test]
752    fn test_builtin_string_accepts_anything() {
753        let schema = load_schema(
754            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
755                <xs:element name="e" type="xs:string"/>
756            </xs:schema>"#,
757        );
758        let tk = element_type(&schema, "e");
759        let result = validate_simple_type("hello world", tk, &schema).unwrap();
760        assert!(result.member_type.is_none());
761        assert_eq!(result.typed_value.type_code, XmlTypeCode::String);
762    }
763
764    #[test]
765    fn test_builtin_integer_valid() {
766        let schema = load_schema(
767            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
768                <xs:element name="e" type="xs:integer"/>
769            </xs:schema>"#,
770        );
771        let tk = element_type(&schema, "e");
772        let result = validate_simple_type("42", tk, &schema).unwrap();
773        assert_eq!(result.typed_value.type_code, XmlTypeCode::Integer);
774    }
775
776    #[test]
777    fn test_builtin_integer_invalid() {
778        let schema = load_schema(
779            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
780                <xs:element name="e" type="xs:integer"/>
781            </xs:schema>"#,
782        );
783        let tk = element_type(&schema, "e");
784        let err = validate_simple_type("abc", tk, &schema).unwrap_err();
785        assert_eq!(err.constraint, "cvc-datatype-valid");
786    }
787
788    #[test]
789    fn test_builtin_boolean_valid() {
790        let schema = load_schema(
791            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
792                <xs:element name="e" type="xs:boolean"/>
793            </xs:schema>"#,
794        );
795        let tk = element_type(&schema, "e");
796        for v in &["true", "false", "1", "0"] {
797            assert!(
798                validate_simple_type(v, tk, &schema).is_ok(),
799                "failed for '{}'",
800                v
801            );
802        }
803    }
804
805    #[test]
806    fn test_builtin_boolean_invalid() {
807        let schema = load_schema(
808            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
809                <xs:element name="e" type="xs:boolean"/>
810            </xs:schema>"#,
811        );
812        let tk = element_type(&schema, "e");
813        assert!(validate_simple_type("yes", tk, &schema).is_err());
814    }
815
816    #[test]
817    fn test_user_defined_restriction_min_max() {
818        let schema = load_schema(
819            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
820                <xs:simpleType name="score">
821                    <xs:restriction base="xs:integer">
822                        <xs:minInclusive value="0"/>
823                        <xs:maxInclusive value="100"/>
824                    </xs:restriction>
825                </xs:simpleType>
826                <xs:element name="e" type="score"/>
827            </xs:schema>"#,
828        );
829        let tk = element_type(&schema, "e");
830
831        assert!(validate_simple_type("50", tk, &schema).is_ok());
832        assert!(validate_simple_type("0", tk, &schema).is_ok());
833        assert!(validate_simple_type("100", tk, &schema).is_ok());
834
835        let err = validate_simple_type("101", tk, &schema).unwrap_err();
836        assert_eq!(err.constraint, "cvc-maxInclusive-valid");
837
838        let err = validate_simple_type("-1", tk, &schema).unwrap_err();
839        assert_eq!(err.constraint, "cvc-minInclusive-valid");
840    }
841
842    #[test]
843    fn test_enumeration_facet() {
844        let schema = load_schema(
845            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
846                <xs:simpleType name="color">
847                    <xs:restriction base="xs:string">
848                        <xs:enumeration value="red"/>
849                        <xs:enumeration value="green"/>
850                        <xs:enumeration value="blue"/>
851                    </xs:restriction>
852                </xs:simpleType>
853                <xs:element name="e" type="color"/>
854            </xs:schema>"#,
855        );
856        let tk = element_type(&schema, "e");
857
858        assert!(validate_simple_type("red", tk, &schema).is_ok());
859        assert!(validate_simple_type("green", tk, &schema).is_ok());
860
861        let err = validate_simple_type("yellow", tk, &schema).unwrap_err();
862        assert_eq!(err.constraint, "cvc-enumeration-valid");
863    }
864
865    #[test]
866    fn test_pattern_facet() {
867        let schema = load_schema(
868            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
869                <xs:simpleType name="zipCode">
870                    <xs:restriction base="xs:string">
871                        <xs:pattern value="[0-9]{5}"/>
872                    </xs:restriction>
873                </xs:simpleType>
874                <xs:element name="e" type="zipCode"/>
875            </xs:schema>"#,
876        );
877        let tk = element_type(&schema, "e");
878
879        assert!(validate_simple_type("12345", tk, &schema).is_ok());
880
881        let err = validate_simple_type("1234", tk, &schema).unwrap_err();
882        assert_eq!(err.constraint, "cvc-pattern-valid");
883    }
884
885    #[test]
886    fn test_empty_string_against_integer() {
887        let schema = load_schema(
888            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
889                <xs:element name="e" type="xs:integer"/>
890            </xs:schema>"#,
891        );
892        let tk = element_type(&schema, "e");
893        assert!(validate_simple_type("", tk, &schema).is_err());
894    }
895
896    #[test]
897    fn test_empty_string_against_string() {
898        let schema = load_schema(
899            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
900                <xs:element name="e" type="xs:string"/>
901            </xs:schema>"#,
902        );
903        let tk = element_type(&schema, "e");
904        assert!(validate_simple_type("", tk, &schema).is_ok());
905    }
906
907    #[test]
908    fn test_list_type() {
909        let schema = load_schema(
910            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
911                <xs:simpleType name="intList">
912                    <xs:list itemType="xs:integer"/>
913                </xs:simpleType>
914                <xs:element name="e" type="intList"/>
915            </xs:schema>"#,
916        );
917        let tk = element_type(&schema, "e");
918
919        let result = validate_simple_type("1 2 3", tk, &schema).unwrap();
920        assert!(result.typed_value.is_list());
921
922        // Non-integer item should fail
923        assert!(validate_simple_type("1 abc 3", tk, &schema).is_err());
924    }
925
926    #[test]
927    fn test_union_type() {
928        let schema = load_schema(
929            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
930                <xs:simpleType name="intOrString">
931                    <xs:union memberTypes="xs:integer xs:string"/>
932                </xs:simpleType>
933                <xs:element name="e" type="intOrString"/>
934            </xs:schema>"#,
935        );
936        let tk = element_type(&schema, "e");
937
938        // Integer matches first
939        let result = validate_simple_type("42", tk, &schema).unwrap();
940        assert!(result.member_type.is_some());
941        assert_eq!(result.typed_value.type_code, XmlTypeCode::Integer);
942
943        // Non-integer matches xs:string
944        let result = validate_simple_type("hello", tk, &schema).unwrap();
945        assert!(result.member_type.is_some());
946        assert_eq!(result.typed_value.type_code, XmlTypeCode::String);
947    }
948}
949
950// ---------------------------------------------------------------------------
951// XSD 1.1 tests: schema_type propagation and to_xpath_value conversion
952// ---------------------------------------------------------------------------
953
954#[cfg(all(test, feature = "xsd11"))]
955mod xsd11_tests {
956    use super::*;
957    use crate::navigator::RoXmlNavigator;
958    use crate::pipeline::load_and_process_schema;
959    use crate::types::sequence::resolve_list_item_schema_type;
960    use crate::xpath::XPathValue;
961
962    fn load_schema(xsd: &str) -> SchemaSet {
963        let mut schema_set = SchemaSet::xsd11();
964        load_and_process_schema(xsd.as_bytes(), "test.xsd", &mut schema_set, None)
965            .expect("failed to load schema");
966        schema_set
967    }
968
969    fn element_type(schema_set: &SchemaSet, local_name: &str) -> TypeKey {
970        let name_id = schema_set.name_table.add(local_name);
971        let elem_key = schema_set
972            .lookup_element(None, name_id)
973            .expect("element not found");
974        schema_set.arenas.elements[elem_key]
975            .resolved_type
976            .expect("element has no resolved type")
977    }
978
979    #[test]
980    fn test_atomic_schema_type_set() {
981        let schema = load_schema(
982            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
983                <xs:simpleType name="score">
984                    <xs:restriction base="xs:integer">
985                        <xs:minInclusive value="0"/>
986                        <xs:maxInclusive value="100"/>
987                    </xs:restriction>
988                </xs:simpleType>
989                <xs:element name="e" type="score"/>
990            </xs:schema>"#,
991        );
992        let tk = element_type(&schema, "e");
993        let result = validate_simple_type("50", tk, &schema).unwrap();
994        assert!(
995            result.typed_value.schema_type.is_some(),
996            "atomic value should have schema_type set"
997        );
998    }
999
1000    #[test]
1001    fn test_list_schema_type_set() {
1002        let schema = load_schema(
1003            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1004                <xs:simpleType name="intList">
1005                    <xs:list itemType="xs:integer"/>
1006                </xs:simpleType>
1007                <xs:element name="e" type="intList"/>
1008            </xs:schema>"#,
1009        );
1010        let tk = element_type(&schema, "e");
1011        let result = validate_simple_type("1 2 3", tk, &schema).unwrap();
1012        assert!(
1013            result.typed_value.schema_type.is_some(),
1014            "list value should have schema_type set"
1015        );
1016    }
1017
1018    #[test]
1019    fn test_union_schema_type_set() {
1020        let schema = load_schema(
1021            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1022                <xs:simpleType name="intOrString">
1023                    <xs:union memberTypes="xs:integer xs:string"/>
1024                </xs:simpleType>
1025                <xs:element name="e" type="intOrString"/>
1026            </xs:schema>"#,
1027        );
1028        let tk = element_type(&schema, "e");
1029        let result = validate_simple_type("42", tk, &schema).unwrap();
1030
1031        // Outer union value has schema_type
1032        assert!(
1033            result.typed_value.schema_type.is_some(),
1034            "union value should have schema_type set"
1035        );
1036
1037        // Inner value also has schema_type (from matched member)
1038        if let XmlValueKind::Union(ref inner) = result.typed_value.value {
1039            assert!(
1040                inner.schema_type.is_some(),
1041                "inner union value should have schema_type set from matched member"
1042            );
1043        } else {
1044            panic!("expected Union variant");
1045        }
1046    }
1047
1048    #[test]
1049    fn test_list_to_xpath_value() {
1050        let schema = load_schema(
1051            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1052                <xs:simpleType name="intList">
1053                    <xs:list itemType="xs:integer"/>
1054                </xs:simpleType>
1055                <xs:element name="e" type="intList"/>
1056            </xs:schema>"#,
1057        );
1058        let tk = element_type(&schema, "e");
1059        let result = validate_simple_type("1 2 3", tk, &schema).unwrap();
1060
1061        // Resolve list item schema type
1062        let list_sk = tk.as_simple().expect("should be simple type");
1063        let item_sk = resolve_list_item_schema_type(list_sk, &schema);
1064
1065        let xpath_val: XPathValue<RoXmlNavigator<'static>> =
1066            result.typed_value.to_xpath_value(item_sk);
1067
1068        // Should produce a sequence of 3 items
1069        let items = xpath_val.into_vec();
1070        assert_eq!(items.len(), 3, "list should produce 3 XPath items");
1071
1072        // Each item should have schema_type set
1073        for item in &items {
1074            let val = item.as_atomic().expect("should be atomic");
1075            assert!(
1076                val.schema_type.is_some(),
1077                "each list item should have schema_type"
1078            );
1079        }
1080    }
1081
1082    #[test]
1083    fn test_union_to_xpath_value() {
1084        let schema = load_schema(
1085            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1086                <xs:simpleType name="intOrString">
1087                    <xs:union memberTypes="xs:integer xs:string"/>
1088                </xs:simpleType>
1089                <xs:element name="e" type="intOrString"/>
1090            </xs:schema>"#,
1091        );
1092        let tk = element_type(&schema, "e");
1093        let result = validate_simple_type("42", tk, &schema).unwrap();
1094
1095        let xpath_val: XPathValue<RoXmlNavigator<'static>> =
1096            result.typed_value.to_xpath_value(None);
1097
1098        // Union unwraps to the inner atomic value
1099        let items = xpath_val.into_vec();
1100        assert_eq!(items.len(), 1, "union should unwrap to single item");
1101        assert!(items[0].is_atomic());
1102    }
1103
1104    #[test]
1105    fn test_atomic_to_xpath_value() {
1106        let schema = load_schema(
1107            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1108                <xs:element name="e" type="xs:integer"/>
1109            </xs:schema>"#,
1110        );
1111        let tk = element_type(&schema, "e");
1112        let result = validate_simple_type("42", tk, &schema).unwrap();
1113
1114        let xpath_val: XPathValue<RoXmlNavigator<'static>> =
1115            result.typed_value.to_xpath_value(None);
1116
1117        let items = xpath_val.into_vec();
1118        assert_eq!(items.len(), 1, "atomic should produce single item");
1119        assert!(items[0].is_atomic());
1120        assert_eq!(
1121            items[0].as_atomic().unwrap().type_code,
1122            XmlTypeCode::Integer
1123        );
1124    }
1125
1126    // -----------------------------------------------------------------------
1127    // Assertion facet tests
1128    // -----------------------------------------------------------------------
1129
1130    #[test]
1131    fn test_assertion_even_integer() {
1132        let schema = load_schema(
1133            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1134                <xs:simpleType name="evenInt">
1135                    <xs:restriction base="xs:integer">
1136                        <xs:assertion test="$value mod 2 = 0"/>
1137                    </xs:restriction>
1138                </xs:simpleType>
1139                <xs:element name="e" type="evenInt"/>
1140            </xs:schema>"#,
1141        );
1142        let tk = element_type(&schema, "e");
1143        assert!(validate_simple_type("4", tk, &schema).is_ok());
1144        assert!(validate_simple_type("0", tk, &schema).is_ok());
1145        assert!(validate_simple_type("-2", tk, &schema).is_ok());
1146
1147        let err = validate_simple_type("3", tk, &schema).unwrap_err();
1148        assert_eq!(err.constraint, "cvc-assertion");
1149
1150        let err = validate_simple_type("7", tk, &schema).unwrap_err();
1151        assert_eq!(err.constraint, "cvc-assertion");
1152    }
1153
1154    #[test]
1155    fn test_assertion_positive_value() {
1156        let schema = load_schema(
1157            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1158                <xs:simpleType name="posInt">
1159                    <xs:restriction base="xs:integer">
1160                        <xs:assertion test="$value gt 0"/>
1161                    </xs:restriction>
1162                </xs:simpleType>
1163                <xs:element name="e" type="posInt"/>
1164            </xs:schema>"#,
1165        );
1166        let tk = element_type(&schema, "e");
1167        assert!(validate_simple_type("1", tk, &schema).is_ok());
1168        assert!(validate_simple_type("100", tk, &schema).is_ok());
1169
1170        let err = validate_simple_type("0", tk, &schema).unwrap_err();
1171        assert_eq!(err.constraint, "cvc-assertion");
1172
1173        let err = validate_simple_type("-5", tk, &schema).unwrap_err();
1174        assert_eq!(err.constraint, "cvc-assertion");
1175    }
1176
1177    #[test]
1178    fn test_assertion_string_length() {
1179        let schema = load_schema(
1180            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1181                <xs:simpleType name="shortStr">
1182                    <xs:restriction base="xs:string">
1183                        <xs:assertion test="string-length($value) le 5"/>
1184                    </xs:restriction>
1185                </xs:simpleType>
1186                <xs:element name="e" type="shortStr"/>
1187            </xs:schema>"#,
1188        );
1189        let tk = element_type(&schema, "e");
1190        assert!(validate_simple_type("hello", tk, &schema).is_ok());
1191        assert!(validate_simple_type("", tk, &schema).is_ok());
1192        assert!(validate_simple_type("abcde", tk, &schema).is_ok());
1193
1194        let err = validate_simple_type("toolong", tk, &schema).unwrap_err();
1195        assert_eq!(err.constraint, "cvc-assertion");
1196    }
1197
1198    #[test]
1199    fn test_assertion_inherited() {
1200        // Assertion inherited from base type through derivation chain
1201        let schema = load_schema(
1202            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1203                <xs:simpleType name="positiveInt">
1204                    <xs:restriction base="xs:integer">
1205                        <xs:assertion test="$value gt 0"/>
1206                    </xs:restriction>
1207                </xs:simpleType>
1208                <xs:simpleType name="smallPositiveInt">
1209                    <xs:restriction base="positiveInt">
1210                        <xs:maxInclusive value="10"/>
1211                    </xs:restriction>
1212                </xs:simpleType>
1213                <xs:element name="e" type="smallPositiveInt"/>
1214            </xs:schema>"#,
1215        );
1216        let tk = element_type(&schema, "e");
1217        assert!(validate_simple_type("5", tk, &schema).is_ok());
1218        assert!(validate_simple_type("10", tk, &schema).is_ok());
1219
1220        // Fails maxInclusive
1221        assert!(validate_simple_type("11", tk, &schema).is_err());
1222        // Fails inherited assertion ($value gt 0)
1223        let err = validate_simple_type("0", tk, &schema).unwrap_err();
1224        assert_eq!(err.constraint, "cvc-assertion");
1225    }
1226
1227    #[test]
1228    fn test_assertion_compile_error() {
1229        // Invalid XPath in assertion test → cvc-assertion error
1230        let schema = load_schema(
1231            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1232                <xs:simpleType name="badAssert">
1233                    <xs:restriction base="xs:integer">
1234                        <xs:assertion test="$value @@@ invalid"/>
1235                    </xs:restriction>
1236                </xs:simpleType>
1237                <xs:element name="e" type="badAssert"/>
1238            </xs:schema>"#,
1239        );
1240        let tk = element_type(&schema, "e");
1241        let err = validate_simple_type("42", tk, &schema).unwrap_err();
1242        assert_eq!(err.constraint, "cvc-assertion");
1243        assert!(err.message.contains("compile"));
1244    }
1245
1246    #[test]
1247    fn test_assertion_with_other_facets() {
1248        // Assertion combined with pattern and range facets
1249        let schema = load_schema(
1250            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1251                <xs:simpleType name="evenScore">
1252                    <xs:restriction base="xs:integer">
1253                        <xs:minInclusive value="0"/>
1254                        <xs:maxInclusive value="100"/>
1255                        <xs:assertion test="$value mod 2 = 0"/>
1256                    </xs:restriction>
1257                </xs:simpleType>
1258                <xs:element name="e" type="evenScore"/>
1259            </xs:schema>"#,
1260        );
1261        let tk = element_type(&schema, "e");
1262        assert!(validate_simple_type("50", tk, &schema).is_ok());
1263        assert!(validate_simple_type("0", tk, &schema).is_ok());
1264        assert!(validate_simple_type("100", tk, &schema).is_ok());
1265
1266        // Fails assertion (odd number)
1267        let err = validate_simple_type("51", tk, &schema).unwrap_err();
1268        assert_eq!(err.constraint, "cvc-assertion");
1269
1270        // Fails range (out of bounds)
1271        assert!(validate_simple_type("102", tk, &schema).is_err());
1272        assert!(validate_simple_type("-2", tk, &schema).is_err());
1273    }
1274
1275    #[test]
1276    fn test_assertion_multiple() {
1277        // Multiple assertion facets on the same type
1278        let schema = load_schema(
1279            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1280                <xs:simpleType name="constrained">
1281                    <xs:restriction base="xs:integer">
1282                        <xs:assertion test="$value gt 0"/>
1283                        <xs:assertion test="$value mod 2 = 0"/>
1284                    </xs:restriction>
1285                </xs:simpleType>
1286                <xs:element name="e" type="constrained"/>
1287            </xs:schema>"#,
1288        );
1289        let tk = element_type(&schema, "e");
1290        // Must satisfy both: positive AND even
1291        assert!(validate_simple_type("2", tk, &schema).is_ok());
1292        assert!(validate_simple_type("4", tk, &schema).is_ok());
1293
1294        // Positive but odd -> fails second assertion
1295        let err = validate_simple_type("3", tk, &schema).unwrap_err();
1296        assert_eq!(err.constraint, "cvc-assertion");
1297
1298        // Even but not positive -> fails first assertion
1299        let err = validate_simple_type("0", tk, &schema).unwrap_err();
1300        assert_eq!(err.constraint, "cvc-assertion");
1301    }
1302
1303    #[test]
1304    fn test_assertion_boolean_value() {
1305        // Assertion on xs:boolean type
1306        let schema = load_schema(
1307            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1308                <xs:simpleType name="mustBeTrue">
1309                    <xs:restriction base="xs:boolean">
1310                        <xs:assertion test="$value"/>
1311                    </xs:restriction>
1312                </xs:simpleType>
1313                <xs:element name="e" type="mustBeTrue"/>
1314            </xs:schema>"#,
1315        );
1316        let tk = element_type(&schema, "e");
1317        assert!(validate_simple_type("true", tk, &schema).is_ok());
1318        assert!(validate_simple_type("1", tk, &schema).is_ok());
1319
1320        let err = validate_simple_type("false", tk, &schema).unwrap_err();
1321        assert_eq!(err.constraint, "cvc-assertion");
1322    }
1323
1324    #[test]
1325    fn test_assertion_union_with_list_member_item_typing() {
1326        // Union whose member is a list of xs:integer.
1327        // The assertion uses `instance of` on each list item, which requires
1328        // item_schema_type to be propagated through the union validation path.
1329        let schema = load_schema(
1330            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1331                <xs:simpleType name="intList">
1332                    <xs:list itemType="xs:integer"/>
1333                </xs:simpleType>
1334                <xs:simpleType name="unionWithList">
1335                    <xs:union memberTypes="intList">
1336                        <xs:simpleType>
1337                            <xs:restriction base="xs:string"/>
1338                        </xs:simpleType>
1339                    </xs:union>
1340                </xs:simpleType>
1341                <xs:simpleType name="checkedUnion">
1342                    <xs:restriction base="unionWithList">
1343                        <xs:assertion test="every $i in $value satisfies $i instance of xs:integer"/>
1344                    </xs:restriction>
1345                </xs:simpleType>
1346                <xs:element name="e" type="checkedUnion"/>
1347            </xs:schema>"#,
1348        );
1349        let tk = element_type(&schema, "e");
1350        // Space-separated integers should match the intList member and pass the assertion
1351        assert!(validate_simple_type("1 2 3", tk, &schema).is_ok());
1352    }
1353
1354    // -----------------------------------------------------------------------
1355    // xpathDefaultNamespace cascade for assertion facets
1356    // -----------------------------------------------------------------------
1357
1358    #[test]
1359    fn test_assertion_xpath_default_ns_schema_level_fallback() {
1360        // Schema-level xpathDefaultNamespace set to the XS namespace.
1361        // The assertion uses an unprefixed type name `integer` which should
1362        // resolve to xs:integer via the default element namespace cascade.
1363        let schema = load_schema(
1364            r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
1365                        xpathDefaultNamespace="http://www.w3.org/2001/XMLSchema">
1366                <xs:simpleType name="checkedInt">
1367                    <xs:restriction base="xs:integer">
1368                        <xs:assertion test="$value instance of integer"/>
1369                    </xs:restriction>
1370                </xs:simpleType>
1371                <xs:element name="e" type="checkedInt"/>
1372            </xs:schema>"#,
1373        );
1374        let tk = element_type(&schema, "e");
1375        // Unprefixed `integer` resolves to xs:integer → assertion passes
1376        assert!(validate_simple_type("42", tk, &schema).is_ok());
1377    }
1378
1379    #[test]
1380    fn test_assertion_xpath_default_ns_assertion_level_overrides_schema() {
1381        // Schema-level xpathDefaultNamespace is the XS namespace, but the
1382        // assertion element overrides it with ##local.  Now unprefixed
1383        // `integer` resolves to no-namespace, which has no matching type,
1384        // so the assertion must fail.
1385        let schema = load_schema(
1386            r###"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
1387                        xpathDefaultNamespace="http://www.w3.org/2001/XMLSchema">
1388                <xs:simpleType name="checkedInt">
1389                    <xs:restriction base="xs:integer">
1390                        <xs:assertion test="$value instance of integer"
1391                                      xpathDefaultNamespace="##local"/>
1392                    </xs:restriction>
1393                </xs:simpleType>
1394                <xs:element name="e" type="checkedInt"/>
1395            </xs:schema>"###,
1396        );
1397        let tk = element_type(&schema, "e");
1398        // ##local overrides → `integer` resolves to no-namespace → assertion fails
1399        let err = validate_simple_type("42", tk, &schema).unwrap_err();
1400        assert_eq!(err.constraint, "cvc-assertion");
1401    }
1402
1403    #[test]
1404    fn test_assertion_xpath_default_ns_target_namespace_token() {
1405        // xpathDefaultNamespace="##targetNamespace" with a non-XS target namespace.
1406        // Unprefixed `integer` resolves to http://example.com/ns (the target
1407        // namespace), not to the XS namespace, so `instance of integer` must
1408        // fail — proving the token is correctly resolved.
1409        let schema = load_schema(
1410            r###"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
1411                        targetNamespace="http://example.com/ns"
1412                        xmlns:tns="http://example.com/ns"
1413                        xpathDefaultNamespace="##targetNamespace">
1414                <xs:simpleType name="checkedInt">
1415                    <xs:restriction base="xs:integer">
1416                        <xs:assertion test="$value instance of integer"/>
1417                    </xs:restriction>
1418                </xs:simpleType>
1419                <xs:element name="e" type="tns:checkedInt"/>
1420            </xs:schema>"###,
1421        );
1422        let ns_id = schema.name_table.add("http://example.com/ns");
1423        let name_id = schema.name_table.add("e");
1424        let elem_key = schema
1425            .lookup_element(Some(ns_id), name_id)
1426            .expect("element not found");
1427        let tk = schema.arenas.elements[elem_key]
1428            .resolved_type
1429            .expect("element has no resolved type");
1430        // ##targetNamespace → http://example.com/ns → `integer` is NOT xs:integer → fails
1431        let err = validate_simple_type("42", tk, &schema).unwrap_err();
1432        assert_eq!(err.constraint, "cvc-assertion");
1433    }
1434}