1use ifc_model::guid::Guid;
5use ifc_model::{Entity, EntityId, Value};
6use ifc_schema::SchemaVersion;
7
8use crate::release::Layout;
9use crate::types::{Benchmark, ConstraintGrade, LogicalOperator, MetricValue, ObjectiveQualifier};
10use crate::view::{invalid, wrong, ConstraintView, Record};
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 record: Record<'m>,
24 }
25 impl<'m> $name<'m> {
26 pub fn try_new(
39 id: EntityId,
40 entity: &'m Entity,
41 release: SchemaVersion,
42 ) -> ConstraintResult<Self> {
43 Self::bound(id, entity, Layout::of_version(release)?)
44 }
45 pub(crate) fn bound(
46 id: EntityId,
47 entity: &'m Entity,
48 layout: Layout,
49 ) -> ConstraintResult<Self> {
50 if !entity.is_type($kind) {
51 return Err(wrong($kind, entity));
52 }
53 layout.require_entity($kind)?;
54 Ok(Self {
55 record: Record {
56 kind: $kind,
57 id,
58 entity,
59 layout,
60 },
61 })
62 }
63 #[must_use]
65 pub const fn id(self) -> EntityId {
66 self.record.id
67 }
68 #[must_use]
70 pub const fn release(self) -> SchemaVersion {
71 self.record.layout.version()
72 }
73 }
74 };
75}
76
77projection!(Metric, METRIC);
78projection!(Objective, OBJECTIVE);
79projection!(ResourceConstraintRelationship, RESOURCE_REL);
80projection!(ConstraintAssignment, ASSIGNMENT);
81
82fn enumeration<T>(
84 record: Record<'_>,
85 attribute: &'static str,
86 parse: fn(&str) -> Option<T>,
87) -> ConstraintResult<Option<T>> {
88 match record.value(attribute)? {
89 None => Ok(None),
90 Some(Value::Enum(value)) => parse(value).map(Some).ok_or_else(|| {
91 invalid(
92 record.kind,
93 record.id,
94 attribute,
95 &Value::Enum(value.clone()),
96 )
97 }),
98 Some(value) => Err(invalid(record.kind, record.id, attribute, value)),
99 }
100}
101
102fn required<T>(
103 record: Record<'_>,
104 attribute: &'static str,
105 value: Option<T>,
106) -> ConstraintResult<T> {
107 value.ok_or(ConstraintError::MissingAttribute {
108 entity: record.kind,
109 id: record.id,
110 attribute,
111 })
112}
113
114fn validate_base(view: ConstraintView<'_>, record: Record<'_>) -> ConstraintResult<()> {
115 record.required_text("Name")?;
116 let grade = required(
117 record,
118 "ConstraintGrade",
119 enumeration(record, "ConstraintGrade", ConstraintGrade::parse)?,
120 )?;
121 if grade == ConstraintGrade::UserDefined && record.optional_text("UserDefinedGrade")?.is_none()
122 {
123 return Err(ConstraintError::Semantic {
124 entity: record.kind,
125 id: record.id,
126 rule: "WR11",
127 detail: "USERDEFINED grade requires UserDefinedGrade".into(),
128 });
129 }
130 if let Some(actor) = record.optional_ref("CreatingActor")? {
131 record.validate_target(view.model(), "CreatingActor", actor, "IfcActorSelect")?;
132 }
133 match record.optional_text("CreationTime") {
134 Ok(_) => {}
135 Err(ConstraintError::StructuredValue { target, .. }) => {
136 record.validate_target(view.model(), "CreationTime", target, "IfcDateTimeSelect")?;
137 }
138 Err(error) => return Err(error),
139 }
140 Ok(())
141}
142
143macro_rules! base_accessors {
144 ($name:ident) => {
145 impl<'m> $name<'m> {
146 pub fn name(self) -> ConstraintResult<&'m str> {
148 self.record.required_text("Name")
149 }
150 pub fn description(self) -> ConstraintResult<Option<&'m str>> {
152 self.record.optional_text("Description")
153 }
154 pub fn grade(self) -> ConstraintResult<ConstraintGrade> {
156 required(
157 self.record,
158 "ConstraintGrade",
159 enumeration(self.record, "ConstraintGrade", ConstraintGrade::parse)?,
160 )
161 }
162 pub fn source(self) -> ConstraintResult<Option<&'m str>> {
164 self.record.optional_text("ConstraintSource")
165 }
166 pub fn creating_actor(self) -> ConstraintResult<Option<EntityId>> {
168 self.record.optional_ref("CreatingActor")
169 }
170 pub fn creation_time(self) -> ConstraintResult<Option<&'m str>> {
174 self.record.optional_text("CreationTime")
175 }
176 pub fn user_defined_grade(self) -> ConstraintResult<Option<&'m str>> {
178 self.record.optional_text("UserDefinedGrade")
179 }
180 }
181 };
182}
183
184base_accessors!(Metric);
185base_accessors!(Objective);
186
187impl<'m> Metric<'m> {
188 pub fn benchmark(self) -> ConstraintResult<Benchmark> {
190 required(
191 self.record,
192 "Benchmark",
193 enumeration(self.record, "Benchmark", Benchmark::parse)?,
194 )
195 }
196
197 pub fn value_source(self) -> ConstraintResult<Option<&'m str>> {
199 self.record.optional_text("ValueSource")
200 }
201
202 pub fn data_value(self) -> ConstraintResult<Option<MetricValue<'m>>> {
205 let record = self.record;
206 match record.value("DataValue")? {
207 None => Ok(None),
208 Some(Value::Ref(target)) => Ok(Some(MetricValue::Entity(*target))),
209 Some(Value::Typed { type_name, value })
210 if record
211 .layout
212 .schema()
213 .accepts_type("IfcMetricValueSelect", type_name) =>
214 {
215 Ok(Some(MetricValue::Typed {
216 type_name,
217 value: value.as_ref(),
218 }))
219 }
220 Some(value) => Err(invalid(METRIC, record.id, "DataValue", value)),
221 }
222 }
223
224 pub fn reference_path(self) -> ConstraintResult<Option<EntityId>> {
226 self.record.optional_ref("ReferencePath")
227 }
228
229 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
230 let record = self.record;
231 validate_base(view, record)?;
232 self.benchmark()?;
233 match self.data_value()? {
234 None if record.requires("DataValue") => {
235 return Err(ConstraintError::MissingAttribute {
236 entity: METRIC,
237 id: record.id,
238 attribute: "DataValue",
239 })
240 }
241 Some(MetricValue::Entity(target)) => {
242 record.validate_target(
243 view.model(),
244 "DataValue",
245 target,
246 "IfcMetricValueSelect",
247 )?;
248 }
249 _ => {}
250 }
251 if record.declares("ReferencePath") {
252 if let Some(reference) = self.reference_path()? {
253 record.validate_target(view.model(), "ReferencePath", reference, "IfcReference")?;
254 }
255 }
256 Ok(self)
257 }
258}
259
260impl<'m> Objective<'m> {
261 pub fn benchmark_values(self) -> ConstraintResult<Option<Vec<EntityId>>> {
267 let record = self.record;
268 match record.value("BenchmarkValues")? {
269 None => Ok(None),
270 Some(Value::Ref(target)) if !record.aggregate("BenchmarkValues") => {
271 Ok(Some(vec![*target]))
272 }
273 Some(Value::List(values))
274 if record.aggregate("BenchmarkValues") && !values.is_empty() =>
275 {
276 values
277 .iter()
278 .map(|value| match value {
279 Value::Ref(target) => Ok(*target),
280 other => Err(invalid(OBJECTIVE, record.id, "BenchmarkValues", other)),
281 })
282 .collect::<ConstraintResult<Vec<_>>>()
283 .map(Some)
284 }
285 Some(value) => Err(invalid(OBJECTIVE, record.id, "BenchmarkValues", value)),
286 }
287 }
288
289 pub fn logical_aggregator(self) -> ConstraintResult<Option<LogicalOperator>> {
292 enumeration(self.record, "LogicalAggregator", LogicalOperator::parse)
293 }
294
295 pub fn qualifier(self) -> ConstraintResult<ObjectiveQualifier> {
297 required(
298 self.record,
299 "ObjectiveQualifier",
300 enumeration(self.record, "ObjectiveQualifier", ObjectiveQualifier::parse)?,
301 )
302 }
303
304 pub fn user_defined_qualifier(self) -> ConstraintResult<Option<&'m str>> {
306 self.record.optional_text("UserDefinedQualifier")
307 }
308
309 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
310 let record = self.record;
311 validate_base(view, record)?;
312 if record.declares("LogicalAggregator") {
313 self.logical_aggregator()?;
314 }
315 if self.qualifier()? == ObjectiveQualifier::UserDefined
316 && self.user_defined_qualifier()?.is_none()
317 {
318 return Err(ConstraintError::Semantic {
319 entity: OBJECTIVE,
320 id: record.id,
321 rule: "WR21",
322 detail: "USERDEFINED qualifier requires UserDefinedQualifier".into(),
323 });
324 }
325 if let Some(values) = self.benchmark_values()? {
326 let expected = record.declared_type("BenchmarkValues");
327 for target in values {
328 record.validate_target(view.model(), "BenchmarkValues", target, expected)?;
329 }
330 }
331 Ok(self)
332 }
333}
334
335impl<'m> ResourceConstraintRelationship<'m> {
336 pub fn name(self) -> ConstraintResult<Option<&'m str>> {
338 self.record.optional_text("Name")
339 }
340 pub fn description(self) -> ConstraintResult<Option<&'m str>> {
342 self.record.optional_text("Description")
343 }
344 pub fn relating_constraint(self) -> ConstraintResult<EntityId> {
346 self.record.required_ref("RelatingConstraint")
347 }
348 pub fn related_resources(self) -> ConstraintResult<Vec<EntityId>> {
350 self.record.required_refs("RelatedResourceObjects")
351 }
352 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
353 let record = self.record;
354 record.validate_target(
355 view.model(),
356 "RelatingConstraint",
357 self.relating_constraint()?,
358 "IfcConstraint",
359 )?;
360 for target in self.related_resources()? {
361 record.validate_target(
362 view.model(),
363 "RelatedResourceObjects",
364 target,
365 "IfcResourceObjectSelect",
366 )?;
367 }
368 Ok(self)
369 }
370}
371
372impl<'m> ConstraintAssignment<'m> {
373 pub fn global_id(self) -> ConstraintResult<&'m str> {
375 self.record.required_text("GlobalId")
376 }
377 pub fn intent(self) -> ConstraintResult<Option<&'m str>> {
379 self.record.optional_text("Intent")
380 }
381 pub fn related_objects(self) -> ConstraintResult<Vec<EntityId>> {
383 self.record.required_refs("RelatedObjects")
384 }
385 pub fn relating_constraint(self) -> ConstraintResult<EntityId> {
387 self.record.required_ref("RelatingConstraint")
388 }
389 fn validate(self, view: ConstraintView<'m>) -> ConstraintResult<Self> {
390 let record = self.record;
391 if Guid::parse(self.global_id()?).is_none() {
392 return Err(ConstraintError::InvalidValue {
393 entity: ASSIGNMENT,
394 id: record.id,
395 attribute: "GlobalId",
396 value: self.global_id()?.into(),
397 });
398 }
399 record.validate_target(
400 view.model(),
401 "RelatingConstraint",
402 self.relating_constraint()?,
403 "IfcConstraint",
404 )?;
405 for target in self.related_objects()? {
406 record.validate_target(
407 view.model(),
408 "RelatedObjects",
409 target,
410 "IfcDefinitionSelect",
411 )?;
412 }
413 Ok(self)
414 }
415}
416
417impl<'m> ConstraintView<'m> {
418 fn entity(self, id: EntityId) -> ConstraintResult<&'m Entity> {
419 self.model()
420 .get(id)
421 .ok_or(ConstraintError::UnknownEntity { id })
422 }
423
424 pub fn metric(self, id: EntityId) -> ConstraintResult<Metric<'m>> {
426 Metric::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
427 }
428 pub fn objective(self, id: EntityId) -> ConstraintResult<Objective<'m>> {
430 Objective::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
431 }
432 pub fn resource_constraint_relationship(
434 self,
435 id: EntityId,
436 ) -> ConstraintResult<ResourceConstraintRelationship<'m>> {
437 ResourceConstraintRelationship::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
438 }
439 pub fn constraint_assignment(self, id: EntityId) -> ConstraintResult<ConstraintAssignment<'m>> {
441 ConstraintAssignment::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
442 }
443 pub fn resources_constrained_by(self, constraint: EntityId) -> ConstraintResult<Vec<EntityId>> {
447 self.constraint(constraint)?;
448 let layout = self.layout()?;
449 let mut out = Vec::new();
450 if layout.require_entity(RESOURCE_REL).is_err() {
451 return Ok(out);
452 }
453 for (id, entity) in self.model().of_type(RESOURCE_REL) {
454 let relationship = ResourceConstraintRelationship::bound(id, entity, layout)?;
455 if relationship.relating_constraint()? == constraint {
456 out.extend(relationship.validate(self)?.related_resources()?);
457 }
458 }
459 Ok(out)
460 }
461 pub fn objects_constrained_by(self, constraint: EntityId) -> ConstraintResult<Vec<EntityId>> {
463 self.constraint(constraint)?;
464 let layout = self.layout()?;
465 let mut out = Vec::new();
466 for (id, entity) in self.model().of_type(ASSIGNMENT) {
467 let relationship = ConstraintAssignment::bound(id, entity, layout)?;
468 if relationship.relating_constraint()? == constraint {
469 out.extend(relationship.validate(self)?.related_objects()?);
470 }
471 }
472 Ok(out)
473 }
474 fn constraint(self, id: EntityId) -> ConstraintResult<()> {
475 let entity = self.entity(id)?;
476 if entity.is_type(METRIC) {
477 self.metric(id).map(|_| ())
478 } else if entity.is_type(OBJECTIVE) {
479 self.objective(id).map(|_| ())
480 } else {
481 Err(wrong("IfcConstraint", entity))
482 }
483 }
484}