use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId};
use ifc_schema::SchemaVersion;
use crate::release::Layout;
use crate::view::{wrong, ApprovalView, Record};
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> {
record: Record<'m>,
}
impl<'m> $name<'m> {
pub fn try_new(
id: EntityId,
entity: &'m Entity,
release: SchemaVersion,
) -> ApprovalResult<Self> {
Self::bound(id, entity, Layout::of_version(release)?)
}
pub(crate) fn bound(
id: EntityId,
entity: &'m Entity,
layout: Layout,
) -> ApprovalResult<Self> {
if !entity.is_type($kind) {
return Err(wrong($kind, entity));
}
layout.require_entity($kind)?;
Ok(Self {
record: Record {
kind: $kind,
id,
entity,
layout,
},
})
}
#[must_use]
pub const fn id(self) -> EntityId {
self.record.id
}
#[must_use]
pub const fn release(self) -> SchemaVersion {
self.record.layout.version()
}
}
};
}
projection!(Approval, APPROVAL);
projection!(ApprovalRelationship, APPROVAL_REL);
projection!(ResourceApprovalRelationship, RESOURCE_REL);
projection!(ApprovalAssignment, ASSIGNMENT);
fn required_if_declared(record: Record<'_>, attribute: &'static str) -> ApprovalResult<()> {
if record.requires(attribute) {
record.required_text(attribute)?;
}
Ok(())
}
impl<'m> Approval<'m> {
pub fn identifier(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Identifier")
}
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Description")
}
pub fn time_of_approval(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("TimeOfApproval")
}
pub fn status(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Status")
}
pub fn level(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Level")
}
pub fn qualifier(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Qualifier")
}
pub fn requesting_approval(self) -> ApprovalResult<Option<EntityId>> {
self.record.optional_ref("RequestingApproval")
}
pub fn giving_approval(self) -> ApprovalResult<Option<EntityId>> {
self.record.optional_ref("GivingApproval")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
let record = self.record;
required_if_declared(record, "Identifier")?;
required_if_declared(record, "Name")?;
if self.identifier()?.is_none() && self.name()?.is_none() {
return Err(ApprovalError::Semantic {
entity: APPROVAL,
id: record.id,
rule: "HasIdentifierOrName",
detail: "Identifier and Name are both absent".into(),
});
}
match self.time_of_approval() {
Ok(_) => {}
Err(ApprovalError::StructuredValue { target, .. }) => record.validate_target(
view.model(),
"TimeOfApproval",
target,
"IfcDateTimeSelect",
)?,
Err(error) => return Err(error),
}
if record.requires("TimeOfApproval") && matches!(self.time_of_approval(), Ok(None)) {
return Err(ApprovalError::MissingAttribute {
entity: APPROVAL,
id: record.id,
attribute: "TimeOfApproval",
});
}
for attribute in ["RequestingApproval", "GivingApproval"] {
if !record.declares(attribute) {
continue;
}
if let Some(target) = record.optional_ref(attribute)? {
record.validate_target(view.model(), attribute, target, "IfcActorSelect")?;
}
}
Ok(self)
}
}
impl<'m> ApprovalRelationship<'m> {
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Description")
}
pub fn relating_approval(self) -> ApprovalResult<EntityId> {
self.record.required_ref("RelatingApproval")
}
pub fn related_approvals(self) -> ApprovalResult<Vec<EntityId>> {
self.record.required_refs("RelatedApprovals")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
let record = self.record;
required_if_declared(record, "Name")?;
let relating = self.relating_approval()?;
record.validate_target(view.model(), "RelatingApproval", relating, APPROVAL)?;
for target in self.related_approvals()? {
if target == relating {
return Err(ApprovalError::Semantic {
entity: APPROVAL_REL,
id: record.id,
rule: "NoSelfRelationship",
detail: format!("approval {target} appears on both ends"),
});
}
record.validate_target(view.model(), "RelatedApprovals", target, APPROVAL)?;
}
Ok(self)
}
}
impl<'m> ResourceApprovalRelationship<'m> {
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Description")
}
pub fn related_resources(self) -> ApprovalResult<Vec<EntityId>> {
self.record.required_refs("RelatedResourceObjects")
}
pub fn relating_approval(self) -> ApprovalResult<EntityId> {
self.record.required_ref("RelatingApproval")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
let record = self.record;
record.validate_target(
view.model(),
"RelatingApproval",
self.relating_approval()?,
APPROVAL,
)?;
for target in self.related_resources()? {
record.validate_target(
view.model(),
"RelatedResourceObjects",
target,
"IfcResourceObjectSelect",
)?;
}
Ok(self)
}
}
impl<'m> ApprovalAssignment<'m> {
pub fn global_id(self) -> ApprovalResult<&'m str> {
self.record.required_text("GlobalId")
}
pub fn name(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Name")
}
pub fn description(self) -> ApprovalResult<Option<&'m str>> {
self.record.optional_text("Description")
}
pub fn related_objects(self) -> ApprovalResult<Vec<EntityId>> {
self.record.required_refs("RelatedObjects")
}
pub fn relating_approval(self) -> ApprovalResult<EntityId> {
self.record.required_ref("RelatingApproval")
}
fn validate(self, view: ApprovalView<'m>) -> ApprovalResult<Self> {
let record = self.record;
if Guid::parse(self.global_id()?).is_none() {
return Err(ApprovalError::InvalidValue {
entity: ASSIGNMENT,
id: record.id,
attribute: "GlobalId",
value: self.global_id()?.into(),
});
}
record.validate_target(
view.model(),
"RelatingApproval",
self.relating_approval()?,
APPROVAL,
)?;
for target in self.related_objects()? {
record.validate_target(
view.model(),
"RelatedObjects",
target,
"IfcDefinitionSelect",
)?;
}
Ok(self)
}
}
impl<'m> ApprovalView<'m> {
fn entity(self, id: EntityId) -> ApprovalResult<&'m Entity> {
self.model()
.get(id)
.ok_or(ApprovalError::UnknownEntity { id })
}
pub fn approval(self, id: EntityId) -> ApprovalResult<Approval<'m>> {
Approval::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
}
pub fn approval_relationship(self, id: EntityId) -> ApprovalResult<ApprovalRelationship<'m>> {
ApprovalRelationship::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
}
pub fn resource_approval_relationship(
self,
id: EntityId,
) -> ApprovalResult<ResourceApprovalRelationship<'m>> {
ResourceApprovalRelationship::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
}
pub fn approval_assignment(self, id: EntityId) -> ApprovalResult<ApprovalAssignment<'m>> {
ApprovalAssignment::bound(id, self.entity(id)?, self.layout()?)?.validate(self)
}
pub fn resources_approved_by(self, approval: EntityId) -> ApprovalResult<Vec<EntityId>> {
self.approval(approval)?;
let layout = self.layout()?;
let mut out = Vec::new();
if layout.require_entity(RESOURCE_REL).is_err() {
return Ok(out);
}
for (id, entity) in self.model().of_type(RESOURCE_REL) {
let relationship = ResourceApprovalRelationship::bound(id, entity, layout)?;
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 layout = self.layout()?;
let mut out = Vec::new();
for (id, entity) in self.model().of_type(ASSIGNMENT) {
let assignment = ApprovalAssignment::bound(id, entity, layout)?;
if assignment.relating_approval()? == approval {
out.extend(assignment.validate(self)?.related_objects()?);
}
}
Ok(out)
}
}