laminar_connectors/fpml/
references.rs1use std::collections::{HashMap, HashSet};
6
7use super::document::{Document, Element, Interner, NameId};
8use super::error::{Rejection, Stage};
9use super::schema_model::SchemaModel;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ReferencePolicy {
14 Resolve,
17 Schema,
20}
21
22#[derive(Debug, Default)]
24pub(crate) struct ReferenceRules {
25 targets: HashMap<NameId, Vec<NameId>>,
27 element_types: HashMap<NameId, HashSet<NameId>>,
29 type_ancestors: HashMap<NameId, HashSet<NameId>>,
31}
32
33impl ReferenceRules {
34 pub(crate) fn new(model: &SchemaModel, interner: &mut Interner) -> Self {
35 let mut rules = Self::default();
36 for (element, types) in &model.element_types {
37 let name = interner.intern(&element.0, &element.1);
38 let ancestors = rules.element_types.entry(name).or_default();
39 for ty in types {
40 for ancestor in model.ancestors(ty) {
41 ancestors.insert(interner.intern(&ancestor.0, &ancestor.1));
42 }
43 }
44 if let Some(targets) = model.reference_targets_of(element) {
45 rules.targets.insert(
46 name,
47 targets
48 .iter()
49 .map(|target| interner.intern(&target.0, &target.1))
50 .collect(),
51 );
52 }
53 }
54 for ty in model.bases.keys().chain(model.reference_targets.keys()) {
55 let id = interner.intern(&ty.0, &ty.1);
56 let ancestors = model
57 .ancestors(ty)
58 .map(|ancestor| interner.intern(&ancestor.0, &ancestor.1))
59 .collect::<HashSet<_>>();
60 if let Some(target) = model.reference_target_of_type(ty) {
61 rules
62 .targets
63 .entry(id)
64 .or_insert_with(|| vec![interner.intern(&target.0, &target.1)]);
65 }
66 rules.type_ancestors.insert(id, ancestors);
67 }
68 rules
69 }
70
71 pub(crate) fn check(
73 &self,
74 document: &Document,
75 interner: &Interner,
76 href: NameId,
77 policy: ReferencePolicy,
78 ) -> Result<(), Rejection> {
79 for &source_id in &document.references {
80 let Some(source) = document.element(source_id) else {
81 continue;
82 };
83 let value = document.attribute(source, href).unwrap_or_default().trim();
84 let Some(target) = document
85 .dereference(value)
86 .and_then(|id| document.element(id))
87 else {
88 return Err(Rejection::new(
89 Stage::References,
90 format!(
91 "line {}: '{}' refers to an id that does not exist in the document",
92 source.line,
93 interner.local(source.name)
94 ),
95 ));
96 };
97 if policy == ReferencePolicy::Schema {
98 self.check_target(source, target, interner)?;
99 }
100 }
101 Ok(())
102 }
103
104 pub(crate) fn check_target(
107 &self,
108 source: &Element,
109 target: &Element,
110 interner: &Interner,
111 ) -> Result<(), Rejection> {
112 let allowed = source
113 .xsi_type
114 .and_then(|ty| self.targets.get(&ty))
115 .or_else(|| self.targets.get(&source.name));
116 let Some(allowed) = allowed else {
117 return Ok(());
118 };
119 let target_types = target
120 .xsi_type
121 .and_then(|ty| self.type_ancestors.get(&ty))
122 .or_else(|| self.element_types.get(&target.name));
123 if target_types.is_some_and(|types| allowed.iter().any(|ty| types.contains(ty))) {
124 return Ok(());
125 }
126 Err(Rejection::new(
127 Stage::References,
128 format!(
129 "line {}: '{}' refers to a '{}' element, which is not a {}",
130 source.line,
131 interner.local(source.name),
132 interner.local(target.name),
133 allowed
134 .iter()
135 .map(|ty| interner.local(*ty))
136 .collect::<Vec<_>>()
137 .join(" or ")
138 ),
139 ))
140 }
141}