use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId};
use crate::view::{
optional_ref, optional_text, required_ref, required_refs, required_text, validate_target,
wrong, ApprovalView,
};
use crate::{ApprovalError, ApprovalResult};
const APPROVAL: &str = "IFCAPPROVAL";
const APPROVAL_REL: &str = "IFCAPPROVALRELATIONSHIP";
const RESOURCE_REL: &str = "IFCRESOURCEAPPROVALRELATIONSHIP";
const ASSIGNMENT: &str = "IFCRELASSOCIATESAPPROVAL";
macro_rules! projection {
($name:ident, $kind:expr) => {
#[derive(Debug, Clone, Copy)]
pub struct $name<'m> {
id: EntityId,
entity: &'m Entity,
}
impl<'m> $name<'m> {
pub fn try_new(id: EntityId, entity: &'m Entity) -> ApprovalResult<Self> {
if entity.is_type($kind) {
Ok(Self { id, entity })
} else {
Err(wrong($kind, entity))
}
}
#[must_use]
pub const fn id(self) -> EntityId {
self.id
}
}
};
}
projection!(Approval, APPROVAL);
projection!(ApprovalRelationship, APPROVAL_REL);
projection!(ResourceApprovalRelationship, RESOURCE_REL);
projection!(ApprovalAssignment, ASSIGNMENT);
impl<'m> Approval<'m> {
pub fn identifier(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL, self.id, self.entity, 0, "Identifier")
}
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL, self.id, self.entity, 1, "Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL, self.id, self.entity, 2, "Description")
}
pub fn time_of_approval(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL, self.id, self.entity, 3, "TimeOfApproval")
}
pub fn status(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL, self.id, self.entity, 4, "Status")
}
pub fn level(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL, self.id, self.entity, 5, "Level")
}
pub fn qualifier(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL, self.id, self.entity, 6, "Qualifier")
}
pub fn requesting_approval(self) -> ApprovalResult<Option<EntityId>> {
optional_ref(APPROVAL, self.id, self.entity, 7, "RequestingApproval")
}
pub fn giving_approval(self) -> ApprovalResult<Option<EntityId>> {
optional_ref(APPROVAL, self.id, self.entity, 8, "GivingApproval")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
if self.identifier()?.is_none() && self.name()?.is_none() {
return Err(ApprovalError::Semantic {
entity: APPROVAL,
id: self.id,
rule: "HasIdentifierOrName",
detail: "Identifier and Name are both absent".into(),
});
}
for (attribute, target) in [
("RequestingApproval", self.requesting_approval()?),
("GivingApproval", self.giving_approval()?),
] {
if let Some(target) = target {
validate_target(
view.model(),
APPROVAL,
self.id,
attribute,
target,
"IfcActorSelect",
)?;
}
}
Ok(self)
}
}
impl<'m> ApprovalRelationship<'m> {
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL_REL, self.id, self.entity, 0, "Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
optional_text(APPROVAL_REL, self.id, self.entity, 1, "Description")
}
pub fn relating_approval(self) -> ApprovalResult<EntityId> {
required_ref(APPROVAL_REL, self.id, self.entity, 2, "RelatingApproval")
}
pub fn related_approvals(self) -> ApprovalResult<Vec<EntityId>> {
required_refs(APPROVAL_REL, self.id, self.entity, 3, "RelatedApprovals")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
let relating = self.relating_approval()?;
validate_target(
view.model(),
APPROVAL_REL,
self.id,
"RelatingApproval",
relating,
APPROVAL,
)?;
for target in self.related_approvals()? {
if target == relating {
return Err(ApprovalError::Semantic {
entity: APPROVAL_REL,
id: self.id,
rule: "NoSelfRelationship",
detail: format!("approval {target} appears on both ends"),
});
}
validate_target(
view.model(),
APPROVAL_REL,
self.id,
"RelatedApprovals",
target,
APPROVAL,
)?;
}
Ok(self)
}
}
impl<'m> ResourceApprovalRelationship<'m> {
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
optional_text(RESOURCE_REL, self.id, self.entity, 0, "Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
optional_text(RESOURCE_REL, self.id, self.entity, 1, "Description")
}
pub fn related_resources(self) -> ApprovalResult<Vec<EntityId>> {
required_refs(
RESOURCE_REL,
self.id,
self.entity,
2,
"RelatedResourceObjects",
)
}
pub fn relating_approval(self) -> ApprovalResult<EntityId> {
required_ref(RESOURCE_REL, self.id, self.entity, 3, "RelatingApproval")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
validate_target(
view.model(),
RESOURCE_REL,
self.id,
"RelatingApproval",
self.relating_approval()?,
APPROVAL,
)?;
for target in self.related_resources()? {
validate_target(
view.model(),
RESOURCE_REL,
self.id,
"RelatedResourceObjects",
target,
"IfcResourceObjectSelect",
)?;
}
Ok(self)
}
}
impl<'m> ApprovalAssignment<'m> {
pub fn global_id(self) -> ApprovalResult<&'m str> {
required_text(ASSIGNMENT, self.id, self.entity, 0, "GlobalId")
}
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
optional_text(ASSIGNMENT, self.id, self.entity, 2, "Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
optional_text(ASSIGNMENT, self.id, self.entity, 3, "Description")
}
pub fn related_objects(self) -> ApprovalResult<Vec<EntityId>> {
required_refs(ASSIGNMENT, self.id, self.entity, 4, "RelatedObjects")
}
pub fn relating_approval(self) -> ApprovalResult<EntityId> {
required_ref(ASSIGNMENT, self.id, self.entity, 5, "RelatingApproval")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
if Guid::parse(self.global_id()?).is_none() {
return Err(ApprovalError::InvalidValue {
entity: ASSIGNMENT,
id: self.id,
attribute: "GlobalId",
value: self.global_id()?.into(),
});
}
validate_target(
view.model(),
ASSIGNMENT,
self.id,
"RelatingApproval",
self.relating_approval()?,
APPROVAL,
)?;
for target in self.related_objects()? {
validate_target(
view.model(),
ASSIGNMENT,
self.id,
"RelatedObjects",
target,
"IfcDefinitionSelect",
)?;
}
Ok(self)
}
}
impl<'m> ApprovalView<'m> {
pub fn approval(self, id: EntityId) -> ApprovalResult<Approval<'m>> {
let entity = self
.model()
.get(id)
.ok_or(ApprovalError::UnknownEntity { id })?;
Approval::try_new(id, entity)?.validate(self)
}
pub fn approval_relationship(self, id: EntityId) -> ApprovalResult<ApprovalRelationship<'m>> {
let entity = self
.model()
.get(id)
.ok_or(ApprovalError::UnknownEntity { id })?;
ApprovalRelationship::try_new(id, entity)?.validate(self)
}
pub fn resource_approval_relationship(
self,
id: EntityId,
) -> ApprovalResult<ResourceApprovalRelationship<'m>> {
let entity = self
.model()
.get(id)
.ok_or(ApprovalError::UnknownEntity { id })?;
ResourceApprovalRelationship::try_new(id, entity)?.validate(self)
}
pub fn approval_assignment(self, id: EntityId) -> ApprovalResult<ApprovalAssignment<'m>> {
let entity = self
.model()
.get(id)
.ok_or(ApprovalError::UnknownEntity { id })?;
ApprovalAssignment::try_new(id, entity)?.validate(self)
}
pub fn resources_approved_by(self, approval: EntityId) -> ApprovalResult<Vec<EntityId>> {
self.approval(approval)?;
let mut out = Vec::new();
for (id, entity) in self.model().of_type(RESOURCE_REL) {
let relationship = ResourceApprovalRelationship { id, entity };
if relationship.relating_approval()? == approval {
out.extend(relationship.validate(self)?.related_resources()?);
}
}
Ok(out)
}
pub fn objects_approved_by(self, approval: EntityId) -> ApprovalResult<Vec<EntityId>> {
self.approval(approval)?;
let mut out = Vec::new();
for (id, entity) in self.model().of_type(ASSIGNMENT) {
let assignment = ApprovalAssignment { id, entity };
if assignment.relating_approval()? == approval {
out.extend(assignment.validate(self)?.related_objects()?);
}
}
Ok(out)
}
}