Skip to main content

ifc_approval/
projection.rs

1//! Strict borrowed projections and deterministic direct queries.
2
3use ifc_model::guid::Guid;
4use ifc_model::{Entity, EntityId};
5
6use crate::view::{
7    optional_ref, optional_text, required_ref, required_refs, required_text, validate_target,
8    wrong, ApprovalView,
9};
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        /// Strict borrowed IFC4 projection.
20        #[derive(Debug, Clone, Copy)]
21        pub struct $name<'m> {
22            id: EntityId,
23            entity: &'m Entity,
24        }
25        impl<'m> $name<'m> {
26            /// Construct from an entity of the exact expected kind.
27            pub fn try_new(id: EntityId, entity: &'m Entity) -> ApprovalResult<Self> {
28                if entity.is_type($kind) {
29                    Ok(Self { id, entity })
30                } else {
31                    Err(wrong($kind, entity))
32                }
33            }
34            /// Stable model identifier.
35            #[must_use]
36            pub const fn id(self) -> EntityId {
37                self.id
38            }
39        }
40    };
41}
42
43projection!(Approval, APPROVAL);
44projection!(ApprovalRelationship, APPROVAL_REL);
45projection!(ResourceApprovalRelationship, RESOURCE_REL);
46projection!(ApprovalAssignment, ASSIGNMENT);
47
48impl<'m> Approval<'m> {
49    /// Optional approval identifier.
50    pub fn identifier(self) -> ApprovalResult<Option<&'m str>> {
51        optional_text(APPROVAL, self.id, self.entity, 0, "Identifier")
52    }
53    /// Optional approval name.
54    pub fn name(self) -> ApprovalResult<Option<&'m str>> {
55        optional_text(APPROVAL, self.id, self.entity, 1, "Name")
56    }
57    /// Optional description.
58    pub fn description(self) -> ApprovalResult<Option<&'m str>> {
59        optional_text(APPROVAL, self.id, self.entity, 2, "Description")
60    }
61    /// Optional authored approval time.
62    pub fn time_of_approval(self) -> ApprovalResult<Option<&'m str>> {
63        optional_text(APPROVAL, self.id, self.entity, 3, "TimeOfApproval")
64    }
65    /// Optional status label.
66    pub fn status(self) -> ApprovalResult<Option<&'m str>> {
67        optional_text(APPROVAL, self.id, self.entity, 4, "Status")
68    }
69    /// Optional approval level label.
70    pub fn level(self) -> ApprovalResult<Option<&'m str>> {
71        optional_text(APPROVAL, self.id, self.entity, 5, "Level")
72    }
73    /// Optional qualifier text.
74    pub fn qualifier(self) -> ApprovalResult<Option<&'m str>> {
75        optional_text(APPROVAL, self.id, self.entity, 6, "Qualifier")
76    }
77    /// Optional requesting actor-select reference.
78    pub fn requesting_approval(self) -> ApprovalResult<Option<EntityId>> {
79        optional_ref(APPROVAL, self.id, self.entity, 7, "RequestingApproval")
80    }
81    /// Optional giving actor-select reference.
82    pub fn giving_approval(self) -> ApprovalResult<Option<EntityId>> {
83        optional_ref(APPROVAL, self.id, self.entity, 8, "GivingApproval")
84    }
85
86    fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
87        if self.identifier()?.is_none() && self.name()?.is_none() {
88            return Err(ApprovalError::Semantic {
89                entity: APPROVAL,
90                id: self.id,
91                rule: "HasIdentifierOrName",
92                detail: "Identifier and Name are both absent".into(),
93            });
94        }
95        for (attribute, target) in [
96            ("RequestingApproval", self.requesting_approval()?),
97            ("GivingApproval", self.giving_approval()?),
98        ] {
99            if let Some(target) = target {
100                validate_target(
101                    view.model(),
102                    APPROVAL,
103                    self.id,
104                    attribute,
105                    target,
106                    "IfcActorSelect",
107                )?;
108            }
109        }
110        Ok(self)
111    }
112}
113
114impl<'m> ApprovalRelationship<'m> {
115    /// Optional relationship name.
116    pub fn name(self) -> ApprovalResult<Option<&'m str>> {
117        optional_text(APPROVAL_REL, self.id, self.entity, 0, "Name")
118    }
119    /// Optional relationship description.
120    pub fn description(self) -> ApprovalResult<Option<&'m str>> {
121        optional_text(APPROVAL_REL, self.id, self.entity, 1, "Description")
122    }
123    /// Relating approval.
124    pub fn relating_approval(self) -> ApprovalResult<EntityId> {
125        required_ref(APPROVAL_REL, self.id, self.entity, 2, "RelatingApproval")
126    }
127    /// Non-empty unique related approvals.
128    pub fn related_approvals(self) -> ApprovalResult<Vec<EntityId>> {
129        required_refs(APPROVAL_REL, self.id, self.entity, 3, "RelatedApprovals")
130    }
131    fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
132        let relating = self.relating_approval()?;
133        validate_target(
134            view.model(),
135            APPROVAL_REL,
136            self.id,
137            "RelatingApproval",
138            relating,
139            APPROVAL,
140        )?;
141        for target in self.related_approvals()? {
142            if target == relating {
143                return Err(ApprovalError::Semantic {
144                    entity: APPROVAL_REL,
145                    id: self.id,
146                    rule: "NoSelfRelationship",
147                    detail: format!("approval {target} appears on both ends"),
148                });
149            }
150            validate_target(
151                view.model(),
152                APPROVAL_REL,
153                self.id,
154                "RelatedApprovals",
155                target,
156                APPROVAL,
157            )?;
158        }
159        Ok(self)
160    }
161}
162
163impl<'m> ResourceApprovalRelationship<'m> {
164    /// Optional relationship name.
165    pub fn name(self) -> ApprovalResult<Option<&'m str>> {
166        optional_text(RESOURCE_REL, self.id, self.entity, 0, "Name")
167    }
168    /// Optional relationship description.
169    pub fn description(self) -> ApprovalResult<Option<&'m str>> {
170        optional_text(RESOURCE_REL, self.id, self.entity, 1, "Description")
171    }
172    /// Non-empty unique resource-select targets.
173    pub fn related_resources(self) -> ApprovalResult<Vec<EntityId>> {
174        required_refs(
175            RESOURCE_REL,
176            self.id,
177            self.entity,
178            2,
179            "RelatedResourceObjects",
180        )
181    }
182    /// Approval governing the resources.
183    pub fn relating_approval(self) -> ApprovalResult<EntityId> {
184        required_ref(RESOURCE_REL, self.id, self.entity, 3, "RelatingApproval")
185    }
186    fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
187        validate_target(
188            view.model(),
189            RESOURCE_REL,
190            self.id,
191            "RelatingApproval",
192            self.relating_approval()?,
193            APPROVAL,
194        )?;
195        for target in self.related_resources()? {
196            validate_target(
197                view.model(),
198                RESOURCE_REL,
199                self.id,
200                "RelatedResourceObjects",
201                target,
202                "IfcResourceObjectSelect",
203            )?;
204        }
205        Ok(self)
206    }
207}
208
209impl<'m> ApprovalAssignment<'m> {
210    /// Root GlobalId.
211    pub fn global_id(self) -> ApprovalResult<&'m str> {
212        required_text(ASSIGNMENT, self.id, self.entity, 0, "GlobalId")
213    }
214    /// Optional relationship name.
215    pub fn name(self) -> ApprovalResult<Option<&'m str>> {
216        optional_text(ASSIGNMENT, self.id, self.entity, 2, "Name")
217    }
218    /// Optional relationship description.
219    pub fn description(self) -> ApprovalResult<Option<&'m str>> {
220        optional_text(ASSIGNMENT, self.id, self.entity, 3, "Description")
221    }
222    /// Related definition-select targets.
223    pub fn related_objects(self) -> ApprovalResult<Vec<EntityId>> {
224        required_refs(ASSIGNMENT, self.id, self.entity, 4, "RelatedObjects")
225    }
226    /// Relating approval.
227    pub fn relating_approval(self) -> ApprovalResult<EntityId> {
228        required_ref(ASSIGNMENT, self.id, self.entity, 5, "RelatingApproval")
229    }
230    fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
231        if Guid::parse(self.global_id()?).is_none() {
232            return Err(ApprovalError::InvalidValue {
233                entity: ASSIGNMENT,
234                id: self.id,
235                attribute: "GlobalId",
236                value: self.global_id()?.into(),
237            });
238        }
239        validate_target(
240            view.model(),
241            ASSIGNMENT,
242            self.id,
243            "RelatingApproval",
244            self.relating_approval()?,
245            APPROVAL,
246        )?;
247        for target in self.related_objects()? {
248            validate_target(
249                view.model(),
250                ASSIGNMENT,
251                self.id,
252                "RelatedObjects",
253                target,
254                "IfcDefinitionSelect",
255            )?;
256        }
257        Ok(self)
258    }
259}
260
261impl<'m> ApprovalView<'m> {
262    /// Strictly project one approval.
263    pub fn approval(self, id: EntityId) -> ApprovalResult<Approval<'m>> {
264        let entity = self
265            .model()
266            .get(id)
267            .ok_or(ApprovalError::UnknownEntity { id })?;
268        Approval::try_new(id, entity)?.validate(self)
269    }
270    /// Strictly project one approval-to-approval relationship.
271    pub fn approval_relationship(self, id: EntityId) -> ApprovalResult<ApprovalRelationship<'m>> {
272        let entity = self
273            .model()
274            .get(id)
275            .ok_or(ApprovalError::UnknownEntity { id })?;
276        ApprovalRelationship::try_new(id, entity)?.validate(self)
277    }
278    /// Strictly project one resource approval relationship.
279    pub fn resource_approval_relationship(
280        self,
281        id: EntityId,
282    ) -> ApprovalResult<ResourceApprovalRelationship<'m>> {
283        let entity = self
284            .model()
285            .get(id)
286            .ok_or(ApprovalError::UnknownEntity { id })?;
287        ResourceApprovalRelationship::try_new(id, entity)?.validate(self)
288    }
289    /// Strictly project one rooted object approval association.
290    pub fn approval_assignment(self, id: EntityId) -> ApprovalResult<ApprovalAssignment<'m>> {
291        let entity = self
292            .model()
293            .get(id)
294            .ok_or(ApprovalError::UnknownEntity { id })?;
295        ApprovalAssignment::try_new(id, entity)?.validate(self)
296    }
297
298    /// Resource-select IDs directly governed by an approval.
299    pub fn resources_approved_by(self, approval: EntityId) -> ApprovalResult<Vec<EntityId>> {
300        self.approval(approval)?;
301        let mut out = Vec::new();
302        for (id, entity) in self.model().of_type(RESOURCE_REL) {
303            let relationship = ResourceApprovalRelationship { id, entity };
304            if relationship.relating_approval()? == approval {
305                out.extend(relationship.validate(self)?.related_resources()?);
306            }
307        }
308        Ok(out)
309    }
310
311    /// Definition-select IDs directly associated with an approval.
312    pub fn objects_approved_by(self, approval: EntityId) -> ApprovalResult<Vec<EntityId>> {
313        self.approval(approval)?;
314        let mut out = Vec::new();
315        for (id, entity) in self.model().of_type(ASSIGNMENT) {
316            let assignment = ApprovalAssignment { id, entity };
317            if assignment.relating_approval()? == approval {
318                out.extend(assignment.validate(self)?.related_objects()?);
319            }
320        }
321        Ok(out)
322    }
323}