use crate::ids::{SimpleTypeKey, TypeKey};
use crate::parser::frames::{ComplexContentResult, DerivationMethod, SimpleTypeVariety};
use crate::schema::SchemaSet;
use crate::types::facets::{normalize_whitespace, FacetSet, WhitespaceMode};
use crate::types::validators::VALIDATOR_REGISTRY;
use crate::types::value::{XmlValue, XmlValueKind};
use crate::types::XmlTypeCode;
use super::errors::{self, ValidationError};
#[derive(Debug)]
pub struct SimpleTypeResult {
pub typed_value: XmlValue,
pub member_type: Option<TypeKey>,
pub normalized_value: Option<String>,
}
pub fn validate_simple_type(
value: &str,
type_key: TypeKey,
schema_set: &SchemaSet,
) -> Result<SimpleTypeResult, ValidationError> {
validate_simple_type_inner(value, type_key, schema_set)
}
pub fn fixed_values_equal(
a: &str,
b: &str,
type_key: Option<TypeKey>,
schema_set: &SchemaSet,
) -> bool {
if a == b {
return true;
}
let Some(tk) = type_key else {
return false;
};
let Ok(a_result) = validate_simple_type(a, tk, schema_set) else {
return false;
};
let Ok(b_result) = validate_simple_type(b, tk, schema_set) else {
return false;
};
a_result.typed_value == b_result.typed_value
}
pub fn fixed_matches_typed(
instance_raw: &str,
instance_typed: &XmlValue,
fixed: &str,
type_key: Option<TypeKey>,
schema_set: &SchemaSet,
) -> bool {
if instance_raw == fixed {
return true;
}
let Some(tk) = type_key else {
return false;
};
let Ok(fixed_result) = validate_simple_type(fixed, tk, schema_set) else {
return false;
};
*instance_typed == fixed_result.typed_value
}
fn validate_simple_type_inner(
value: &str,
type_key: TypeKey,
schema_set: &SchemaSet,
) -> Result<SimpleTypeResult, ValidationError> {
match type_key {
TypeKey::Simple(sk) => {
let st_data = match schema_set.arenas.simple_types.get(sk) {
Some(d) => d,
None => {
return Err(errors::error(
"cvc-simple-type",
"Simple type definition not found",
None,
));
}
};
match st_data.variety {
SimpleTypeVariety::Atomic => validate_atomic_type(value, sk, schema_set),
SimpleTypeVariety::List => validate_list_type(value, sk, schema_set),
SimpleTypeVariety::Union => validate_union_type(value, sk, schema_set),
}
}
TypeKey::Complex(ck) => {
let ct_data = match schema_set.arenas.complex_types.get(ck) {
Some(d) => d,
None => {
return Err(errors::error(
"cvc-simple-type",
"Complex type definition not found",
None,
));
}
};
let result = if let Some(inline_st) = ct_data.resolved_simple_content_type {
validate_simple_type_inner(value, inline_st, schema_set)?
} else if let Some(base_key) = ct_data.resolved_base_type {
validate_simple_type_inner(value, base_key, schema_set)?
} else {
SimpleTypeResult {
typed_value: XmlValue::untyped(value),
member_type: None,
normalized_value: None,
}
};
if let ComplexContentResult::Simple(sc) = &ct_data.content {
if sc.derivation == DerivationMethod::Restriction && !sc.facets.is_empty() {
let lex = result.normalized_value.as_deref().unwrap_or(value);
sc.facets.validate_string(lex).map_err(|e| {
let code = errors::facet_constraint_code(&e);
errors::from_facet_error(code, e, None)
})?;
}
}
Ok(result)
}
}
}
fn resolve_type_code(sk: SimpleTypeKey, schema_set: &SchemaSet) -> Option<XmlTypeCode> {
let mut current = sk;
for _ in 0..100 {
if let Some(code) = schema_set.get_type_code(current) {
return Some(code);
}
let st_data = schema_set.arenas.simple_types.get(current)?;
match st_data.resolved_base_type {
Some(TypeKey::Simple(base)) => current = base,
_ => return None,
}
}
None
}
fn has_enumeration_in_chain(sk: SimpleTypeKey, schema_set: &SchemaSet) -> bool {
let mut current = sk;
for _ in 0..100 {
let Some(st_data) = schema_set.arenas.simple_types.get(current) else {
return false;
};
if st_data.facets.enumeration.is_some() {
return true;
}
match st_data.resolved_base_type {
Some(TypeKey::Simple(base)) => current = base,
_ => return false,
}
}
false
}
fn collect_facets(sk: SimpleTypeKey, schema_set: &SchemaSet) -> FacetSet {
let st_data = match schema_set.arenas.simple_types.get(sk) {
Some(d) => d,
None => return FacetSet::new(),
};
let mut facets = st_data.facets.clone();
let mut current_base = st_data.resolved_base_type;
for _ in 0..100 {
match current_base {
Some(TypeKey::Simple(base_sk)) => {
if let Some(base_data) = schema_set.arenas.simple_types.get(base_sk) {
facets.inherit_from(&base_data.facets);
current_base = base_data.resolved_base_type;
} else {
break;
}
}
_ => break,
}
}
facets
}
fn validate_atomic_type(
value: &str,
sk: SimpleTypeKey,
schema_set: &SchemaSet,
) -> Result<SimpleTypeResult, ValidationError> {
let type_code = resolve_type_code(sk, schema_set);
match type_code {
Some(XmlTypeCode::AnySimpleType) | Some(XmlTypeCode::AnyAtomicType) | None => {
return Ok(SimpleTypeResult {
typed_value: XmlValue::untyped(value),
member_type: None,
normalized_value: None,
});
}
_ => {}
}
let type_code = type_code.unwrap();
let validator = match VALIDATOR_REGISTRY.get_by_code(type_code) {
Some(v) => v,
None => {
return Err(errors::error(
"cvc-datatype-valid",
format!("No validator for type code {:?}", type_code),
None,
));
}
};
let facets = collect_facets(sk, schema_set);
if type_code == XmlTypeCode::Notation && !has_enumeration_in_chain(sk, schema_set) {
return Err(errors::error(
"cvc-datatype-valid",
"xs:NOTATION cannot be used directly for instance validation; \
only datatypes derived from NOTATION by restriction with an \
enumeration facet are permitted (§3.2)",
None,
));
}
let effective_ws = facets
.whitespace
.as_ref()
.map(|w| w.value)
.unwrap_or_else(|| validator.whitespace());
let normalized = normalize_whitespace(value, effective_ws);
let typed_value = if facets.is_empty() {
validator.validate(value)
} else {
validator.validate_with_facets(value, &facets)
};
match typed_value {
Ok(mut val) => {
if val.schema_type.is_none() {
val.schema_type = Some(sk);
}
if schema_set.is_xsd10() {
check_xsd10_year_zero(&val, value)?;
check_xsd10_plus_inf(&val, value)?;
}
#[cfg(feature = "xsd11")]
evaluate_assertion_facets(&val, &facets, schema_set, Some(sk))?;
Ok(SimpleTypeResult {
typed_value: val,
member_type: None,
normalized_value: Some(normalized),
})
}
Err(type_err) => {
let code = errors::value_error_constraint_code(&type_err);
Err(errors::from_value_error(code, type_err, None))
}
}
}
fn check_xsd10_plus_inf(val: &XmlValue, raw: &str) -> Result<(), ValidationError> {
use crate::types::value::XmlAtomicValue;
let is_float_or_double = matches!(
val.value,
XmlValueKind::Atomic(XmlAtomicValue::Float(_)) | XmlValueKind::Atomic(XmlAtomicValue::Double(_))
);
if !is_float_or_double {
return Ok(());
}
let collapsed =
crate::types::facets::normalize_whitespace(raw, crate::types::facets::WhitespaceMode::Collapse);
if collapsed == "+INF" {
Err(errors::error(
"cvc-datatype-valid",
format!("'+INF' is not a valid XSD 1.0 float/double lexical form (value '{}')", raw),
None,
))
} else {
Ok(())
}
}
fn check_xsd10_year_zero(val: &XmlValue, raw: &str) -> Result<(), ValidationError> {
use crate::types::value::XmlAtomicValue;
let year_zero = match &val.value {
XmlValueKind::Atomic(XmlAtomicValue::DateTime(v)) => v.year == 0,
XmlValueKind::Atomic(XmlAtomicValue::Date(v)) => v.year == 0,
XmlValueKind::Atomic(XmlAtomicValue::GYear(v)) => v.year == 0,
XmlValueKind::Atomic(XmlAtomicValue::GYearMonth(v)) => v.year == 0,
_ => false,
};
if year_zero {
Err(errors::error(
"cvc-datatype-valid",
format!(
"Year 0000 is not a valid XSD 1.0 lexical form (value '{}'); use '-0001' for 1 BCE",
raw
),
None,
))
} else {
Ok(())
}
}
fn validate_list_type(
value: &str,
sk: SimpleTypeKey,
schema_set: &SchemaSet,
) -> Result<SimpleTypeResult, ValidationError> {
let st_data = match schema_set.arenas.simple_types.get(sk) {
Some(d) => d,
None => {
return Err(errors::error(
"cvc-simple-type",
"List type definition not found",
None,
));
}
};
let item_type_key = match st_data.resolved_item_type {
Some(tk) => tk,
None => {
if let Some(deferred) = &st_data.deferred_item_type_error {
return Err(errors::error(
"src-resolve",
deferred.message.clone(),
None,
));
}
return Ok(SimpleTypeResult {
typed_value: XmlValue::untyped(value),
member_type: None,
normalized_value: None,
});
}
};
let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
let items_str: Vec<&str> = if normalized.is_empty() {
Vec::new()
} else {
normalized.split(' ').collect()
};
let mut items = Vec::with_capacity(items_str.len());
let mut item_type_code = XmlTypeCode::UntypedAtomic;
for item_str in &items_str {
let result = validate_simple_type_inner(item_str, item_type_key, schema_set)?;
item_type_code = result.typed_value.type_code;
match result.typed_value.value {
XmlValueKind::Atomic(atom) => items.push(atom),
XmlValueKind::UntypedAtomic(s) => {
items.push(crate::types::value::XmlAtomicValue::String(s));
}
_ => {
items.push(crate::types::value::XmlAtomicValue::String(
result.typed_value.to_string_value(),
));
}
}
}
let facets = collect_facets(sk, schema_set);
if !facets.is_empty() {
facets
.validate_list_length(items_str.len() as u64)
.map_err(|e| {
let code = errors::facet_constraint_code(&e);
errors::from_facet_error(code, e, None)
})?;
facets
.validate_string_patterns_enums(&normalized)
.map_err(|e| {
let code = errors::facet_constraint_code(&e);
errors::from_facet_error(code, e, None)
})?;
}
let list_type_code = resolve_type_code(sk, schema_set)
.filter(|code| code.is_list())
.unwrap_or(item_type_code);
let typed_value = XmlValue::with_schema_type(
list_type_code,
sk,
XmlValueKind::List {
item_type: item_type_code,
items,
},
);
#[cfg(feature = "xsd11")]
{
let item_sk = item_type_key.as_simple();
evaluate_assertion_facets(&typed_value, &facets, schema_set, item_sk)?;
}
Ok(SimpleTypeResult {
typed_value,
member_type: None,
normalized_value: Some(normalized),
})
}
fn validate_union_type(
value: &str,
sk: SimpleTypeKey,
schema_set: &SchemaSet,
) -> Result<SimpleTypeResult, ValidationError> {
let st_data = match schema_set.arenas.simple_types.get(sk) {
Some(d) => d,
None => {
return Err(errors::error(
"cvc-simple-type",
"Union type definition not found",
None,
));
}
};
for &member_key in &st_data.resolved_member_types {
if let Ok(result) = validate_simple_type_inner(value, member_key, schema_set) {
let facets = collect_facets(sk, schema_set);
if !facets.is_empty() {
let check_value = result.typed_value.to_string_value();
let lex_value = normalize_whitespace(value, WhitespaceMode::Collapse);
facets.validate_patterns_only(&lex_value).map_err(|e| {
let code = errors::facet_constraint_code(&e);
errors::from_facet_error(code, e, None)
})?;
let type_code = result.typed_value.type_code;
facets
.validate_enum_value_space(
|s| {
VALIDATOR_REGISTRY
.validate(type_code, s)
.map(|v| v.to_string_value() == check_value)
.unwrap_or(false)
},
&check_value,
)
.map_err(|e| {
let code = errors::facet_constraint_code(&e);
errors::from_facet_error(code, e, None)
})?;
}
let mut inner = result.typed_value;
if inner.schema_type.is_none() {
inner.schema_type = member_key.as_simple();
}
#[cfg(feature = "xsd11")]
{
let item_sk = member_key.as_simple().and_then(|sk| {
crate::types::sequence::resolve_list_item_schema_type(sk, schema_set)
});
evaluate_assertion_facets(&inner, &facets, schema_set, item_sk)?;
}
return Ok(SimpleTypeResult {
normalized_value: result.normalized_value,
typed_value: XmlValue::with_schema_type(
inner.type_code,
sk,
XmlValueKind::Union(Box::new(inner)),
),
member_type: Some(member_key),
});
}
}
Err(errors::error(
"cvc-simple-type",
format!(
"Value '{}' does not match any member type of the union",
value
),
None,
))
}
#[cfg(feature = "xsd11")]
fn resolve_assertion_default_ns(
raw: Option<&str>,
source: Option<&crate::parser::location::SourceRef>,
ns_snapshot: &crate::namespace::context::NamespaceContextSnapshot,
schema_set: &SchemaSet,
) -> Option<crate::ids::NameId> {
let doc = source.and_then(|s| schema_set.documents.get(s.doc_id as usize));
let effective = if let Some(raw) = raw {
Some(raw.to_string())
} else {
doc.and_then(|d| d.xpath_default_namespace)
.map(|id| schema_set.name_table.resolve(id))
};
match effective.as_deref() {
Some("##defaultNamespace") => ns_snapshot.default_ns,
Some("##targetNamespace") => doc.and_then(|d| d.target_namespace),
Some("##local") | None => None,
Some(uri) => Some(schema_set.name_table.add(uri)),
}
}
#[cfg(feature = "xsd11")]
fn evaluate_assertion_facets(
typed_value: &XmlValue,
facets: &FacetSet,
schema_set: &SchemaSet,
item_schema_type: Option<SimpleTypeKey>,
) -> Result<(), ValidationError> {
use crate::xpath::api::XPathExpr;
use crate::xpath::functions::effective_boolean_value;
use crate::xpath::{RoXmlNavigator, XPathContext};
if facets.assertions.is_empty() {
return Ok(());
}
for assertion in &facets.assertions {
if assertion.test.is_empty() {
continue;
}
let location = assertion
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
let ctx = XPathContext::new(&schema_set.name_table)
.with_namespaces(assertion.ns_snapshot.clone())
.with_schema_set(schema_set);
let ctx = if let Some(default_ns) = resolve_assertion_default_ns(
assertion.xpath_default_namespace.as_deref(),
assertion.source.as_ref(),
&assertion.ns_snapshot,
schema_set,
) {
ctx.with_default_element_ns(default_ns)
} else {
ctx
};
let expr =
XPathExpr::compile_with_vars(&assertion.test, &ctx, &["value"]).map_err(|e| {
errors::error(
"cvc-assertion",
format!(
"Failed to compile assertion test '{}': {}",
assertion.test, e
),
location.clone(),
)
})?;
let xpath_value = typed_value.to_xpath_value::<RoXmlNavigator<'static>>(item_schema_type);
let result = expr
.evaluator(&ctx)
.run_with::<RoXmlNavigator<'static>, _>(|eval| {
eval.set_variable_by_name("value", xpath_value).unwrap();
})
.map_err(|e| {
errors::error(
"cvc-assertion",
format!(
"Failed to evaluate assertion test '{}': {}",
assertion.test, e
),
location.clone(),
)
})?;
let ebv = effective_boolean_value(&result).map_err(|e| {
errors::error(
"cvc-assertion",
format!(
"Failed to compute boolean value for assertion '{}': {}",
assertion.test, e
),
location.clone(),
)
})?;
if !ebv {
return Err(errors::error(
"cvc-assertion",
format!(
"Assertion '{}' failed for value '{}'",
assertion.test,
typed_value.to_string_value()
),
location,
));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::load_and_process_schema;
fn load_schema(xsd: &str) -> SchemaSet {
let mut schema_set = SchemaSet::new();
load_and_process_schema(xsd.as_bytes(), "test.xsd", &mut schema_set, None)
.expect("failed to load schema");
schema_set
}
fn element_type(schema_set: &SchemaSet, local_name: &str) -> TypeKey {
let name_id = schema_set.name_table.add(local_name);
let elem_key = schema_set
.lookup_element(None, name_id)
.expect("element not found");
schema_set.arenas.elements[elem_key]
.resolved_type
.expect("element has no resolved type")
}
#[test]
fn test_builtin_string_accepts_anything() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:string"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("hello world", tk, &schema).unwrap();
assert!(result.member_type.is_none());
assert_eq!(result.typed_value.type_code, XmlTypeCode::String);
}
#[test]
fn test_builtin_integer_valid() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:integer"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("42", tk, &schema).unwrap();
assert_eq!(result.typed_value.type_code, XmlTypeCode::Integer);
}
#[test]
fn test_builtin_integer_invalid() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:integer"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let err = validate_simple_type("abc", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-datatype-valid");
}
#[test]
fn test_builtin_boolean_valid() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:boolean"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
for v in &["true", "false", "1", "0"] {
assert!(
validate_simple_type(v, tk, &schema).is_ok(),
"failed for '{}'",
v
);
}
}
#[test]
fn test_builtin_boolean_invalid() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:boolean"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("yes", tk, &schema).is_err());
}
#[test]
fn test_user_defined_restriction_min_max() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="score">
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="100"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="score"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("50", tk, &schema).is_ok());
assert!(validate_simple_type("0", tk, &schema).is_ok());
assert!(validate_simple_type("100", tk, &schema).is_ok());
let err = validate_simple_type("101", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-maxInclusive-valid");
let err = validate_simple_type("-1", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-minInclusive-valid");
}
#[test]
fn test_enumeration_facet() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="color">
<xs:restriction base="xs:string">
<xs:enumeration value="red"/>
<xs:enumeration value="green"/>
<xs:enumeration value="blue"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="color"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("red", tk, &schema).is_ok());
assert!(validate_simple_type("green", tk, &schema).is_ok());
let err = validate_simple_type("yellow", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-enumeration-valid");
}
#[test]
fn test_pattern_facet() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="zipCode">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{5}"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="zipCode"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("12345", tk, &schema).is_ok());
let err = validate_simple_type("1234", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-pattern-valid");
}
#[test]
fn test_empty_string_against_integer() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:integer"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("", tk, &schema).is_err());
}
#[test]
fn test_empty_string_against_string() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:string"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("", tk, &schema).is_ok());
}
#[test]
fn test_list_type() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="intList">
<xs:list itemType="xs:integer"/>
</xs:simpleType>
<xs:element name="e" type="intList"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("1 2 3", tk, &schema).unwrap();
assert!(result.typed_value.is_list());
assert!(validate_simple_type("1 abc 3", tk, &schema).is_err());
}
#[test]
fn test_union_type() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="intOrString">
<xs:union memberTypes="xs:integer xs:string"/>
</xs:simpleType>
<xs:element name="e" type="intOrString"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("42", tk, &schema).unwrap();
assert!(result.member_type.is_some());
assert_eq!(result.typed_value.type_code, XmlTypeCode::Integer);
let result = validate_simple_type("hello", tk, &schema).unwrap();
assert!(result.member_type.is_some());
assert_eq!(result.typed_value.type_code, XmlTypeCode::String);
}
}
#[cfg(all(test, feature = "xsd11"))]
mod xsd11_tests {
use super::*;
use crate::navigator::RoXmlNavigator;
use crate::pipeline::load_and_process_schema;
use crate::types::sequence::resolve_list_item_schema_type;
use crate::xpath::XPathValue;
fn load_schema(xsd: &str) -> SchemaSet {
let mut schema_set = SchemaSet::xsd11();
load_and_process_schema(xsd.as_bytes(), "test.xsd", &mut schema_set, None)
.expect("failed to load schema");
schema_set
}
fn element_type(schema_set: &SchemaSet, local_name: &str) -> TypeKey {
let name_id = schema_set.name_table.add(local_name);
let elem_key = schema_set
.lookup_element(None, name_id)
.expect("element not found");
schema_set.arenas.elements[elem_key]
.resolved_type
.expect("element has no resolved type")
}
#[test]
fn test_atomic_schema_type_set() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="score">
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="100"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="score"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("50", tk, &schema).unwrap();
assert!(
result.typed_value.schema_type.is_some(),
"atomic value should have schema_type set"
);
}
#[test]
fn test_list_schema_type_set() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="intList">
<xs:list itemType="xs:integer"/>
</xs:simpleType>
<xs:element name="e" type="intList"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("1 2 3", tk, &schema).unwrap();
assert!(
result.typed_value.schema_type.is_some(),
"list value should have schema_type set"
);
}
#[test]
fn test_union_schema_type_set() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="intOrString">
<xs:union memberTypes="xs:integer xs:string"/>
</xs:simpleType>
<xs:element name="e" type="intOrString"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("42", tk, &schema).unwrap();
assert!(
result.typed_value.schema_type.is_some(),
"union value should have schema_type set"
);
if let XmlValueKind::Union(ref inner) = result.typed_value.value {
assert!(
inner.schema_type.is_some(),
"inner union value should have schema_type set from matched member"
);
} else {
panic!("expected Union variant");
}
}
#[test]
fn test_list_to_xpath_value() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="intList">
<xs:list itemType="xs:integer"/>
</xs:simpleType>
<xs:element name="e" type="intList"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("1 2 3", tk, &schema).unwrap();
let list_sk = tk.as_simple().expect("should be simple type");
let item_sk = resolve_list_item_schema_type(list_sk, &schema);
let xpath_val: XPathValue<RoXmlNavigator<'static>> =
result.typed_value.to_xpath_value(item_sk);
let items = xpath_val.into_vec();
assert_eq!(items.len(), 3, "list should produce 3 XPath items");
for item in &items {
let val = item.as_atomic().expect("should be atomic");
assert!(
val.schema_type.is_some(),
"each list item should have schema_type"
);
}
}
#[test]
fn test_union_to_xpath_value() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="intOrString">
<xs:union memberTypes="xs:integer xs:string"/>
</xs:simpleType>
<xs:element name="e" type="intOrString"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("42", tk, &schema).unwrap();
let xpath_val: XPathValue<RoXmlNavigator<'static>> =
result.typed_value.to_xpath_value(None);
let items = xpath_val.into_vec();
assert_eq!(items.len(), 1, "union should unwrap to single item");
assert!(items[0].is_atomic());
}
#[test]
fn test_atomic_to_xpath_value() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="e" type="xs:integer"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let result = validate_simple_type("42", tk, &schema).unwrap();
let xpath_val: XPathValue<RoXmlNavigator<'static>> =
result.typed_value.to_xpath_value(None);
let items = xpath_val.into_vec();
assert_eq!(items.len(), 1, "atomic should produce single item");
assert!(items[0].is_atomic());
assert_eq!(
items[0].as_atomic().unwrap().type_code,
XmlTypeCode::Integer
);
}
#[test]
fn test_assertion_even_integer() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="evenInt">
<xs:restriction base="xs:integer">
<xs:assertion test="$value mod 2 = 0"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="evenInt"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("4", tk, &schema).is_ok());
assert!(validate_simple_type("0", tk, &schema).is_ok());
assert!(validate_simple_type("-2", tk, &schema).is_ok());
let err = validate_simple_type("3", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
let err = validate_simple_type("7", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
#[test]
fn test_assertion_positive_value() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="posInt">
<xs:restriction base="xs:integer">
<xs:assertion test="$value gt 0"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="posInt"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("1", tk, &schema).is_ok());
assert!(validate_simple_type("100", tk, &schema).is_ok());
let err = validate_simple_type("0", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
let err = validate_simple_type("-5", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
#[test]
fn test_assertion_string_length() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="shortStr">
<xs:restriction base="xs:string">
<xs:assertion test="string-length($value) le 5"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="shortStr"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("hello", tk, &schema).is_ok());
assert!(validate_simple_type("", tk, &schema).is_ok());
assert!(validate_simple_type("abcde", tk, &schema).is_ok());
let err = validate_simple_type("toolong", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
#[test]
fn test_assertion_inherited() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="positiveInt">
<xs:restriction base="xs:integer">
<xs:assertion test="$value gt 0"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="smallPositiveInt">
<xs:restriction base="positiveInt">
<xs:maxInclusive value="10"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="smallPositiveInt"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("5", tk, &schema).is_ok());
assert!(validate_simple_type("10", tk, &schema).is_ok());
assert!(validate_simple_type("11", tk, &schema).is_err());
let err = validate_simple_type("0", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
#[test]
fn test_assertion_compile_error() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="badAssert">
<xs:restriction base="xs:integer">
<xs:assertion test="$value @@@ invalid"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="badAssert"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
let err = validate_simple_type("42", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
assert!(err.message.contains("compile"));
}
#[test]
fn test_assertion_with_other_facets() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="evenScore">
<xs:restriction base="xs:integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="100"/>
<xs:assertion test="$value mod 2 = 0"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="evenScore"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("50", tk, &schema).is_ok());
assert!(validate_simple_type("0", tk, &schema).is_ok());
assert!(validate_simple_type("100", tk, &schema).is_ok());
let err = validate_simple_type("51", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
assert!(validate_simple_type("102", tk, &schema).is_err());
assert!(validate_simple_type("-2", tk, &schema).is_err());
}
#[test]
fn test_assertion_multiple() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="constrained">
<xs:restriction base="xs:integer">
<xs:assertion test="$value gt 0"/>
<xs:assertion test="$value mod 2 = 0"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="constrained"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("2", tk, &schema).is_ok());
assert!(validate_simple_type("4", tk, &schema).is_ok());
let err = validate_simple_type("3", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
let err = validate_simple_type("0", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
#[test]
fn test_assertion_boolean_value() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="mustBeTrue">
<xs:restriction base="xs:boolean">
<xs:assertion test="$value"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="mustBeTrue"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("true", tk, &schema).is_ok());
assert!(validate_simple_type("1", tk, &schema).is_ok());
let err = validate_simple_type("false", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
#[test]
fn test_assertion_union_with_list_member_item_typing() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="intList">
<xs:list itemType="xs:integer"/>
</xs:simpleType>
<xs:simpleType name="unionWithList">
<xs:union memberTypes="intList">
<xs:simpleType>
<xs:restriction base="xs:string"/>
</xs:simpleType>
</xs:union>
</xs:simpleType>
<xs:simpleType name="checkedUnion">
<xs:restriction base="unionWithList">
<xs:assertion test="every $i in $value satisfies $i instance of xs:integer"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="checkedUnion"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("1 2 3", tk, &schema).is_ok());
}
#[test]
fn test_assertion_xpath_default_ns_schema_level_fallback() {
let schema = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpathDefaultNamespace="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="checkedInt">
<xs:restriction base="xs:integer">
<xs:assertion test="$value instance of integer"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="checkedInt"/>
</xs:schema>"#,
);
let tk = element_type(&schema, "e");
assert!(validate_simple_type("42", tk, &schema).is_ok());
}
#[test]
fn test_assertion_xpath_default_ns_assertion_level_overrides_schema() {
let schema = load_schema(
r###"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpathDefaultNamespace="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="checkedInt">
<xs:restriction base="xs:integer">
<xs:assertion test="$value instance of integer"
xpathDefaultNamespace="##local"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="checkedInt"/>
</xs:schema>"###,
);
let tk = element_type(&schema, "e");
let err = validate_simple_type("42", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
#[test]
fn test_assertion_xpath_default_ns_target_namespace_token() {
let schema = load_schema(
r###"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.com/ns"
xmlns:tns="http://example.com/ns"
xpathDefaultNamespace="##targetNamespace">
<xs:simpleType name="checkedInt">
<xs:restriction base="xs:integer">
<xs:assertion test="$value instance of integer"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="e" type="tns:checkedInt"/>
</xs:schema>"###,
);
let ns_id = schema.name_table.add("http://example.com/ns");
let name_id = schema.name_table.add("e");
let elem_key = schema
.lookup_element(Some(ns_id), name_id)
.expect("element not found");
let tk = schema.arenas.elements[elem_key]
.resolved_type
.expect("element has no resolved type");
let err = validate_simple_type("42", tk, &schema).unwrap_err();
assert_eq!(err.constraint, "cvc-assertion");
}
}