1use ifc_model::{EntityId, Model, Value};
4use ifc_schema::Schema;
5
6use crate::report::{Finding, Path, Report};
7use crate::type_check::element_type;
8
9pub fn dangling_references(model: &Model, report: &mut Report) {
19 let mut ids: Vec<_> = model.iter().map(|(id, _)| id).collect();
20 ids.sort_unstable();
21 for id in ids {
22 let Some(entity) = model.get(id) else {
23 continue;
24 };
25 for (index, value) in entity.attributes.iter().enumerate() {
26 let mut missing = Vec::new();
27 value.for_each_ref(&mut |target| {
28 if model.get(target).is_none() {
29 missing.push(target);
30 }
31 });
32 missing.sort_unstable();
33 missing.dedup();
34 for target in missing {
35 report.push(Finding::error(
36 "structure.reference.dangling",
37 Path::Attribute {
38 entity: id,
39 index,
40 name: None,
41 },
42 format!("references {target}, which the file does not contain"),
43 ));
44 }
45 }
46 }
47}
48
49pub fn wrong_kind_references(model: &Model, schema: &Schema, report: &mut Report) {
62 let mut ids: Vec<_> = model.iter().map(|(id, _)| id).collect();
63 ids.sort_unstable();
64 for id in ids {
65 let Some(entity) = model.get(id) else {
66 continue;
67 };
68 let declared = schema.attributes(&entity.type_name);
69 for (index, value) in entity.attributes.iter().enumerate() {
70 let Some(attribute) = declared.get(index) else {
71 continue;
72 };
73 let mut offenders: Vec<(EntityId, String, &str)> = Vec::new();
74 for (expected, target) in expected_references(schema, &attribute.type_name, value) {
75 if schema.entity(&expected).is_none() {
78 continue;
79 }
80 let Some(target_entity) = model.get(target) else {
81 continue; };
83 if schema.entity(&target_entity.type_name).is_none() {
87 continue;
88 }
89 if !schema.is_a(&target_entity.type_name, &expected) {
90 offenders.push((target, expected, &target_entity.type_name));
91 }
92 }
93 offenders.sort_unstable();
94 offenders.dedup();
95 for (target, expected, actual) in offenders {
96 report.push(Finding::error(
97 "structure.reference.wrong_type",
98 Path::Attribute {
99 entity: id,
100 index,
101 name: Some(attribute.name.clone()),
102 },
103 format!("declared {expected} but {target} is {actual}"),
104 ));
105 }
106 }
107 }
108}
109
110const MAX_NESTING: usize = 8;
115
116pub(crate) fn expected_references(
124 schema: &Schema,
125 declared: &str,
126 value: &Value,
127) -> Vec<(String, EntityId)> {
128 let mut found = Vec::new();
129 collect(schema, declared, value, 0, &mut found);
130 found
131}
132
133fn collect(
134 schema: &Schema,
135 declared: &str,
136 value: &Value,
137 depth: usize,
138 found: &mut Vec<(String, EntityId)>,
139) {
140 match value {
141 Value::Ref(target) => found.push((declared.to_string(), *target)),
142 Value::List(items) if depth < MAX_NESTING => {
143 let element = element_type(schema, declared);
144 for item in items {
145 collect(schema, &element, item, depth + 1, found);
146 }
147 }
148 Value::Typed { type_name, value } => collect(schema, type_name, value, depth, found),
149 _ => {}
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
161 fn an_undeclared_target_type_is_not_judged() {
162 let schema = ifc_schema::ifc4();
163 let mut model = Model::new();
164 model.insert(
165 EntityId(1),
166 ifc_model::Entity::new("IFCREFERENT", Vec::new()),
167 );
168 model.insert(
169 EntityId(2),
170 ifc_model::Entity::new("IFCPROPERTYSET", Vec::new()),
171 );
172 let mut attributes =
173 vec![Value::Null; schema.attributes("IFCRELDEFINESBYPROPERTIES").len()];
174 attributes[4] = Value::List(vec![Value::Ref(EntityId(1)), Value::Ref(EntityId(2))]);
175 model.insert(
176 EntityId(3),
177 ifc_model::Entity::new("IFCRELDEFINESBYPROPERTIES", attributes),
178 );
179 let mut report = Report::new();
180 wrong_kind_references(&model, schema, &mut report);
181 let messages: Vec<&str> = report
182 .findings()
183 .iter()
184 .map(|finding| finding.message.as_str())
185 .collect();
186 assert_eq!(
187 messages,
188 ["declared IfcObjectDefinition but #2 is IFCPROPERTYSET"]
189 );
190 }
191
192 #[test]
195 fn references_are_paired_with_the_type_their_position_expects() {
196 let schema = ifc_schema::ifc4();
197 let set = Value::List(vec![Value::Ref(EntityId(1)), Value::Ref(EntityId(2))]);
198 assert_eq!(
199 expected_references(schema, "IfcProduct", &set),
200 [
201 ("IfcProduct".to_string(), EntityId(1)),
202 ("IfcProduct".to_string(), EntityId(2))
203 ]
204 );
205 let typed = Value::Typed {
206 type_name: "IFCPROPERTYSETDEFINITIONSET".into(),
207 value: Box::new(Value::List(vec![Value::Ref(EntityId(3))])),
208 };
209 assert_eq!(
210 expected_references(schema, "IfcPropertySetDefinitionSelect", &typed),
211 [("IfcPropertySetDefinition".to_string(), EntityId(3))]
212 );
213 }
214}