use crate::error::{SchemaError, SchemaResult};
use crate::ids::*;
use crate::parser::frames::{ParticleResult, ParticleTerm};
use crate::schema::composition::{
record_provenance, redefined_action, ComponentIdentity, ComponentKey, ComponentKind,
};
use crate::schema::model::RedefineDirective;
use crate::schema::SchemaSet;
pub fn apply_redefine(
schema_set: &mut SchemaSet,
redefine: &RedefineDirective,
) -> SchemaResult<()> {
let target_doc_id = redefine.resolved_doc_id;
let redefining_doc_id = redefine.source.as_ref().map(|s| s.doc_id);
for simple_key in &redefine.simple_types {
apply_simple_type_redefine(schema_set, *simple_key, target_doc_id, redefining_doc_id)?;
}
for complex_key in &redefine.complex_types {
apply_complex_type_redefine(schema_set, *complex_key, target_doc_id, redefining_doc_id)?;
}
for group_key in &redefine.groups {
apply_model_group_redefine(schema_set, *group_key, target_doc_id, redefining_doc_id)?;
}
for attr_group_key in &redefine.attribute_groups {
apply_attribute_group_redefine(
schema_set,
*attr_group_key,
target_doc_id,
redefining_doc_id,
)?;
}
Ok(())
}
fn apply_simple_type_redefine(
schema_set: &mut SchemaSet,
new_key: SimpleTypeKey,
target_doc_id: Option<DocumentId>,
redefining_doc_id: Option<DocumentId>,
) -> SchemaResult<()> {
let new_type = schema_set
.arenas
.simple_types
.get(new_key)
.ok_or_else(|| SchemaError::internal("Redefine: new simple type not found"))?;
let name = new_type.name.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
"Redefined simple type must have a name",
None,
)
})?;
let namespace = new_type.target_namespace;
let original_key = match target_doc_id {
Some(id) => schema_set
.documents
.get(id as usize)
.and_then(|doc| doc.component_index.lookup_simple_type(namespace, name))
.map(TypeKey::Simple),
None => schema_set.lookup_type(namespace, name),
}
.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
format!(
"Original simple type '{}' not found for redefinition in {}",
schema_set.name_table.resolve(name),
target_doc_id
.and_then(|id| schema_set.documents.get(id as usize))
.map(|d| d.base_uri.as_str())
.unwrap_or("schema"),
),
None,
)
})?;
validate_self_derivation_simple(schema_set, new_key, name, namespace)?;
if let TypeKey::Simple(orig_key) = original_key {
if let Some(st) = schema_set.arenas.simple_types.get_mut(new_key) {
st.redefine_original = Some(orig_key);
}
}
let ns_table = schema_set.get_or_create_namespace(namespace);
ns_table.register_type(name, TypeKey::Simple(new_key));
if let Some(doc_id) = redefining_doc_id {
if let Some(doc) = schema_set.documents.get_mut(doc_id as usize) {
doc.component_index.insert(
ComponentIdentity {
kind: ComponentKind::SimpleType,
name,
namespace,
},
ComponentKey::Type(TypeKey::Simple(new_key)),
);
}
}
record_provenance(
&mut schema_set.effective_components,
ComponentKey::Type(TypeKey::Simple(new_key)),
ComponentKind::SimpleType,
namespace,
name,
redefining_doc_id,
redefined_action(
redefining_doc_id,
ComponentKind::SimpleType,
name,
namespace,
target_doc_id,
),
);
Ok(())
}
fn apply_complex_type_redefine(
schema_set: &mut SchemaSet,
new_key: ComplexTypeKey,
target_doc_id: Option<DocumentId>,
redefining_doc_id: Option<DocumentId>,
) -> SchemaResult<()> {
let new_type = schema_set
.arenas
.complex_types
.get(new_key)
.ok_or_else(|| SchemaError::internal("Redefine: new complex type not found"))?;
let name = new_type.name.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
"Redefined complex type must have a name",
None,
)
})?;
let namespace = new_type.target_namespace;
let original_key = match target_doc_id {
Some(id) => schema_set
.documents
.get(id as usize)
.and_then(|doc| doc.component_index.lookup_complex_type(namespace, name))
.map(TypeKey::Complex),
None => schema_set.lookup_type(namespace, name),
}
.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
format!(
"Original complex type '{}' not found for redefinition in {}",
schema_set.name_table.resolve(name),
target_doc_id
.and_then(|id| schema_set.documents.get(id as usize))
.map(|d| d.base_uri.as_str())
.unwrap_or("schema"),
),
None,
)
})?;
validate_self_derivation_complex(schema_set, new_key, name, namespace)?;
if let TypeKey::Complex(orig_key) = original_key {
if let Some(ct) = schema_set.arenas.complex_types.get_mut(new_key) {
ct.redefine_original = Some(orig_key);
}
}
let ns_table = schema_set.get_or_create_namespace(namespace);
ns_table.register_type(name, TypeKey::Complex(new_key));
if let Some(doc_id) = redefining_doc_id {
if let Some(doc) = schema_set.documents.get_mut(doc_id as usize) {
doc.component_index.insert(
ComponentIdentity {
kind: ComponentKind::ComplexType,
name,
namespace,
},
ComponentKey::Type(TypeKey::Complex(new_key)),
);
}
}
record_provenance(
&mut schema_set.effective_components,
ComponentKey::Type(TypeKey::Complex(new_key)),
ComponentKind::ComplexType,
namespace,
name,
redefining_doc_id,
redefined_action(
redefining_doc_id,
ComponentKind::ComplexType,
name,
namespace,
target_doc_id,
),
);
Ok(())
}
fn apply_model_group_redefine(
schema_set: &mut SchemaSet,
new_key: ModelGroupKey,
target_doc_id: Option<DocumentId>,
redefining_doc_id: Option<DocumentId>,
) -> SchemaResult<()> {
let new_group = schema_set
.arenas
.model_groups
.get(new_key)
.ok_or_else(|| SchemaError::internal("Redefine: new model group not found"))?;
let name = new_group.name.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
"Redefined model group must have a name",
None,
)
})?;
let namespace = new_group.target_namespace;
let original_key = match target_doc_id {
Some(id) => schema_set
.documents
.get(id as usize)
.and_then(|doc| doc.component_index.lookup_model_group(namespace, name)),
None => schema_set.lookup_model_group(namespace, name),
}
.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
format!(
"Original group '{}' not found for redefinition in {}",
schema_set.name_table.resolve(name),
target_doc_id
.and_then(|id| schema_set.documents.get(id as usize))
.map(|d| d.base_uri.as_str())
.unwrap_or("schema"),
),
None,
)
})?;
let has_self_ref = validate_self_reference_group(schema_set, new_key, name)?;
if let Some(group) = schema_set.arenas.model_groups.get_mut(new_key) {
group.redefine_original = Some(original_key);
group.redefine_requires_restriction_check = !has_self_ref;
}
let ns_table = schema_set.get_or_create_namespace(namespace);
ns_table.register_model_group(name, new_key);
if let Some(doc_id) = redefining_doc_id {
if let Some(doc) = schema_set.documents.get_mut(doc_id as usize) {
doc.component_index.insert(
ComponentIdentity {
kind: ComponentKind::ModelGroup,
name,
namespace,
},
ComponentKey::ModelGroup(new_key),
);
}
}
record_provenance(
&mut schema_set.effective_components,
ComponentKey::ModelGroup(new_key),
ComponentKind::ModelGroup,
namespace,
name,
redefining_doc_id,
redefined_action(
redefining_doc_id,
ComponentKind::ModelGroup,
name,
namespace,
target_doc_id,
),
);
Ok(())
}
fn apply_attribute_group_redefine(
schema_set: &mut SchemaSet,
new_key: AttributeGroupKey,
target_doc_id: Option<DocumentId>,
redefining_doc_id: Option<DocumentId>,
) -> SchemaResult<()> {
let new_group = schema_set
.arenas
.attribute_groups
.get(new_key)
.ok_or_else(|| SchemaError::internal("Redefine: new attribute group not found"))?;
let name = new_group.name.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
"Redefined attribute group must have a name",
None,
)
})?;
let namespace = new_group.target_namespace;
let original_key = match target_doc_id {
Some(id) => schema_set
.documents
.get(id as usize)
.and_then(|doc| doc.component_index.lookup_attribute_group(namespace, name)),
None => schema_set.lookup_attribute_group(namespace, name),
}
.ok_or_else(|| {
SchemaError::structural(
"src-redefine",
format!(
"Original attribute group '{}' not found for redefinition in {}",
schema_set.name_table.resolve(name),
target_doc_id
.and_then(|id| schema_set.documents.get(id as usize))
.map(|d| d.base_uri.as_str())
.unwrap_or("schema"),
),
None,
)
})?;
let has_self_ref = validate_self_reference_attribute_group(schema_set, new_key, name)?;
if let Some(group) = schema_set.arenas.attribute_groups.get_mut(new_key) {
group.redefine_original = Some(original_key);
group.redefine_requires_restriction_check = !has_self_ref;
}
let ns_table = schema_set.get_or_create_namespace(namespace);
ns_table.register_attribute_group(name, new_key);
if let Some(doc_id) = redefining_doc_id {
if let Some(doc) = schema_set.documents.get_mut(doc_id as usize) {
doc.component_index.insert(
ComponentIdentity {
kind: ComponentKind::AttributeGroup,
name,
namespace,
},
ComponentKey::AttributeGroup(new_key),
);
}
}
record_provenance(
&mut schema_set.effective_components,
ComponentKey::AttributeGroup(new_key),
ComponentKind::AttributeGroup,
namespace,
name,
redefining_doc_id,
redefined_action(
redefining_doc_id,
ComponentKind::AttributeGroup,
name,
namespace,
target_doc_id,
),
);
Ok(())
}
fn validate_self_derivation_simple(
schema_set: &SchemaSet,
type_key: SimpleTypeKey,
expected_name: NameId,
expected_namespace: Option<NameId>,
) -> SchemaResult<()> {
use crate::parser::frames::TypeRefResult;
let type_def = schema_set
.arenas
.simple_types
.get(type_key)
.ok_or_else(|| SchemaError::internal("Type not found"))?;
if let Some(TypeRefResult::QName(ref qname)) = type_def.base_type {
if qname.local_name != expected_name {
return Err(SchemaError::structural(
"src-redefine",
"Redefined simple type must derive from the original type (self-reference)",
type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
));
}
if let Some(ref_ns) = qname.namespace {
if Some(ref_ns) != expected_namespace {
return Err(SchemaError::structural(
"src-redefine",
"Redefined simple type base references a different namespace than the original",
type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
));
}
}
} else {
return Err(SchemaError::structural(
"src-redefine",
"Redefined simple type must have a base type reference",
type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
));
}
Ok(())
}
fn validate_self_derivation_complex(
schema_set: &SchemaSet,
type_key: ComplexTypeKey,
expected_name: NameId,
expected_namespace: Option<NameId>,
) -> SchemaResult<()> {
use crate::parser::frames::TypeRefResult;
let type_def = schema_set
.arenas
.complex_types
.get(type_key)
.ok_or_else(|| SchemaError::internal("Type not found"))?;
if let Some(TypeRefResult::QName(ref qname)) = type_def.base_type {
if qname.local_name != expected_name {
return Err(SchemaError::structural(
"src-redefine",
"Redefined complex type must derive from the original type (self-reference)",
type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
));
}
if let Some(ref_ns) = qname.namespace {
if Some(ref_ns) != expected_namespace {
return Err(SchemaError::structural(
"src-redefine",
"Redefined complex type base references a different namespace than the original",
type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
));
}
}
} else {
return Err(SchemaError::structural(
"src-redefine",
"Redefined complex type must have a base type reference",
type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
));
}
Ok(())
}
struct GroupSelfRefScan {
count: u32,
min_occurs: u32,
max_occurs: Option<u32>,
}
fn count_group_self_refs(
particles: &[ParticleResult],
expected_name: NameId,
scan: &mut GroupSelfRefScan,
) {
for particle in particles {
if let ParticleTerm::Group(ref grp) = particle.term {
match grp.ref_name {
Some(ref ref_name) => {
if ref_name.local_name == expected_name {
scan.count += 1;
scan.min_occurs = particle.min_occurs;
scan.max_occurs = particle.max_occurs;
}
}
None => {
debug_assert!(
grp.name.is_none(),
"named group definition unexpectedly nested in a particle list"
);
count_group_self_refs(&grp.particles, expected_name, scan);
}
}
}
}
}
fn validate_self_reference_group(
schema_set: &SchemaSet,
group_key: ModelGroupKey,
expected_name: NameId,
) -> SchemaResult<bool> {
let group = schema_set
.arenas
.model_groups
.get(group_key)
.ok_or_else(|| SchemaError::internal("Group not found"))?;
let mut scan = GroupSelfRefScan {
count: 0,
min_occurs: 0,
max_occurs: None,
};
count_group_self_refs(&group.particles, expected_name, &mut scan);
match scan.count {
0 => Ok(false),
1 => {
if scan.min_occurs != 1 || scan.max_occurs != Some(1) {
Err(SchemaError::structural(
"src-redefine",
format!(
"Self-referencing group particle must have \
minOccurs=1 and maxOccurs=1, but found \
minOccurs={} maxOccurs={}",
scan.min_occurs,
scan.max_occurs
.map(|v| v.to_string())
.unwrap_or_else(|| "unbounded".to_string()),
),
group
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
))
} else {
Ok(true)
}
}
_ => Err(SchemaError::structural(
"src-redefine",
format!(
"Redefined group must contain at most one \
self-reference (found {})",
scan.count,
),
group
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
)),
}
}
fn validate_self_reference_attribute_group(
schema_set: &SchemaSet,
group_key: AttributeGroupKey,
expected_name: NameId,
) -> SchemaResult<bool> {
let group = schema_set
.arenas
.attribute_groups
.get(group_key)
.ok_or_else(|| SchemaError::internal("Attribute group not found"))?;
let mut self_refs = 0u32;
for attr_group_ref in &group.attribute_groups {
if attr_group_ref.local_name == expected_name {
self_refs += 1;
}
}
match self_refs {
0 => Ok(false),
1 => Ok(true),
_ => Err(SchemaError::structural(
"src-redefine",
format!(
"Redefined attribute group must contain at most one \
self-reference (found {})",
self_refs,
),
group
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s)),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arenas::{AttributeGroupData, ModelGroupData};
use crate::parser::frames::{
Compositor, ElementFrameResult, ModelGroupDefResult, ParticleResult, ParticleTerm, QNameRef,
};
use crate::schema::composition::ComponentKind;
use crate::schema::model::SchemaDocument;
fn setup_model_group_redefine() -> (SchemaSet, ModelGroupKey, ModelGroupKey) {
let mut schema_set = SchemaSet::new();
let base_doc_id = schema_set.documents.len() as u32;
let base_doc = SchemaDocument::new(base_doc_id, "base.xsd".to_string());
schema_set.documents.push(base_doc);
let group_name = schema_set.name_table.add("personGroup");
let name_elem = schema_set.name_table.add("name");
let original_data = ModelGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
compositor: Some(Compositor::Sequence),
particles: vec![ParticleResult {
term: ParticleTerm::Element(ElementFrameResult {
name: Some(name_elem),
ref_name: None,
target_namespace: None,
type_ref: None,
inline_type: None,
substitution_group: vec![],
default_value: None,
fixed_value: None,
nillable: false,
is_abstract: false,
min_occurs: 1,
max_occurs: Some(1),
block: None,
final_derivation: None,
form: None,
id: None,
alternatives: vec![],
identity_constraints: vec![],
identity_constraint_refs: vec![],
annotation: None,
source: None,
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
}],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_particles: Vec::new(),
resolved_particle_types: Vec::new(),
resolved_particle_elements: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let original_key = schema_set.arenas.alloc_model_group(original_data);
schema_set
.get_or_create_namespace(None)
.register_model_group(group_name, original_key);
let age_elem = schema_set.name_table.add("age");
let new_data = ModelGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
compositor: Some(Compositor::Sequence),
particles: vec![
ParticleResult {
term: ParticleTerm::Group(ModelGroupDefResult {
name: None,
ref_name: Some(QNameRef {
prefix: None,
local_name: group_name,
namespace: None,
}),
compositor: None,
particles: vec![],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
},
ParticleResult {
term: ParticleTerm::Element(ElementFrameResult {
name: Some(age_elem),
ref_name: None,
target_namespace: None,
type_ref: None,
inline_type: None,
substitution_group: vec![],
default_value: None,
fixed_value: None,
nillable: false,
is_abstract: false,
min_occurs: 1,
max_occurs: Some(1),
block: None,
final_derivation: None,
form: None,
id: None,
alternatives: vec![],
identity_constraints: vec![],
identity_constraint_refs: vec![],
annotation: None,
source: None,
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
},
],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_particles: Vec::new(),
resolved_particle_types: Vec::new(),
resolved_particle_elements: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let new_key = schema_set.arenas.alloc_model_group(new_data);
(schema_set, original_key, new_key)
}
#[test]
fn test_redefine_model_group_self_reference() {
let (mut schema_set, original_key, new_key) = setup_model_group_redefine();
let result = apply_model_group_redefine(&mut schema_set, new_key, None, None);
assert!(
result.is_ok(),
"apply_model_group_redefine failed: {:?}",
result.err()
);
let group = schema_set.arenas.model_groups.get(new_key).unwrap();
assert_eq!(
group.redefine_original,
Some(original_key),
"redefine_original should point to the original group"
);
let result = crate::schema::resolver::resolve_all_references(&mut schema_set);
assert!(
result.is_ok(),
"resolve_all_references failed: {:?}",
result.err()
);
let group = schema_set.arenas.model_groups.get(new_key).unwrap();
match &group.resolved_particles[0] {
crate::arenas::ResolvedParticleTerm::Group {
resolved_ref: Some(key),
} => {
assert_eq!(
*key, original_key,
"Self-reference should resolve to the original group, not the new one"
);
}
other => panic!("Expected Group particle with resolved_ref, got {:?}", other),
}
}
#[test]
fn test_redefine_attribute_group_self_reference() {
let mut schema_set = SchemaSet::new();
let group_name = schema_set.name_table.add("commonAttrs");
let original_data = AttributeGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: None,
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let original_key = schema_set.arenas.alloc_attribute_group(original_data);
schema_set
.get_or_create_namespace(None)
.register_attribute_group(group_name, original_key);
let new_data = AttributeGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: vec![QNameRef {
prefix: None,
local_name: group_name,
namespace: None,
}],
attribute_wildcard: None,
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let new_key = schema_set.arenas.alloc_attribute_group(new_data);
let result = apply_attribute_group_redefine(&mut schema_set, new_key, None, None);
assert!(
result.is_ok(),
"apply_attribute_group_redefine failed: {:?}",
result.err()
);
let group = schema_set.arenas.attribute_groups.get(new_key).unwrap();
assert_eq!(group.redefine_original, Some(original_key));
let result = crate::schema::resolver::resolve_all_references(&mut schema_set);
assert!(
result.is_ok(),
"resolve_all_references failed: {:?}",
result.err()
);
let group = schema_set.arenas.attribute_groups.get(new_key).unwrap();
assert_eq!(group.resolved_attribute_groups.len(), 1);
assert_eq!(
group.resolved_attribute_groups[0], original_key,
"Self-reference should resolve to the original attribute group"
);
}
#[test]
fn test_provenance_note_redefined() {
let mut schema_set = SchemaSet::new();
let base_doc_id = schema_set.documents.len() as u32;
let base_doc = SchemaDocument::new(base_doc_id, "base.xsd".to_string());
schema_set.documents.push(base_doc);
let redefining_doc_id = schema_set.documents.len() as u32;
let redefining_doc = SchemaDocument::new(redefining_doc_id, "main.xsd".to_string());
schema_set.documents.push(redefining_doc);
let group_name = schema_set.name_table.add("testGroup");
crate::schema::composition::record_provenance(
&mut schema_set.effective_components,
crate::schema::composition::ComponentKey::ModelGroup(
schema_set.arenas.alloc_model_group(ModelGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
compositor: Some(Compositor::Sequence),
particles: Vec::new(),
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_particles: Vec::new(),
resolved_particle_types: Vec::new(),
resolved_particle_elements: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
}),
),
ComponentKind::ModelGroup,
None,
group_name,
Some(redefining_doc_id),
crate::schema::composition::redefined_action(
Some(redefining_doc_id),
ComponentKind::ModelGroup,
group_name,
None,
Some(base_doc_id),
),
);
let note = schema_set.format_provenance_note(ComponentKind::ModelGroup, None, group_name);
assert!(
note.contains("base.xsd"),
"Provenance note should mention the original document: {}",
note
);
assert!(
note.contains("main.xsd"),
"Provenance note should mention the redefining document: {}",
note
);
assert!(
note.contains("redefined"),
"Provenance note should mention 'redefined': {}",
note
);
}
#[test]
fn test_provenance_note_declared() {
let schema_set = SchemaSet::new();
let name = schema_set.name_table.get("string").unwrap();
let note = schema_set.format_provenance_note(ComponentKind::SimpleType, None, name);
assert!(
note.is_empty(),
"Provenance note for undeclared component should be empty, got: {}",
note
);
}
#[test]
fn test_redefine_model_group_no_self_reference() {
let (mut schema_set, _original_key, _new_key) = setup_model_group_redefine();
let group_name = schema_set.name_table.add("personGroup");
let age_elem = schema_set.name_table.add("age");
let new_data = ModelGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
compositor: Some(Compositor::Sequence),
particles: vec![ParticleResult {
term: ParticleTerm::Element(ElementFrameResult {
name: Some(age_elem),
ref_name: None,
target_namespace: None,
type_ref: None,
inline_type: None,
substitution_group: vec![],
default_value: None,
fixed_value: None,
nillable: false,
is_abstract: false,
min_occurs: 1,
max_occurs: Some(1),
block: None,
final_derivation: None,
form: None,
id: None,
alternatives: vec![],
identity_constraints: vec![],
identity_constraint_refs: vec![],
annotation: None,
source: None,
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
}],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_particles: Vec::new(),
resolved_particle_types: Vec::new(),
resolved_particle_elements: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let no_selfref_key = schema_set.arenas.alloc_model_group(new_data);
let result = apply_model_group_redefine(&mut schema_set, no_selfref_key, None, None);
assert!(
result.is_ok(),
"Group redefine without self-reference should succeed (clause 6.2): {:?}",
result.err()
);
}
#[test]
fn test_redefine_model_group_self_ref_wrong_min_occurs() {
let (mut schema_set, _original_key, _new_key) = setup_model_group_redefine();
let group_name = schema_set.name_table.add("personGroup");
let age_elem = schema_set.name_table.add("age");
let new_data = ModelGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
compositor: Some(Compositor::Sequence),
particles: vec![
ParticleResult {
term: ParticleTerm::Group(ModelGroupDefResult {
name: None,
ref_name: Some(QNameRef {
prefix: None,
local_name: group_name,
namespace: None,
}),
compositor: None,
particles: vec![],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
}),
min_occurs: 0, max_occurs: Some(1),
source: None,
},
ParticleResult {
term: ParticleTerm::Element(ElementFrameResult {
name: Some(age_elem),
ref_name: None,
target_namespace: None,
type_ref: None,
inline_type: None,
substitution_group: vec![],
default_value: None,
fixed_value: None,
nillable: false,
is_abstract: false,
min_occurs: 1,
max_occurs: Some(1),
block: None,
final_derivation: None,
form: None,
id: None,
alternatives: vec![],
identity_constraints: vec![],
identity_constraint_refs: vec![],
annotation: None,
source: None,
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
},
],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_particles: Vec::new(),
resolved_particle_types: Vec::new(),
resolved_particle_elements: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let bad_key = schema_set.arenas.alloc_model_group(new_data);
let result = apply_model_group_redefine(&mut schema_set, bad_key, None, None);
assert!(
result.is_err(),
"Self-ref with minOccurs=0 should fail (clause 6.1.2)"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("minOccurs"),
"Error should mention minOccurs: {}",
msg
);
}
#[test]
fn test_redefine_model_group_self_ref_wrong_max_occurs() {
let (mut schema_set, _original_key, _new_key) = setup_model_group_redefine();
let group_name = schema_set.name_table.add("personGroup");
let new_data = ModelGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
compositor: Some(Compositor::Sequence),
particles: vec![ParticleResult {
term: ParticleTerm::Group(ModelGroupDefResult {
name: None,
ref_name: Some(QNameRef {
prefix: None,
local_name: group_name,
namespace: None,
}),
compositor: None,
particles: vec![],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
}),
min_occurs: 1,
max_occurs: None, source: None,
}],
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_particles: Vec::new(),
resolved_particle_types: Vec::new(),
resolved_particle_elements: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let bad_key = schema_set.arenas.alloc_model_group(new_data);
let result = apply_model_group_redefine(&mut schema_set, bad_key, None, None);
assert!(
result.is_err(),
"Self-ref with maxOccurs=unbounded should fail (clause 6.1.2)"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("maxOccurs"),
"Error should mention maxOccurs: {}",
msg
);
}
fn model_group_def(
ref_name: Option<QNameRef>,
compositor: Option<Compositor>,
particles: Vec<ParticleResult>,
) -> ModelGroupDefResult {
ModelGroupDefResult {
name: None,
ref_name,
compositor,
particles,
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
}
}
fn self_ref_particle(
group_name: NameId,
min_occurs: u32,
max_occurs: Option<u32>,
) -> ParticleResult {
ParticleResult {
term: ParticleTerm::Group(model_group_def(
Some(QNameRef {
prefix: None,
local_name: group_name,
namespace: None,
}),
None,
vec![],
)),
min_occurs,
max_occurs,
source: None,
}
}
fn simple_element_particle(name: NameId) -> ParticleResult {
ParticleResult {
term: ParticleTerm::Element(ElementFrameResult {
name: Some(name),
ref_name: None,
target_namespace: None,
type_ref: None,
inline_type: None,
substitution_group: vec![],
default_value: None,
fixed_value: None,
nillable: false,
is_abstract: false,
min_occurs: 1,
max_occurs: Some(1),
block: None,
final_derivation: None,
form: None,
id: None,
alternatives: vec![],
identity_constraints: vec![],
identity_constraint_refs: vec![],
annotation: None,
source: None,
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
}
}
fn inline_compositor_particle(
compositor: Compositor,
inner: Vec<ParticleResult>,
) -> ParticleResult {
ParticleResult {
term: ParticleTerm::Group(model_group_def(None, Some(compositor), inner)),
min_occurs: 1,
max_occurs: Some(1),
source: None,
}
}
fn alloc_redefining_group(
schema_set: &mut SchemaSet,
top_particles: Vec<ParticleResult>,
) -> ModelGroupKey {
let group_name = schema_set.name_table.add("personGroup");
let data = ModelGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
compositor: Some(Compositor::Sequence),
particles: top_particles,
min_occurs: 1,
max_occurs: Some(1),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_particles: Vec::new(),
resolved_particle_types: Vec::new(),
resolved_particle_elements: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
schema_set.arenas.alloc_model_group(data)
}
#[test]
fn test_redefine_model_group_nested_self_ref_in_sequence() {
let (mut schema_set, _original_key, _ignored) = setup_model_group_redefine();
let group_name = schema_set.name_table.add("personGroup");
let age_elem = schema_set.name_table.add("age");
let new_key = alloc_redefining_group(
&mut schema_set,
vec![inline_compositor_particle(
Compositor::Sequence,
vec![
self_ref_particle(group_name, 1, Some(1)),
simple_element_particle(age_elem),
],
)],
);
let result = apply_model_group_redefine(&mut schema_set, new_key, None, None);
assert!(
result.is_ok(),
"nested self-ref inside <sequence> should be accepted \
(§src-redefine 6.1 'at some level'): {:?}",
result.err()
);
let group = schema_set.arenas.model_groups.get(new_key).unwrap();
assert!(
!group.redefine_requires_restriction_check,
"§6.1 self-ref path must clear the §6.2.2 restriction flag"
);
}
#[test]
fn test_redefine_model_group_nested_self_ref_wrong_min_occurs() {
let (mut schema_set, _original_key, _ignored) = setup_model_group_redefine();
let group_name = schema_set.name_table.add("personGroup");
let new_key = alloc_redefining_group(
&mut schema_set,
vec![inline_compositor_particle(
Compositor::Sequence,
vec![self_ref_particle(group_name, 2, Some(2))],
)],
);
let result = apply_model_group_redefine(&mut schema_set, new_key, None, None);
assert!(
result.is_err(),
"nested self-ref with minOccurs=2 should fail (§src-redefine 6.1.2)"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("minOccurs"),
"error should mention minOccurs: {}",
msg
);
}
#[test]
fn test_redefine_model_group_multiple_self_refs_at_different_depths() {
let (mut schema_set, _original_key, _ignored) = setup_model_group_redefine();
let group_name = schema_set.name_table.add("personGroup");
let new_key = alloc_redefining_group(
&mut schema_set,
vec![
self_ref_particle(group_name, 1, Some(1)),
inline_compositor_particle(
Compositor::Choice,
vec![self_ref_particle(group_name, 1, Some(1))],
),
],
);
let result = apply_model_group_redefine(&mut schema_set, new_key, None, None);
assert!(
result.is_err(),
"two self-refs at different depths should fail (§src-redefine 6.1.1)"
);
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("at most one") || msg.contains("found 2"),
"error should mention the count violation: {}",
msg
);
}
#[test]
fn test_count_group_self_refs_element_is_terminal() {
let schema_set = SchemaSet::new();
let group_name = schema_set.name_table.add("personGroup");
let age_elem = schema_set.name_table.add("age");
let particles = vec![simple_element_particle(age_elem)];
let mut scan = GroupSelfRefScan {
count: 0,
min_occurs: 0,
max_occurs: None,
};
count_group_self_refs(&particles, group_name, &mut scan);
assert_eq!(
scan.count, 0,
"walker must treat ParticleTerm::Element as terminal per \
§src-redefine 6.1 element-ancestor exclusion"
);
let particles_with_ref = vec![self_ref_particle(group_name, 1, Some(1))];
let mut scan = GroupSelfRefScan {
count: 0,
min_occurs: 0,
max_occurs: None,
};
count_group_self_refs(&particles_with_ref, group_name, &mut scan);
assert_eq!(
scan.count, 1,
"sanity check: walker detects a top-level self-ref"
);
}
#[test]
fn test_redefine_attribute_group_no_self_reference() {
let mut schema_set = SchemaSet::new();
let group_name = schema_set.name_table.add("commonAttrs");
let original_data = AttributeGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: None,
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let _original_key = schema_set.arenas.alloc_attribute_group(original_data);
schema_set
.get_or_create_namespace(None)
.register_attribute_group(group_name, _original_key);
let new_data = AttributeGroupData {
name: Some(group_name),
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(), attribute_wildcard: None,
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let new_key = schema_set.arenas.alloc_attribute_group(new_data);
let result = apply_attribute_group_redefine(&mut schema_set, new_key, None, None);
assert!(
result.is_ok(),
"Attribute group redefine without self-reference should succeed (clause 7.2): {:?}",
result.err()
);
}
}