1use ifc_model::guid::Guid;
4use ifc_model::{Entity, EntityId, Value};
5
6use crate::types::{Benchmark, ConstraintGrade, LogicalOperator, MetricValue, ObjectiveQualifier};
7use crate::view::{
8 invalid, optional_ref, optional_text, required_ref, required_refs, required_text,
9 validate_target, wrong, ConstraintView,
10};
11use crate::{ConstraintError, ConstraintResult};
12
13const METRIC: &str = "IFCMETRIC";
14const OBJECTIVE: &str = "IFCOBJECTIVE";
15const RESOURCE_REL: &str = "IFCRESOURCECONSTRAINTRELATIONSHIP";
16const ASSIGNMENT: &str = "IFCRELASSOCIATESCONSTRAINT";
17
18macro_rules! projection {
19 ($name:ident, $kind:expr) => {
20 #[derive(Debug, Clone, Copy)]
22 pub struct $name<'m> {
23 id: EntityId,
24 entity: &'m Entity,
25 }
26 impl<'m> $name<'m> {
27 pub fn try_new(id: EntityId, entity: &'m Entity) -> ConstraintResult<Self> {
29 if entity.is_type($kind) {
30 Ok(Self { id, entity })
31 } else {
32 Err(wrong($kind, entity))
33 }
34 }
35 #[must_use]
37 pub const fn id(self) -> EntityId {
38 self.id
39 }
40 }
41 };
42}
43
44projection!(Metric, METRIC);
45projection!(Objective, OBJECTIVE);
46projection!(ResourceConstraintRelationship, RESOURCE_REL);
47projection!(ConstraintAssignment, ASSIGNMENT);
48
49fn grade(kind: &'static str, id: EntityId, entity: &Entity) -> ConstraintResult<ConstraintGrade> {
50 match entity.attribute(2) {
51 Some(Value::Enum(value)) => ConstraintGrade::parse(value)
52 .ok_or_else(|| invalid(kind, id, "ConstraintGrade", &Value::Enum(value.clone()))),
53 Some(value) => Err(invalid(kind, id, "ConstraintGrade", value)),
54 None => Err(ConstraintError::MissingAttribute {
55 entity: kind,
56 id,
57 attribute: "ConstraintGrade",
58 }),
59 }
60}
61
62fn validate_base(
63 view: ConstraintView<'_>,
64 kind: &'static str,
65 id: EntityId,
66 entity: &Entity,
67) -> ConstraintResult<()> {
68 required_text(kind, id, entity, 0, "Name")?;
69 let grade = grade(kind, id, entity)?;
70 if grade == ConstraintGrade::UserDefined
71 && optional_text(kind, id, entity, 6, "UserDefinedGrade")?.is_none()
72 {
73 return Err(ConstraintError::Semantic {
74 entity: kind,
75 id,
76 rule: "WR11",
77 detail: "USERDEFINED grade requires UserDefinedGrade".into(),
78 });
79 }
80 if let Some(actor) = optional_ref(kind, id, entity, 4, "CreatingActor")? {
81 validate_target(
82 view.model(),
83 kind,
84 id,
85 "CreatingActor",
86 actor,
87 "IfcActorSelect",
88 )?;
89 }
90 Ok(())
91}
92
93macro_rules! base_accessors {
94 ($name:ident, $kind:expr) => {
95 impl<'m> $name<'m> {
96 pub fn name(self) -> ConstraintResult<&'m str> {
98 required_text($kind, self.id, self.entity, 0, "Name")
99 }
100 pub fn description(self) -> ConstraintResult<Option<&'m str>> {
102 optional_text($kind, self.id, self.entity, 1, "Description")
103 }
104 pub fn grade(self) -> ConstraintResult<ConstraintGrade> {
106 grade($kind, self.id, self.entity)
107 }
108 pub fn source(self) -> ConstraintResult<Option<&'m str>> {
110 optional_text($kind, self.id, self.entity, 3, "ConstraintSource")
111 }
112 pub fn creating_actor(self) -> ConstraintResult<Option<EntityId>> {
114 optional_ref($kind, self.id, self.entity, 4, "CreatingActor")
115 }
116 pub fn creation_time(self) -> ConstraintResult<Option<&'m str>> {
118 optional_text($kind, self.id, self.entity, 5, "CreationTime")
119 }
120 pub fn user_defined_grade(self) -> ConstraintResult<Option<&'m str>> {
122 optional_text($kind, self.id, self.entity, 6, "UserDefinedGrade")
123 }
124 }
125 };
126}
127
128base_accessors!(Metric, METRIC);
129base_accessors!(Objective, OBJECTIVE);
130
131impl<'m> Metric<'m> {
132 pub fn benchmark(self) -> ConstraintResult<Benchmark> {
134 match self.entity.attribute(7) {
135 Some(Value::Enum(value)) => Benchmark::parse(value)
136 .ok_or_else(|| invalid(METRIC, self.id, "Benchmark", &Value::Enum(value.clone()))),
137 Some(value) => Err(invalid(METRIC, self.id, "Benchmark", value)),
138 None => Err(ConstraintError::MissingAttribute {
139 entity: METRIC,
140 id: self.id,
141 attribute: "Benchmark",
142 }),
143 }
144 }
145
146 pub fn value_source(self) -> ConstraintResult<Option<&'m str>> {
148 optional_text(METRIC, self.id, self.entity, 8, "ValueSource")
149 }
150
151 pub fn data_value(self) -> ConstraintResult<Option<MetricValue<'m>>> {
153 match self.entity.attribute(9) {
154 None | Some(Value::Null) => Ok(None),
155 Some(Value::Ref(target)) => Ok(Some(MetricValue::Entity(*target))),
156 Some(Value::Typed { type_name, value })
157 if ifc_schema::ifc4().accepts_type("IfcMetricValueSelect", type_name) =>
158 {
159 Ok(Some(MetricValue::Typed {
160 type_name,
161 value: value.as_ref(),
162 }))
163 }
164 Some(value) => Err(invalid(METRIC, self.id, "DataValue", value)),
165 }
166 }
167
168 pub fn reference_path(self) -> ConstraintResult<Option<EntityId>> {
170 optional_ref(METRIC, self.id, self.entity, 10, "ReferencePath")
171 }
172
173 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
174 validate_base(view, METRIC, self.id, self.entity)?;
175 self.benchmark()?;
176 match self.entity.attribute(9) {
177 None | Some(Value::Null) => {}
178 Some(Value::Ref(target)) => validate_target(
179 view.model(),
180 METRIC,
181 self.id,
182 "DataValue",
183 *target,
184 "IfcMetricValueSelect",
185 )?,
186 Some(Value::Typed { type_name, .. })
187 if ifc_schema::ifc4().accepts_type("IfcMetricValueSelect", type_name) => {}
188 Some(value) => return Err(invalid(METRIC, self.id, "DataValue", value)),
189 }
190 if let Some(reference) = self.reference_path()? {
191 validate_target(
192 view.model(),
193 METRIC,
194 self.id,
195 "ReferencePath",
196 reference,
197 "IfcReference",
198 )?;
199 }
200 Ok(self)
201 }
202}
203
204impl<'m> Objective<'m> {
205 pub fn benchmark_values(self) -> ConstraintResult<Option<Vec<EntityId>>> {
210 match self.entity.attribute(7) {
211 None | Some(Value::Null) => Ok(None),
212 Some(Value::List(values)) if !values.is_empty() => values
213 .iter()
214 .map(|value| match value {
215 Value::Ref(target) => Ok(*target),
216 other => Err(invalid(OBJECTIVE, self.id, "BenchmarkValues", other)),
217 })
218 .collect::<ConstraintResult<Vec<_>>>()
219 .map(Some),
220 Some(value) => Err(invalid(OBJECTIVE, self.id, "BenchmarkValues", value)),
221 }
222 }
223
224 pub fn logical_aggregator(self) -> ConstraintResult<Option<LogicalOperator>> {
226 match self.entity.attribute(8) {
227 None | Some(Value::Null) => Ok(None),
228 Some(Value::Enum(value)) => LogicalOperator::parse(value).map(Some).ok_or_else(|| {
229 invalid(
230 OBJECTIVE,
231 self.id,
232 "LogicalAggregator",
233 &Value::Enum(value.clone()),
234 )
235 }),
236 Some(value) => Err(invalid(OBJECTIVE, self.id, "LogicalAggregator", value)),
237 }
238 }
239
240 pub fn qualifier(self) -> ConstraintResult<ObjectiveQualifier> {
242 match self.entity.attribute(9) {
243 Some(Value::Enum(value)) => ObjectiveQualifier::parse(value).ok_or_else(|| {
244 invalid(
245 OBJECTIVE,
246 self.id,
247 "ObjectiveQualifier",
248 &Value::Enum(value.clone()),
249 )
250 }),
251 Some(value) => Err(invalid(OBJECTIVE, self.id, "ObjectiveQualifier", value)),
252 None => Err(ConstraintError::MissingAttribute {
253 entity: OBJECTIVE,
254 id: self.id,
255 attribute: "ObjectiveQualifier",
256 }),
257 }
258 }
259
260 pub fn user_defined_qualifier(self) -> ConstraintResult<Option<&'m str>> {
262 optional_text(OBJECTIVE, self.id, self.entity, 10, "UserDefinedQualifier")
263 }
264
265 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
266 validate_base(view, OBJECTIVE, self.id, self.entity)?;
267 self.logical_aggregator()?;
268 if self.qualifier()? == ObjectiveQualifier::UserDefined
269 && self.user_defined_qualifier()?.is_none()
270 {
271 return Err(ConstraintError::Semantic {
272 entity: OBJECTIVE,
273 id: self.id,
274 rule: "WR21",
275 detail: "USERDEFINED qualifier requires UserDefinedQualifier".into(),
276 });
277 }
278 if let Some(values) = self.benchmark_values()? {
279 for target in values {
280 validate_target(
281 view.model(),
282 OBJECTIVE,
283 self.id,
284 "BenchmarkValues",
285 target,
286 "IfcConstraint",
287 )?;
288 }
289 }
290 Ok(self)
291 }
292}
293
294impl<'m> ResourceConstraintRelationship<'m> {
295 pub fn name(self) -> ConstraintResult<Option<&'m str>> {
297 optional_text(RESOURCE_REL, self.id, self.entity, 0, "Name")
298 }
299 pub fn description(self) -> ConstraintResult<Option<&'m str>> {
301 optional_text(RESOURCE_REL, self.id, self.entity, 1, "Description")
302 }
303 pub fn relating_constraint(self) -> ConstraintResult<EntityId> {
305 required_ref(RESOURCE_REL, self.id, self.entity, 2, "RelatingConstraint")
306 }
307 pub fn related_resources(self) -> ConstraintResult<Vec<EntityId>> {
309 required_refs(
310 RESOURCE_REL,
311 self.id,
312 self.entity,
313 3,
314 "RelatedResourceObjects",
315 )
316 }
317 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
318 validate_target(
319 view.model(),
320 RESOURCE_REL,
321 self.id,
322 "RelatingConstraint",
323 self.relating_constraint()?,
324 "IfcConstraint",
325 )?;
326 for target in self.related_resources()? {
327 validate_target(
328 view.model(),
329 RESOURCE_REL,
330 self.id,
331 "RelatedResourceObjects",
332 target,
333 "IfcResourceObjectSelect",
334 )?;
335 }
336 Ok(self)
337 }
338}
339
340impl<'m> ConstraintAssignment<'m> {
341 pub fn global_id(self) -> ConstraintResult<&'m str> {
343 required_text(ASSIGNMENT, self.id, self.entity, 0, "GlobalId")
344 }
345 pub fn intent(self) -> ConstraintResult<Option<&'m str>> {
347 optional_text(ASSIGNMENT, self.id, self.entity, 5, "Intent")
348 }
349 pub fn related_objects(self) -> ConstraintResult<Vec<EntityId>> {
351 required_refs(ASSIGNMENT, self.id, self.entity, 4, "RelatedObjects")
352 }
353 pub fn relating_constraint(self) -> ConstraintResult<EntityId> {
355 required_ref(ASSIGNMENT, self.id, self.entity, 6, "RelatingConstraint")
356 }
357 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
358 if Guid::parse(self.global_id()?).is_none() {
359 return Err(ConstraintError::InvalidValue {
360 entity: ASSIGNMENT,
361 id: self.id,
362 attribute: "GlobalId",
363 value: self.global_id()?.into(),
364 });
365 }
366 validate_target(
367 view.model(),
368 ASSIGNMENT,
369 self.id,
370 "RelatingConstraint",
371 self.relating_constraint()?,
372 "IfcConstraint",
373 )?;
374 for target in self.related_objects()? {
375 validate_target(
376 view.model(),
377 ASSIGNMENT,
378 self.id,
379 "RelatedObjects",
380 target,
381 "IfcDefinitionSelect",
382 )?;
383 }
384 Ok(self)
385 }
386}
387
388impl<'m> ConstraintView<'m> {
389 pub fn metric(self, id: EntityId) -> ConstraintResult<Metric<'m>> {
391 let entity = self
392 .model()
393 .get(id)
394 .ok_or(ConstraintError::UnknownEntity { id })?;
395 Metric::try_new(id, entity)?.validate(self)
396 }
397 pub fn objective(self, id: EntityId) -> ConstraintResult<Objective<'m>> {
399 let entity = self
400 .model()
401 .get(id)
402 .ok_or(ConstraintError::UnknownEntity { id })?;
403 Objective::try_new(id, entity)?.validate(self)
404 }
405 pub fn resource_constraint_relationship(
407 self,
408 id: EntityId,
409 ) -> ConstraintResult<ResourceConstraintRelationship<'m>> {
410 let entity = self
411 .model()
412 .get(id)
413 .ok_or(ConstraintError::UnknownEntity { id })?;
414 ResourceConstraintRelationship::try_new(id, entity)?.validate(self)
415 }
416 pub fn constraint_assignment(self, id: EntityId) -> ConstraintResult<ConstraintAssignment<'m>> {
418 let entity = self
419 .model()
420 .get(id)
421 .ok_or(ConstraintError::UnknownEntity { id })?;
422 ConstraintAssignment::try_new(id, entity)?.validate(self)
423 }
424 pub fn resources_constrained_by(self, constraint: EntityId) -> ConstraintResult<Vec<EntityId>> {
426 self.constraint(constraint)?;
427 let mut out = Vec::new();
428 for (id, entity) in self.model().of_type(RESOURCE_REL) {
429 let relationship = ResourceConstraintRelationship { id, entity };
430 if relationship.relating_constraint()? == constraint {
431 out.extend(relationship.validate(self)?.related_resources()?);
432 }
433 }
434 Ok(out)
435 }
436 pub fn objects_constrained_by(self, constraint: EntityId) -> ConstraintResult<Vec<EntityId>> {
438 self.constraint(constraint)?;
439 let mut out = Vec::new();
440 for (id, entity) in self.model().of_type(ASSIGNMENT) {
441 let relationship = ConstraintAssignment { id, entity };
442 if relationship.relating_constraint()? == constraint {
443 out.extend(relationship.validate(self)?.related_objects()?);
444 }
445 }
446 Ok(out)
447 }
448 fn constraint(self, id: EntityId) -> ConstraintResult<()> {
449 let entity = self
450 .model()
451 .get(id)
452 .ok_or(ConstraintError::UnknownEntity { id })?;
453 if entity.is_type(METRIC) {
454 self.metric(id).map(|_| ())
455 } else if entity.is_type(OBJECTIVE) {
456 self.objective(id).map(|_| ())
457 } else {
458 Err(wrong("IfcConstraint", entity))
459 }
460 }
461}