Skip to main content

xsd_schema/validation/
info.rs

1//! Validation output types returned to callers after each validation event
2//!
3//! These types represent the schema information associated with validated XML nodes.
4//! `SchemaInfo` is the primary output, returned from each `validate_*` method.
5
6use bitflags::bitflags;
7
8use crate::ids::{AttributeKey, ElementKey, NameId, NotationKey, TypeKey};
9use crate::types::value::XmlValue;
10
11/// Validity status of a validated node
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum SchemaValidity {
14    /// Validity has not been determined
15    #[default]
16    NotKnown,
17    /// The node is valid according to the schema
18    Valid,
19    /// The node is invalid according to the schema
20    Invalid,
21}
22
23/// How much validation was attempted on a node (PSVI `[validation attempted]`)
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum ValidationAttempted {
26    /// No validation was attempted
27    #[default]
28    None,
29    /// Some but not all descendants were validated
30    Partial,
31    /// Full validation was performed on this node and all descendants
32    Full,
33}
34
35/// Content type of a complex type, used to determine what children are allowed
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum ContentType {
38    /// No child elements or text content allowed
39    #[default]
40    Empty,
41    /// Text content only (simple content), possibly with attributes
42    TextOnly,
43    /// Child elements only, no interleaved text
44    ElementOnly,
45    /// Child elements with interleaved text allowed
46    Mixed,
47}
48
49/// How the final `schema_type` was determined
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum TypeSource {
52    /// From the element/attribute declaration's resolved_type
53    Declaration,
54    /// Overridden by xsi:type attribute
55    XsiType,
56    /// Selected by Conditional Type Assignment (XSD 1.1)
57    #[cfg(feature = "xsd11")]
58    TypeAlternative,
59}
60
61/// Complex-type assertion evaluation outcome (XSD 1.1)
62///
63/// Covers only the buffered complex-type assertion path. Simple-type
64/// assertion facet failures are reflected in `validity: Invalid` with
65/// `cvc-assertion` constraint through the error sink.
66#[cfg(feature = "xsd11")]
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum AssertionOutcome {
69    /// All assertions evaluated and passed
70    Passed,
71    /// One or more assertions failed (includes compile/eval/EBV errors)
72    Failed,
73    /// Assertions exist but were not evaluated (PROCESS_ASSERTIONS not set,
74    /// or evaluation deferred to an outer asserted element)
75    NotEvaluated,
76}
77
78/// Stable node identity for cross-phase correlation (e.g., linking Phase 1 results to Phase 2 DOM nodes)
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
80pub struct NodeIdentity(pub u64);
81
82bitflags! {
83    /// Flags controlling validation behavior
84    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
85    pub struct ValidationFlags: u32 {
86        /// Report warnings in addition to errors (default on).
87        const REPORT_WARNINGS = 0x0001;
88        /// Process identity constraints (key, unique, keyref).
89        ///
90        /// When this bit is clear, the runtime still parses `xs:key` /
91        /// `xs:unique` / `xs:keyref` declarations but skips constraint
92        /// evaluation during instance validation. Clear this bit to save
93        /// work when you only need type-level validation.
94        const PROCESS_IDENTITY_CONSTRAINTS = 0x0002;
95        /// Accept every attribute in the reserved
96        /// `http://www.w3.org/XML/1998/namespace` namespace (i.e. `xml:lang`,
97        /// `xml:space`, `xml:base`, `xml:id`) without checking the element's
98        /// complex type for an allowing declaration or wildcard.
99        ///
100        /// This bit is a lenient-parser convenience, **not** an XSD
101        /// conformance mode. The XSD 1.0/1.1 spec requires every attribute
102        /// — including those in the xml namespace — to be matched by a
103        /// declared `{attribute use}` or an `{attribute wildcard}` whose
104        /// namespace constraint admits the xml namespace. Enabling this
105        /// flag therefore deviates from strict conformance; it is off by
106        /// default.
107        ///
108        /// Typical use: set this bit when feeding arbitrary XML through the
109        /// validator and you want `xml:lang` to "just work" even against
110        /// schemas that don't explicitly import the xml namespace. Leave
111        /// it clear for strict XSD conformance validation (including the
112        /// W3C XSD test suite).
113        const ALLOW_XML_ATTRIBUTES = 0x0004;
114        /// Strict mode: treat all warnings as errors.
115        const STRICT_MODE = 0x0008;
116        /// Enable XSD 1.1 assertion processing (fragment buffering).
117        #[cfg(feature = "xsd11")]
118        const PROCESS_ASSERTIONS = 0x0010;
119    }
120}
121
122impl Default for ValidationFlags {
123    /// The strict-conformance defaults.
124    ///
125    /// The default only enables `REPORT_WARNINGS`. Identity constraints,
126    /// `xml:*` leniency, strict-mode warning promotion, and XSD 1.1 assertion
127    /// processing are all opt-in — combine them with `|`:
128    ///
129    /// ```
130    /// use xsd_schema::validation::ValidationFlags;
131    ///
132    /// let flags = ValidationFlags::default()
133    ///     | ValidationFlags::PROCESS_IDENTITY_CONSTRAINTS
134    ///     | ValidationFlags::ALLOW_XML_ATTRIBUTES;
135    /// # let _ = flags;
136    /// ```
137    fn default() -> Self {
138        ValidationFlags::REPORT_WARNINGS
139    }
140}
141
142/// Content processing mode for wildcard-matched elements/attributes
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
144pub enum ContentProcessing {
145    /// Must be validated against the schema; error if no declaration found
146    #[default]
147    Strict,
148    /// Validate if declaration found; skip if not
149    Lax,
150    /// Do not validate content
151    Skip,
152}
153
154/// Schema information returned after validating a node
155///
156/// Contains the resolved schema components and validation status for an element or attribute.
157#[derive(Debug, Clone)]
158pub struct SchemaInfo {
159    /// The element declaration, if one was found
160    pub element_decl: Option<ElementKey>,
161    /// The attribute declaration, if one was found
162    pub attribute_decl: Option<AttributeKey>,
163    /// The resolved schema type (simple or complex)
164    pub schema_type: Option<TypeKey>,
165    /// For union types: the actual member type that matched the value
166    pub member_type: Option<TypeKey>,
167    /// Validity status
168    pub validity: SchemaValidity,
169    /// How much validation was attempted (PSVI `[validation attempted]`)
170    pub validation_attempted: ValidationAttempted,
171    /// Whether the value was supplied by a default declaration
172    pub is_default: bool,
173    /// Whether the element was declared nil via xsi:nil="true"
174    pub is_nil: bool,
175    /// Content type of the element (Empty, TextOnly, ElementOnly, Mixed)
176    pub content_type: Option<ContentType>,
177    /// The parsed typed value from simple-type validation
178    pub typed_value: Option<XmlValue>,
179    /// The whitespace-normalized value (PSVI `[schema normalized value]`)
180    pub normalized_value: Option<String>,
181    /// Constraint codes from validation errors on this node (PSVI `[schema error code]`)
182    pub schema_error_codes: Vec<&'static str>,
183    /// Notation declaration resolved from a NOTATION-typed attribute (PSVI `[notation]`).
184    /// Only meaningful on element-end SchemaInfo; always `None` for attributes.
185    pub notation: Option<NotationKey>,
186    /// Whether this attribute was deferred due to CTA (type alternatives)
187    pub deferred_by_cta: bool,
188    /// How the `schema_type` was determined (declaration, xsi:type, or CTA)
189    pub type_source: Option<TypeSource>,
190    /// Whether CTA evaluation selected a type (even if it matches the declared type)
191    #[cfg(feature = "xsd11")]
192    pub cta_selected: bool,
193    /// Complex-type assertion outcome (XSD 1.1, end-element SchemaInfo only)
194    #[cfg(feature = "xsd11")]
195    pub assertion_outcome: Option<AssertionOutcome>,
196}
197
198impl SchemaInfo {
199    /// Create a SchemaInfo with all fields set to None/default
200    pub fn empty() -> Self {
201        SchemaInfo {
202            element_decl: None,
203            attribute_decl: None,
204            schema_type: None,
205            member_type: None,
206            validity: SchemaValidity::NotKnown,
207            validation_attempted: ValidationAttempted::None,
208            is_default: false,
209            is_nil: false,
210            content_type: None,
211            typed_value: None,
212            normalized_value: None,
213            schema_error_codes: Vec::new(),
214            notation: None,
215            deferred_by_cta: false,
216            type_source: None,
217            #[cfg(feature = "xsd11")]
218            cta_selected: false,
219            #[cfg(feature = "xsd11")]
220            assertion_outcome: None,
221        }
222    }
223
224    /// Create a SchemaInfo indicating a valid element
225    pub fn valid_element(
226        element_decl: ElementKey,
227        schema_type: TypeKey,
228        content_type: ContentType,
229    ) -> Self {
230        SchemaInfo {
231            element_decl: Some(element_decl),
232            attribute_decl: None,
233            schema_type: Some(schema_type),
234            member_type: None,
235            validity: SchemaValidity::Valid,
236            validation_attempted: ValidationAttempted::Full,
237            is_default: false,
238            is_nil: false,
239            content_type: Some(content_type),
240            typed_value: None,
241            normalized_value: None,
242            schema_error_codes: Vec::new(),
243            notation: None,
244            deferred_by_cta: false,
245            type_source: Some(TypeSource::Declaration),
246            #[cfg(feature = "xsd11")]
247            cta_selected: false,
248            #[cfg(feature = "xsd11")]
249            assertion_outcome: None,
250        }
251    }
252
253    /// Create a SchemaInfo indicating a valid attribute
254    pub fn valid_attribute(attribute_decl: AttributeKey, schema_type: TypeKey) -> Self {
255        SchemaInfo {
256            element_decl: None,
257            attribute_decl: Some(attribute_decl),
258            schema_type: Some(schema_type),
259            member_type: None,
260            validity: SchemaValidity::Valid,
261            validation_attempted: ValidationAttempted::Full,
262            is_default: false,
263            is_nil: false,
264            content_type: None,
265            typed_value: None,
266            normalized_value: None,
267            schema_error_codes: Vec::new(),
268            notation: None,
269            deferred_by_cta: false,
270            type_source: Some(TypeSource::Declaration),
271            #[cfg(feature = "xsd11")]
272            cta_selected: false,
273            #[cfg(feature = "xsd11")]
274            assertion_outcome: None,
275        }
276    }
277
278    /// Create a SchemaInfo with Invalid validity
279    pub fn invalid() -> Self {
280        SchemaInfo {
281            validity: SchemaValidity::Invalid,
282            ..SchemaInfo::empty()
283        }
284    }
285
286    /// Returns `true` if the resolved schema type is a simple type.
287    pub fn is_simple_type(&self) -> bool {
288        matches!(self.schema_type, Some(TypeKey::Simple(_)))
289    }
290
291    /// Returns `true` if the resolved schema type is a complex type.
292    pub fn is_complex_type(&self) -> bool {
293        matches!(self.schema_type, Some(TypeKey::Complex(_)))
294    }
295}
296
297/// An expected element in the current content model position
298#[derive(Debug, Clone)]
299pub struct ExpectedElement {
300    /// Local name of the expected element
301    pub local_name: NameId,
302    /// Namespace of the expected element
303    pub namespace: Option<NameId>,
304    /// The element declaration key, if available
305    pub element_key: Option<ElementKey>,
306}
307
308/// An expected attribute for the current element type
309#[derive(Debug, Clone)]
310pub struct ExpectedAttribute {
311    /// Local name of the attribute
312    pub local_name: NameId,
313    /// Namespace of the attribute
314    pub namespace: Option<NameId>,
315    /// The attribute declaration key
316    pub attribute_key: Option<AttributeKey>,
317    /// Whether the attribute is required
318    pub required: bool,
319}
320
321/// A default attribute that should be added to the element
322#[derive(Debug, Clone)]
323pub struct DefaultAttribute {
324    /// Local name of the attribute
325    pub local_name: NameId,
326    /// Namespace of the attribute
327    pub namespace: Option<NameId>,
328    /// The attribute declaration key
329    pub attribute_key: AttributeKey,
330    /// The default value
331    pub value: String,
332}
333
334/// An inherited attribute from an ancestor element (XSD 1.1 §3.3.5.6).
335///
336/// Represents an entry in the PSVI `[inherited attributes]` property.
337#[cfg(feature = "xsd11")]
338#[derive(Debug, Clone)]
339pub struct InheritedAttribute {
340    /// Local name of the attribute
341    pub local_name: NameId,
342    /// Namespace of the attribute
343    pub namespace: Option<NameId>,
344    /// The governing attribute declaration key, if known
345    pub attribute_key: Option<AttributeKey>,
346    /// The inherited attribute value
347    pub value: String,
348}
349
350/// A schema location hint extracted from `xsi:schemaLocation`.
351///
352/// Pairs a namespace URI with a schema location URI plus the base URI
353/// of the instance document element where the hint was found (needed to
354/// resolve relative location URIs).
355#[derive(Debug, Clone, PartialEq, Eq)]
356pub struct SchemaLocationHint {
357    /// The namespace URI (first token of each pair in `xsi:schemaLocation`).
358    pub namespace: String,
359    /// The schema location URI (second token of each pair).
360    pub location: String,
361    /// Base URI of the instance document at the point where this hint was
362    /// found. Empty if no base URI was set on the runtime.
363    pub base_uri: String,
364}
365
366/// A no-namespace schema location hint extracted from
367/// `xsi:noNamespaceSchemaLocation`.
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct NoNamespaceSchemaLocationHint {
370    /// The schema location URI.
371    pub location: String,
372    /// Base URI of the instance document at the point where this hint was
373    /// found. Empty if no base URI was set on the runtime.
374    pub base_uri: String,
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380
381    #[test]
382    fn test_schema_info_empty() {
383        let info = SchemaInfo::empty();
384        assert_eq!(info.validity, SchemaValidity::NotKnown);
385        assert!(info.element_decl.is_none());
386        assert!(info.attribute_decl.is_none());
387        assert!(info.schema_type.is_none());
388        assert!(info.member_type.is_none());
389        assert!(!info.is_default);
390        assert!(!info.is_nil);
391        assert!(info.content_type.is_none());
392        assert!(info.typed_value.is_none());
393        assert!(info.type_source.is_none());
394        #[cfg(feature = "xsd11")]
395        {
396            assert!(!info.cta_selected);
397            assert!(info.assertion_outcome.is_none());
398        }
399    }
400
401    #[test]
402    fn test_schema_info_invalid() {
403        let info = SchemaInfo::invalid();
404        assert_eq!(info.validity, SchemaValidity::Invalid);
405        assert!(info.element_decl.is_none());
406    }
407
408    #[test]
409    fn test_is_simple_type() {
410        let info = SchemaInfo::empty();
411        assert!(!info.is_simple_type());
412        assert!(!info.is_complex_type());
413
414        use slotmap::SlotMap;
415        let mut sm: SlotMap<crate::ids::SimpleTypeKey, ()> = SlotMap::with_key();
416        let sk = sm.insert(());
417        let mut info = SchemaInfo::empty();
418        info.schema_type = Some(TypeKey::Simple(sk));
419        assert!(info.is_simple_type());
420        assert!(!info.is_complex_type());
421    }
422
423    #[test]
424    fn test_is_complex_type() {
425        use slotmap::SlotMap;
426        let mut sm: SlotMap<crate::ids::ComplexTypeKey, ()> = SlotMap::with_key();
427        let ck = sm.insert(());
428        let mut info = SchemaInfo::empty();
429        info.schema_type = Some(TypeKey::Complex(ck));
430        assert!(info.is_complex_type());
431        assert!(!info.is_simple_type());
432    }
433
434    #[test]
435    fn test_schema_validity_default() {
436        let v = SchemaValidity::default();
437        assert_eq!(v, SchemaValidity::NotKnown);
438    }
439
440    #[test]
441    fn test_content_type_default() {
442        let ct = ContentType::default();
443        assert_eq!(ct, ContentType::Empty);
444    }
445
446    #[test]
447    fn test_content_processing_default() {
448        let cp = ContentProcessing::default();
449        assert_eq!(cp, ContentProcessing::Strict);
450    }
451
452    #[test]
453    fn test_validation_flags_default() {
454        let flags = ValidationFlags::default();
455        assert!(flags.contains(ValidationFlags::REPORT_WARNINGS));
456        // Strict-conformance defaults: ALLOW_XML_ATTRIBUTES, identity
457        // constraints, and strict-mode warning promotion are all opt-in.
458        assert!(!flags.contains(ValidationFlags::ALLOW_XML_ATTRIBUTES));
459        assert!(!flags.contains(ValidationFlags::PROCESS_IDENTITY_CONSTRAINTS));
460        assert!(!flags.contains(ValidationFlags::STRICT_MODE));
461    }
462
463    #[test]
464    fn test_validation_flags_bitops() {
465        let flags = ValidationFlags::REPORT_WARNINGS | ValidationFlags::STRICT_MODE;
466        assert!(flags.contains(ValidationFlags::REPORT_WARNINGS));
467        assert!(flags.contains(ValidationFlags::STRICT_MODE));
468        assert!(!flags.contains(ValidationFlags::PROCESS_IDENTITY_CONSTRAINTS));
469
470        let combined = flags | ValidationFlags::PROCESS_IDENTITY_CONSTRAINTS;
471        assert!(combined.contains(ValidationFlags::PROCESS_IDENTITY_CONSTRAINTS));
472    }
473
474    #[cfg(feature = "xsd11")]
475    #[test]
476    fn test_process_assertions_flag() {
477        let default_flags = ValidationFlags::default();
478        assert!(
479            !default_flags.contains(ValidationFlags::PROCESS_ASSERTIONS),
480            "PROCESS_ASSERTIONS must not be in defaults"
481        );
482        let with_flag = default_flags | ValidationFlags::PROCESS_ASSERTIONS;
483        assert!(with_flag.contains(ValidationFlags::PROCESS_ASSERTIONS));
484        // Original defaults still present
485        assert!(with_flag.contains(ValidationFlags::REPORT_WARNINGS));
486        // ALLOW_XML_ATTRIBUTES is no longer in the default — verify it stays
487        // opt-in when callers explicitly mix it in.
488        assert!(!with_flag.contains(ValidationFlags::ALLOW_XML_ATTRIBUTES));
489        let with_xml = with_flag | ValidationFlags::ALLOW_XML_ATTRIBUTES;
490        assert!(with_xml.contains(ValidationFlags::ALLOW_XML_ATTRIBUTES));
491    }
492
493    #[test]
494    fn test_node_identity_eq() {
495        let a = NodeIdentity(42);
496        let b = NodeIdentity(42);
497        let c = NodeIdentity(99);
498        assert_eq!(a, b);
499        assert_ne!(a, c);
500    }
501}