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}