1use ifc_model::guid::Guid;
5use ifc_model::{Entity, EntityId};
6use ifc_schema::SchemaVersion;
7
8use crate::release::Layout;
9use crate::view::{wrong, ApprovalView, Record};
10use crate::{ApprovalError, ApprovalResult};
11
12const APPROVAL: &str = "IFCAPPROVAL";
13const APPROVAL_REL: &str = "IFCAPPROVALRELATIONSHIP";
14const RESOURCE_REL: &str = "IFCRESOURCEAPPROVALRELATIONSHIP";
15const ASSIGNMENT: &str = "IFCRELASSOCIATESAPPROVAL";
16
17macro_rules! projection {
18 ($name:ident, $kind:expr) => {
19 #[derive(Debug, Clone, Copy)]
21 pub struct $name<'m> {
22 record: Record<'m>,
23 }
24 impl<'m> $name<'m> {
25 pub fn try_new(
38 id: EntityId,
39 entity: &'m Entity,
40 release: SchemaVersion,
41 ) -> ApprovalResult<Self> {
42 Self::bound(id, entity, Layout::of_version(release)?)
43 }
44 pub(crate) fn bound(
45 id: EntityId,
46 entity: &'m Entity,
47 layout: Layout,
48 ) -> ApprovalResult<Self> {
49 if !entity.is_type($kind) {
50 return Err(wrong($kind, entity));
51 }
52 layout.require_entity($kind)?;
53 Ok(Self {
54 record: Record {
55 kind: $kind,
56 id,
57 entity,
58 layout,
59 },
60 })
61 }
62 #[must_use]
64 pub const fn id(self) -> EntityId {
65 self.record.id
66 }
67 #[must_use]
69 pub const fn release(self) -> SchemaVersion {
70 self.record.layout.version()
71 }
72 }
73 };
74}
75
76projection!(Approval, APPROVAL);
77projection!(ApprovalRelationship, APPROVAL_REL);
78projection!(ResourceApprovalRelationship, RESOURCE_REL);
79projection!(ApprovalAssignment, ASSIGNMENT);
80
81fn required_if_declared(record: Record<'_>, attribute: &'static str) -> ApprovalResult<()> {
84 if record.requires(attribute) {
85 record.required_text(attribute)?;
86 }
87 Ok(())
88}
89
90impl<'m> Approval<'m> {
91 pub fn identifier(self) -> ApprovalResult<Option<&'m str>> {
93 self.record.optional_text("Identifier")
94 }
95 pub fn name(self) -> ApprovalResult<Option<&'m str>> {
97 self.record.optional_text("Name")
98 }
99 pub fn description(self) -> ApprovalResult<Option<&'m str>> {
101 self.record.optional_text("Description")
102 }
103 pub fn time_of_approval(self) -> ApprovalResult<Option<&'m str>> {
109 self.record.optional_text("TimeOfApproval")
110 }
111 pub fn status(self) -> ApprovalResult<Option<&'m str>> {
113 self.record.optional_text("Status")
114 }
115 pub fn level(self) -> ApprovalResult<Option<&'m str>> {
117 self.record.optional_text("Level")
118 }
119 pub fn qualifier(self) -> ApprovalResult<Option<&'m str>> {
121 self.record.optional_text("Qualifier")
122 }
123 pub fn requesting_approval(self) -> ApprovalResult<Option<EntityId>> {
126 self.record.optional_ref("RequestingApproval")
127 }
128 pub fn giving_approval(self) -> ApprovalResult<Option<EntityId>> {
130 self.record.optional_ref("GivingApproval")
131 }
132
133 fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
134 let record = self.record;
135 required_if_declared(record, "Identifier")?;
136 required_if_declared(record, "Name")?;
137 if self.identifier()?.is_none() && self.name()?.is_none() {
138 return Err(ApprovalError::Semantic {
139 entity: APPROVAL,
140 id: record.id,
141 rule: "HasIdentifierOrName",
142 detail: "Identifier and Name are both absent".into(),
143 });
144 }
145 match self.time_of_approval() {
146 Ok(_) => {}
147 Err(ApprovalError::StructuredValue { target, .. }) => record.validate_target(
148 view.model(),
149 "TimeOfApproval",
150 target,
151 "IfcDateTimeSelect",
152 )?,
153 Err(error) => return Err(error),
154 }
155 if record.requires("TimeOfApproval") && matches!(self.time_of_approval(), Ok(None)) {
157 return Err(ApprovalError::MissingAttribute {
158 entity: APPROVAL,
159 id: record.id,
160 attribute: "TimeOfApproval",
161 });
162 }
163 for attribute in ["RequestingApproval", "GivingApproval"] {
164 if !record.declares(attribute) {
165 continue;
166 }
167 if let Some(target) = record.optional_ref(attribute)? {
168 record.validate_target(view.model(), attribute, target, "IfcActorSelect")?;
169 }
170 }
171 Ok(self)
172 }
173}
174
175impl<'m> ApprovalRelationship<'m> {
176 pub fn name(self) -> ApprovalResult<Option<&'m str>> {
178 self.record.optional_text("Name")
179 }
180 pub fn description(self) -> ApprovalResult<Option<&'m str>> {
182 self.record.optional_text("Description")
183 }
184 pub fn relating_approval(self) -> ApprovalResult<EntityId> {
186 self.record.required_ref("RelatingApproval")
187 }
188 pub fn related_approvals(self) -> ApprovalResult<Vec<EntityId>> {
191 self.record.required_refs("RelatedApprovals")
192 }
193 fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
194 let record = self.record;
195 required_if_declared(record, "Name")?;
196 let relating = self.relating_approval()?;
197 record.validate_target(view.model(), "RelatingApproval", relating, APPROVAL)?;
198 for target in self.related_approvals()? {
199 if target == relating {
200 return Err(ApprovalError::Semantic {
201 entity: APPROVAL_REL,
202 id: record.id,
203 rule: "NoSelfRelationship",
204 detail: format!("approval {target} appears on both ends"),
205 });
206 }
207 record.validate_target(view.model(), "RelatedApprovals", target, APPROVAL)?;
208 }
209 Ok(self)
210 }
211}
212
213impl<'m> ResourceApprovalRelationship<'m> {
214 pub fn name(self) -> ApprovalResult<Option<&'m str>> {
216 self.record.optional_text("Name")
217 }
218 pub fn description(self) -> ApprovalResult<Option<&'m str>> {
220 self.record.optional_text("Description")
221 }
222 pub fn related_resources(self) -> ApprovalResult<Vec<EntityId>> {
224 self.record.required_refs("RelatedResourceObjects")
225 }
226 pub fn relating_approval(self) -> ApprovalResult<EntityId> {
228 self.record.required_ref("RelatingApproval")
229 }
230 fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
231 let record = self.record;
232 record.validate_target(
233 view.model(),
234 "RelatingApproval",
235 self.relating_approval()?,
236 APPROVAL,
237 )?;
238 for target in self.related_resources()? {
239 record.validate_target(
240 view.model(),
241 "RelatedResourceObjects",
242 target,
243 "IfcResourceObjectSelect",
244 )?;
245 }
246 Ok(self)
247 }
248}
249
250impl<'m> ApprovalAssignment<'m> {
251 pub fn global_id(self) -> ApprovalResult<&'m str> {
253 self.record.required_text("GlobalId")
254 }
255 pub fn name(self) -> ApprovalResult<Option<&'m str>> {
257 self.record.optional_text("Name")
258 }
259 pub fn description(self) -> ApprovalResult<Option<&'m str>> {
261 self.record.optional_text("Description")
262 }
263 pub fn related_objects(self) -> ApprovalResult<Vec<EntityId>> {
265 self.record.required_refs("RelatedObjects")
266 }
267 pub fn relating_approval(self) -> ApprovalResult<EntityId> {
269 self.record.required_ref("RelatingApproval")
270 }
271 fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
272 let record = self.record;
273 if Guid::parse(self.global_id()?).is_none() {
274 return Err(ApprovalError::InvalidValue {
275 entity: ASSIGNMENT,
276 id: record.id,
277 attribute: "GlobalId",
278 value: self.global_id()?.into(),
279 });
280 }
281 record.validate_target(
282 view.model(),
283 "RelatingApproval",
284 self.relating_approval()?,
285 APPROVAL,
286 )?;
287 for target in self.related_objects()? {
288 record.validate_target(
289 view.model(),
290 "RelatedObjects",
291 target,
292 "IfcDefinitionSelect",
293 )?;
294 }
295 Ok(self)
296 }
297}
298
299impl<'m> ApprovalView<'m> {
300 fn entity(self, id: EntityId) -> ApprovalResult<&'m Entity> {
301 self.model()
302 .get(id)
303 .ok_or(ApprovalError::UnknownEntity { id })
304 }
305
306 pub fn approval(self, id: EntityId) -> ApprovalResult<Approval<'m>> {
308 Approval::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
309 }
310 pub fn approval_relationship(self, id: EntityId) -> ApprovalResult<ApprovalRelationship<'m>> {
312 ApprovalRelationship::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
313 }
314 pub fn resource_approval_relationship(
316 self,
317 id: EntityId,
318 ) -> ApprovalResult<ResourceApprovalRelationship<'m>> {
319 ResourceApprovalRelationship::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
320 }
321 pub fn approval_assignment(self, id: EntityId) -> ApprovalResult<ApprovalAssignment<'m>> {
323 ApprovalAssignment::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
324 }
325
326 pub fn resources_approved_by(self, approval: EntityId) -> ApprovalResult<Vec<EntityId>> {
330 self.approval(approval)?;
331 let layout = self.layout()?;
332 let mut out = Vec::new();
333 if layout.require_entity(RESOURCE_REL).is_err() {
334 return Ok(out);
335 }
336 for (id, entity) in self.model().of_type(RESOURCE_REL) {
337 let relationship = ResourceApprovalRelationship::bound(id, entity, layout)?;
338 if relationship.relating_approval()? == approval {
339 out.extend(relationship.validate(self)?.related_resources()?);
340 }
341 }
342 Ok(out)
343 }
344
345 pub fn objects_approved_by(self, approval: EntityId) -> ApprovalResult<Vec<EntityId>> {
347 self.approval(approval)?;
348 let layout = self.layout()?;
349 let mut out = Vec::new();
350 for (id, entity) in self.model().of_type(ASSIGNMENT) {
351 let assignment = ApprovalAssignment::bound(id, entity, layout)?;
352 if assignment.relating_approval()? == approval {
353 out.extend(assignment.validate(self)?.related_objects()?);
354 }
355 }
356 Ok(out)
357 }
358}