1use crate::document::buffer::BufferDocument;
13use crate::document::navigator::BufferDocNavigator;
14use crate::ids::{ComplexTypeKey, NameId, TypeKey};
15use crate::navigator::{DomNavigator, TypedValue};
16use crate::parser::frames::{AssertResult, ComplexContentResult};
17use crate::parser::location::SourceLocation;
18use crate::schema::SchemaSet;
19use crate::validation::errors::{self, ValidationError};
20use crate::validation::simple::validate_simple_type;
21use crate::xpath::api::XPathExpr;
22use crate::xpath::functions::{effective_boolean_value, XPathValue};
23use crate::xpath::XPathContext;
24
25use crate::arenas::SchemaArenas;
26
27pub(crate) struct AssertionBufferFrame {
37 pub element_ref: u32,
39 pub complex_type_key: ComplexTypeKey,
41 pub element_path: String,
44 pub location: Option<SourceLocation>,
46}
47
48pub(crate) fn has_inherited_assertions(ct_key: ComplexTypeKey, arenas: &SchemaArenas) -> bool {
56 let ct = &arenas.complex_types[ct_key];
57 if !ct.assertions.is_empty() {
58 return true;
59 }
60 let mut current = ct.resolved_base_type;
62 while let Some(TypeKey::Complex(base_key)) = current {
63 let base = &arenas.complex_types[base_key];
64 if !base.assertions.is_empty() {
65 return true;
66 }
67 current = base.resolved_base_type;
68 }
69 false
70}
71
72pub(crate) fn collect_inherited_assertions(
81 ct_key: ComplexTypeKey,
82 arenas: &SchemaArenas,
83) -> Vec<(&AssertResult, ComplexTypeKey)> {
84 let mut chain = vec![ct_key];
86 let mut current = arenas.complex_types[ct_key].resolved_base_type;
87 while let Some(TypeKey::Complex(base_key)) = current {
88 chain.push(base_key);
89 current = arenas.complex_types[base_key].resolved_base_type;
90 }
91
92 let mut result = Vec::new();
94 for &key in chain.iter().rev() {
95 let ct = &arenas.complex_types[key];
96 for assertion in &ct.assertions {
97 result.push((assertion, key));
98 }
99 }
100 result
101}
102
103fn resolve_ct_assertion_default_ns(
112 assertion: &AssertResult,
113 owner_ct_key: ComplexTypeKey,
114 schema_set: &SchemaSet,
115) -> Option<NameId> {
116 let ct = &schema_set.arenas.complex_types[owner_ct_key];
117
118 let doc = ct
120 .source
121 .as_ref()
122 .and_then(|s| schema_set.documents.get(s.doc_id as usize));
123
124 let effective = if let Some(raw) = &assertion.xpath_default_namespace {
126 Some(raw.clone())
127 } else if let Some(raw) = &ct.xpath_default_namespace {
128 Some(raw.clone())
129 } else {
130 doc.and_then(|d| d.xpath_default_namespace)
131 .map(|id| schema_set.name_table.resolve(id))
132 };
133
134 match effective.as_deref() {
135 Some("##defaultNamespace") => assertion.ns_snapshot.default_ns,
136 Some("##targetNamespace") => doc.and_then(|d| d.target_namespace),
137 Some("##local") | None => None,
138 Some(uri) => Some(schema_set.name_table.add(uri)),
139 }
140}
141
142fn compute_dollar_value<'doc>(
159 doc: &'doc BufferDocument<'doc>,
160 element_ref: u32,
161 governing_ct_key: ComplexTypeKey,
162 schema_set: &SchemaSet,
163) -> XPathValue<BufferDocNavigator<'doc>> {
164 use crate::types::value::{XmlValue, XmlValueKind};
165 use crate::xpath::iterator::XmlItem;
166
167 let ct = &schema_set.arenas.complex_types[governing_ct_key];
168 if !matches!(ct.content, ComplexContentResult::Simple(_)) {
169 return XPathValue::empty();
170 }
171
172 let nav = BufferDocNavigator::new(doc, element_ref);
173 if matches!(nav.typed_value(), TypedValue::Nilled) {
174 return XPathValue::empty();
175 }
176
177 match validate_simple_type(&nav.value(), TypeKey::Complex(governing_ct_key), schema_set) {
178 Ok(result) => {
179 if let XmlValueKind::List { item_type, items } = &result.typed_value.value {
185 let item_type_code = *item_type;
186 let xpath_items: Vec<XmlItem<BufferDocNavigator<'doc>>> = items
187 .iter()
188 .cloned()
189 .map(|atom| {
190 XmlItem::Atomic(XmlValue::new(item_type_code, XmlValueKind::Atomic(atom)))
191 })
192 .collect();
193 return XPathValue::from_sequence(xpath_items);
194 }
195 XPathValue::from_atomic(result.typed_value)
196 }
197 Err(_) => XPathValue::empty(),
198 }
199}
200
201pub(crate) fn evaluate_complex_type_assertions(
210 doc: &BufferDocument<'_>,
211 element_ref: u32,
212 ct_key: ComplexTypeKey,
213 schema_set: &SchemaSet,
214) -> Vec<ValidationError> {
215 let assertions = collect_inherited_assertions(ct_key, &schema_set.arenas);
216 let mut errors = Vec::new();
217
218 let dollar_value = compute_dollar_value(doc, element_ref, ct_key, schema_set);
222
223 for (assertion, owner_key) in assertions {
224 if assertion.test.is_empty() {
225 continue;
226 }
227
228 let ctx = XPathContext::new(&schema_set.name_table)
230 .with_namespaces(assertion.ns_snapshot.clone())
231 .with_schema_set(schema_set);
232
233 let ctx = if let Some(default_ns) =
235 resolve_ct_assertion_default_ns(assertion, owner_key, schema_set)
236 {
237 ctx.with_default_element_ns(default_ns)
238 } else {
239 ctx
240 };
241
242 let expr = match XPathExpr::compile_with_vars(&assertion.test, &ctx, &["value"]) {
245 Ok(e) => e,
246 Err(e) => {
247 errors.push(errors::error(
248 "cvc-assertion",
249 format!(
250 "Failed to compile assertion test '{}': {}",
251 assertion.test, e
252 ),
253 None,
254 ));
255 continue;
256 }
257 };
258
259 let nav = BufferDocNavigator::new_assertion(doc, element_ref);
260 let value_for_eval = dollar_value.clone();
261
262 let result = match expr
263 .evaluator(&ctx)
264 .run_with_node_and_setup(Some(nav), |eval| {
265 eval.set_variable_by_name("value", value_for_eval)
266 .expect("$value declared via compile_with_vars");
267 }) {
268 Ok(r) => r,
269 Err(e) => {
270 errors.push(errors::error(
271 "cvc-assertion",
272 format!(
273 "Failed to evaluate assertion test '{}': {}",
274 assertion.test, e
275 ),
276 None,
277 ));
278 continue;
279 }
280 };
281
282 match effective_boolean_value(&result) {
284 Ok(true) => { }
285 Ok(false) => {
286 errors.push(errors::error(
287 "cvc-assertion",
288 format!("Assertion '{}' failed", assertion.test),
289 None,
290 ));
291 }
292 Err(e) => {
293 errors.push(errors::error(
294 "cvc-assertion",
295 format!(
296 "Failed to compute boolean value for assertion '{}': {}",
297 assertion.test, e
298 ),
299 None,
300 ));
301 }
302 }
303 }
304
305 errors
306}
307
308#[cfg(test)]
313mod tests {
314 use super::*;
315 use crate::pipeline::load_and_process_schema;
316
317 fn load_schema(xsd: &str) -> SchemaSet {
318 let mut schema_set = SchemaSet::xsd11();
319 load_and_process_schema(xsd.as_bytes(), "test.xsd", &mut schema_set, None)
320 .expect("failed to load schema");
321 schema_set
322 }
323
324 fn find_ct_key(schema_set: &SchemaSet, name: &str) -> ComplexTypeKey {
326 let name_id = schema_set.name_table.add(name);
327 for (key, ct) in &schema_set.arenas.complex_types {
328 if ct.name == Some(name_id) {
329 return key;
330 }
331 }
332 panic!("Complex type '{}' not found", name);
333 }
334
335 #[test]
336 fn test_has_inherited_assertions_none() {
337 let schema_set = load_schema(
338 r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
339 <xs:complexType name="plain">
340 <xs:sequence>
341 <xs:element name="x" type="xs:string"/>
342 </xs:sequence>
343 </xs:complexType>
344 </xs:schema>"#,
345 );
346 let key = find_ct_key(&schema_set, "plain");
347 assert!(!has_inherited_assertions(key, &schema_set.arenas));
348 }
349
350 #[test]
351 fn test_has_inherited_assertions_own() {
352 let schema_set = load_schema(
354 r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
355 <xs:complexType name="withAssert">
356 <xs:attribute name="val" type="xs:integer"/>
357 <xs:assert test="@val >= 0"/>
358 </xs:complexType>
359 </xs:schema>"#,
360 );
361 let key = find_ct_key(&schema_set, "withAssert");
362 assert!(has_inherited_assertions(key, &schema_set.arenas));
363 }
364
365 #[test]
366 fn test_has_inherited_assertions_from_base() {
367 let schema_set = load_schema(
368 r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
369 <xs:complexType name="base">
370 <xs:attribute name="val" type="xs:integer"/>
371 <xs:assert test="@val >= 0"/>
372 </xs:complexType>
373 <xs:complexType name="derived">
374 <xs:complexContent>
375 <xs:restriction base="base">
376 <xs:attribute name="val" type="xs:integer"/>
377 </xs:restriction>
378 </xs:complexContent>
379 </xs:complexType>
380 </xs:schema>"#,
381 );
382 let key = find_ct_key(&schema_set, "derived");
383 assert!(has_inherited_assertions(key, &schema_set.arenas));
384 }
385
386 #[test]
387 fn test_collect_inherited_assertions_ordering() {
388 let schema_set = load_schema(
389 r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
390 <xs:complexType name="base">
391 <xs:attribute name="val" type="xs:integer"/>
392 <xs:assert test="@val >= 0"/>
393 </xs:complexType>
394 <xs:complexType name="derived">
395 <xs:complexContent>
396 <xs:restriction base="base">
397 <xs:attribute name="val" type="xs:integer"/>
398 <xs:assert test="@val < 100"/>
399 </xs:restriction>
400 </xs:complexContent>
401 </xs:complexType>
402 </xs:schema>"#,
403 );
404 let derived_key = find_ct_key(&schema_set, "derived");
405 let base_key = find_ct_key(&schema_set, "base");
406 let assertions = collect_inherited_assertions(derived_key, &schema_set.arenas);
407
408 assert_eq!(assertions.len(), 2);
410 assert_eq!(
411 assertions[0].1, base_key,
412 "first assertion should be from base"
413 );
414 assert_eq!(
415 assertions[1].1, derived_key,
416 "second assertion should be from derived"
417 );
418 assert!(assertions[0].0.test.contains(">= 0"));
419 assert!(assertions[1].0.test.contains("< 100"));
420 }
421
422 #[test]
423 fn test_collect_inherited_assertions_no_assertions() {
424 let schema_set = load_schema(
425 r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
426 <xs:complexType name="plain">
427 <xs:sequence>
428 <xs:element name="x" type="xs:string"/>
429 </xs:sequence>
430 </xs:complexType>
431 </xs:schema>"#,
432 );
433 let key = find_ct_key(&schema_set, "plain");
434 let assertions = collect_inherited_assertions(key, &schema_set.arenas);
435 assert!(assertions.is_empty());
436 }
437}