Skip to main content

xsd_schema/schema/
composition.rs

1//! Schema composition graph types
2//!
3//! Tracks the relationships between schema documents created by
4//! `xs:include`, `xs:import`, `xs:redefine`, and `xs:override` directives.
5//!
6//! These types are populated during directive resolution (Step 1) and
7//! consumed by later pipeline stages for document-scoped component lookup,
8//! provenance tracking, and override processing order.
9
10use std::collections::HashMap;
11
12use crate::ids::*;
13use crate::parser::location::SourceRef;
14
15/// The kind of composition relationship between two schema documents.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum CompositionEdgeKind {
18    Include,
19    Import,
20    Redefine,
21    #[cfg(feature = "xsd11")]
22    Override,
23}
24
25/// A directed edge in the schema composition graph.
26///
27/// Records that `source_doc` references `target_doc` via a composition
28/// directive of the given `kind`. The `source` field links back to the
29/// directive element in the source document for diagnostics.
30///
31/// `target_doc` is `None` for cycle edges discovered while the target is
32/// still mid-parse (in the resolver's `resolving` set). After directive
33/// resolution completes, call [`fixup_composition_edges`] to fill in
34/// any `None` targets from `loaded_locations`.
35#[derive(Debug, Clone)]
36pub struct CompositionEdge {
37    pub source_doc: DocumentId,
38    pub target_doc: Option<DocumentId>,
39    /// Resolved URI of the target (for fixup and diagnostics).
40    pub resolved_location: String,
41    pub kind: CompositionEdgeKind,
42    /// Source location of the directive element (for diagnostics).
43    pub source: Option<SourceRef>,
44    /// Raw `schemaLocation` attribute value (not the resolved URI).
45    pub schema_location: String,
46}
47
48// ============================================================================
49// Forward-looking types for Steps 2–3 (defined now, used later)
50// ============================================================================
51
52/// Classification of a top-level schema component.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum ComponentKind {
55    SimpleType,
56    ComplexType,
57    Element,
58    Attribute,
59    ModelGroup,
60    AttributeGroup,
61    Notation,
62    IdentityConstraint,
63}
64
65impl ComponentKind {
66    /// Human-readable label for diagnostic messages
67    /// (e.g. "simple type", "attribute group").
68    pub fn display_name(self) -> &'static str {
69        match self {
70            ComponentKind::SimpleType => "simple type",
71            ComponentKind::ComplexType => "complex type",
72            ComponentKind::Element => "element",
73            ComponentKind::Attribute => "attribute",
74            ComponentKind::ModelGroup => "model group",
75            ComponentKind::AttributeGroup => "attribute group",
76            ComponentKind::Notation => "notation",
77            ComponentKind::IdentityConstraint => "identity constraint",
78        }
79    }
80}
81
82/// Unique identity of a schema component (kind + QName).
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84pub struct ComponentIdentity {
85    pub kind: ComponentKind,
86    pub name: NameId,
87    pub namespace: Option<NameId>,
88}
89
90/// Tracks which document a component originated from.
91///
92/// `owner_doc` is `None` when the originating document is unknown (e.g.,
93/// pre-loaded schemas without directive resolution).
94#[derive(Debug, Clone, Copy)]
95pub struct ComponentOrigin {
96    pub owner_doc: Option<DocumentId>,
97    pub identity: ComponentIdentity,
98}
99
100/// A typed arena key for any top-level schema component.
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum ComponentKey {
103    Type(TypeKey),
104    Element(ElementKey),
105    Attribute(AttributeKey),
106    ModelGroup(ModelGroupKey),
107    AttributeGroup(AttributeGroupKey),
108    Notation(NotationKey),
109    IdentityConstraint(IdentityConstraintKey),
110}
111
112/// How a component arrived in the effective schema set.
113#[derive(Debug, Clone)]
114pub enum CompositionAction {
115    /// Declared directly in its owning document.
116    Declared,
117    /// Included from another document (same namespace merge).
118    Included { from_doc: DocumentId },
119    /// Replaced via `xs:redefine`. `from_doc` is `None` when the redefining
120    /// document is unknown (directive without source location).
121    Redefined {
122        from_doc: Option<DocumentId>,
123        replaced: ComponentOrigin,
124    },
125    /// Replaced via `xs:override` (XSD 1.1). `from_doc` is `None` when the
126    /// overriding document is unknown.
127    #[cfg(feature = "xsd11")]
128    Overridden {
129        from_doc: Option<DocumentId>,
130        replaced: ComponentOrigin,
131    },
132}
133
134/// An effective top-level component with its provenance metadata.
135///
136/// After composition, every visible component in the namespace tables
137/// has an associated `EffectiveComponent` that records which document
138/// contributed it and how it arrived (declared, included, redefined, etc.).
139#[derive(Debug, Clone)]
140pub struct EffectiveComponent {
141    /// The arena key of the component.
142    pub key: ComponentKey,
143    /// Where this component originated.
144    pub origin: ComponentOrigin,
145    /// How this component entered the effective schema.
146    pub action: CompositionAction,
147}
148
149/// Record a composition provenance entry into the effective components map.
150///
151/// Used by both `apply_redefine` and `apply_override` to record how a
152/// component arrived in the effective schema set.
153pub fn record_provenance(
154    effective_components: &mut HashMap<ComponentIdentity, EffectiveComponent>,
155    key: ComponentKey,
156    kind: ComponentKind,
157    namespace: Option<NameId>,
158    name: NameId,
159    acting_doc_id: Option<DocumentId>,
160    action: CompositionAction,
161) {
162    let identity = ComponentIdentity {
163        kind,
164        name,
165        namespace,
166    };
167    let origin = ComponentOrigin {
168        owner_doc: acting_doc_id,
169        identity,
170    };
171    effective_components.insert(
172        identity,
173        EffectiveComponent {
174            key,
175            origin,
176            action,
177        },
178    );
179}
180
181/// Build a [`CompositionAction::Redefined`] action.
182pub fn redefined_action(
183    redefining_doc_id: Option<DocumentId>,
184    kind: ComponentKind,
185    name: NameId,
186    namespace: Option<NameId>,
187    target_doc_id: Option<DocumentId>,
188) -> CompositionAction {
189    let identity = ComponentIdentity {
190        kind,
191        name,
192        namespace,
193    };
194    CompositionAction::Redefined {
195        from_doc: redefining_doc_id,
196        replaced: ComponentOrigin {
197            owner_doc: target_doc_id,
198            identity,
199        },
200    }
201}
202
203/// Build a [`CompositionAction::Overridden`] action (XSD 1.1).
204#[cfg(feature = "xsd11")]
205pub fn overridden_action(
206    overriding_doc_id: Option<DocumentId>,
207    kind: ComponentKind,
208    name: NameId,
209    namespace: Option<NameId>,
210    target_doc_id: Option<DocumentId>,
211) -> CompositionAction {
212    let identity = ComponentIdentity {
213        kind,
214        name,
215        namespace,
216    };
217    CompositionAction::Overridden {
218        from_doc: overriding_doc_id,
219        replaced: ComponentOrigin {
220            owner_doc: target_doc_id,
221            identity,
222        },
223    }
224}
225
226/// Per-document index of top-level components declared by a single schema document.
227///
228/// Populated during assembly (`register_*` calls) and used for document-scoped
229/// lookup in `apply_redefine()` and `apply_override()`.
230#[derive(Debug, Default)]
231pub struct DocumentComponentIndex {
232    index: HashMap<ComponentIdentity, ComponentKey>,
233}
234
235impl DocumentComponentIndex {
236    pub fn new() -> Self {
237        Self::default()
238    }
239
240    /// Insert a component into the index.
241    pub fn insert(&mut self, identity: ComponentIdentity, key: ComponentKey) {
242        self.index.insert(identity, key);
243    }
244
245    /// Raw lookup by kind, namespace, and name.
246    fn get(
247        &self,
248        kind: ComponentKind,
249        namespace: Option<NameId>,
250        name: NameId,
251    ) -> Option<&ComponentKey> {
252        self.index.get(&ComponentIdentity {
253            kind,
254            name,
255            namespace,
256        })
257    }
258
259    /// Look up a type by namespace and local name (both simple and complex).
260    pub fn lookup_type(&self, namespace: Option<NameId>, name: NameId) -> Option<TypeKey> {
261        self.lookup_simple_type(namespace, name)
262            .map(TypeKey::Simple)
263            .or_else(|| {
264                self.lookup_complex_type(namespace, name)
265                    .map(TypeKey::Complex)
266            })
267    }
268
269    /// Look up a simple type by namespace and local name.
270    pub fn lookup_simple_type(
271        &self,
272        namespace: Option<NameId>,
273        name: NameId,
274    ) -> Option<SimpleTypeKey> {
275        match self.get(ComponentKind::SimpleType, namespace, name) {
276            Some(&ComponentKey::Type(TypeKey::Simple(key))) => Some(key),
277            _ => None,
278        }
279    }
280
281    /// Look up a complex type by namespace and local name.
282    pub fn lookup_complex_type(
283        &self,
284        namespace: Option<NameId>,
285        name: NameId,
286    ) -> Option<ComplexTypeKey> {
287        match self.get(ComponentKind::ComplexType, namespace, name) {
288            Some(&ComponentKey::Type(TypeKey::Complex(key))) => Some(key),
289            _ => None,
290        }
291    }
292
293    /// Look up an element by namespace and local name.
294    pub fn lookup_element(&self, namespace: Option<NameId>, name: NameId) -> Option<ElementKey> {
295        match self.get(ComponentKind::Element, namespace, name) {
296            Some(&ComponentKey::Element(key)) => Some(key),
297            _ => None,
298        }
299    }
300
301    /// Look up an attribute by namespace and local name.
302    pub fn lookup_attribute(
303        &self,
304        namespace: Option<NameId>,
305        name: NameId,
306    ) -> Option<AttributeKey> {
307        match self.get(ComponentKind::Attribute, namespace, name) {
308            Some(&ComponentKey::Attribute(key)) => Some(key),
309            _ => None,
310        }
311    }
312
313    /// Look up a model group by namespace and local name.
314    pub fn lookup_model_group(
315        &self,
316        namespace: Option<NameId>,
317        name: NameId,
318    ) -> Option<ModelGroupKey> {
319        match self.get(ComponentKind::ModelGroup, namespace, name) {
320            Some(&ComponentKey::ModelGroup(key)) => Some(key),
321            _ => None,
322        }
323    }
324
325    /// Look up an attribute group by namespace and local name.
326    pub fn lookup_attribute_group(
327        &self,
328        namespace: Option<NameId>,
329        name: NameId,
330    ) -> Option<AttributeGroupKey> {
331        match self.get(ComponentKind::AttributeGroup, namespace, name) {
332            Some(&ComponentKey::AttributeGroup(key)) => Some(key),
333            _ => None,
334        }
335    }
336
337    /// Look up a notation by namespace and local name.
338    pub fn lookup_notation(&self, namespace: Option<NameId>, name: NameId) -> Option<NotationKey> {
339        match self.get(ComponentKind::Notation, namespace, name) {
340            Some(&ComponentKey::Notation(key)) => Some(key),
341            _ => None,
342        }
343    }
344
345    /// Returns `true` if the index is empty.
346    pub fn is_empty(&self) -> bool {
347        self.index.is_empty()
348    }
349
350    /// Returns the number of components in the index.
351    pub fn len(&self) -> usize {
352        self.index.len()
353    }
354
355    /// Iterate over all `(identity, key)` pairs in the index.
356    pub fn iter(&self) -> impl Iterator<Item = (&ComponentIdentity, &ComponentKey)> {
357        self.index.iter()
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use super::*;
364
365    #[test]
366    fn test_composition_edge_kind_equality() {
367        assert_eq!(CompositionEdgeKind::Include, CompositionEdgeKind::Include);
368        assert_ne!(CompositionEdgeKind::Include, CompositionEdgeKind::Import);
369        assert_ne!(CompositionEdgeKind::Import, CompositionEdgeKind::Redefine);
370    }
371
372    #[test]
373    fn test_composition_edge_construction() {
374        let edge = CompositionEdge {
375            source_doc: 0,
376            target_doc: Some(1),
377            resolved_location: "/path/to/types.xsd".to_string(),
378            kind: CompositionEdgeKind::Include,
379            source: None,
380            schema_location: "types.xsd".to_string(),
381        };
382        assert_eq!(edge.source_doc, 0);
383        assert_eq!(edge.target_doc, Some(1));
384        assert_eq!(edge.kind, CompositionEdgeKind::Include);
385        assert!(edge.source.is_none());
386        assert_eq!(edge.schema_location, "types.xsd");
387    }
388
389    #[test]
390    fn test_composition_edge_cycle_none_target() {
391        let edge = CompositionEdge {
392            source_doc: 1,
393            target_doc: None,
394            resolved_location: "/path/to/cyclic.xsd".to_string(),
395            kind: CompositionEdgeKind::Include,
396            source: None,
397            schema_location: "cyclic.xsd".to_string(),
398        };
399        assert!(edge.target_doc.is_none());
400        assert_eq!(edge.resolved_location, "/path/to/cyclic.xsd");
401    }
402
403    #[test]
404    fn test_composition_edge_with_source() {
405        use crate::parser::location::{SourceRef, SourceSpan};
406        let edge = CompositionEdge {
407            source_doc: 0,
408            target_doc: Some(1),
409            resolved_location: "/path/to/base.xsd".to_string(),
410            kind: CompositionEdgeKind::Redefine,
411            source: Some(SourceRef::new(0, SourceSpan::new(100, 200))),
412            schema_location: "base.xsd".to_string(),
413        };
414        let src = edge.source.unwrap();
415        assert_eq!(src.doc_id, 0);
416        assert_eq!(src.span.start, 100);
417    }
418
419    #[test]
420    fn test_component_kind_variants() {
421        let kinds = [
422            ComponentKind::SimpleType,
423            ComponentKind::ComplexType,
424            ComponentKind::Element,
425            ComponentKind::Attribute,
426            ComponentKind::ModelGroup,
427            ComponentKind::AttributeGroup,
428            ComponentKind::Notation,
429            ComponentKind::IdentityConstraint,
430        ];
431        // All variants are distinct
432        for (i, a) in kinds.iter().enumerate() {
433            for (j, b) in kinds.iter().enumerate() {
434                if i == j {
435                    assert_eq!(a, b);
436                } else {
437                    assert_ne!(a, b);
438                }
439            }
440        }
441    }
442
443    #[test]
444    fn test_component_identity_equality_and_copy() {
445        let id1 = ComponentIdentity {
446            kind: ComponentKind::Element,
447            name: NameId(1),
448            namespace: Some(NameId(2)),
449        };
450        // Copy trait: assignment copies value
451        let id2 = id1;
452        assert_eq!(id1, id2);
453
454        let id3 = ComponentIdentity {
455            kind: ComponentKind::Attribute,
456            name: NameId(1),
457            namespace: Some(NameId(2)),
458        };
459        assert_ne!(id1, id3);
460    }
461
462    #[test]
463    fn test_component_origin_construction_and_copy() {
464        let origin = ComponentOrigin {
465            owner_doc: Some(42),
466            identity: ComponentIdentity {
467                kind: ComponentKind::SimpleType,
468                name: NameId(5),
469                namespace: None,
470            },
471        };
472        // Copy trait: assignment copies value
473        let origin2 = origin;
474        assert_eq!(origin.owner_doc, Some(42));
475        assert_eq!(origin.owner_doc, origin2.owner_doc);
476        assert_eq!(origin.identity.kind, ComponentKind::SimpleType);
477        assert_eq!(origin.identity.name, NameId(5));
478        assert_eq!(origin.identity.namespace, None);
479    }
480
481    #[cfg(feature = "xsd11")]
482    #[test]
483    fn test_override_edge_kind() {
484        assert_eq!(CompositionEdgeKind::Override, CompositionEdgeKind::Override);
485        assert_ne!(CompositionEdgeKind::Override, CompositionEdgeKind::Include);
486    }
487}