use ifc_model::guid::Guid;
use ifc_model::{EntityId, Model, Transaction, Value};
use super::{optional_text, refs, require_accepts, text, AssociationDraft};
use crate::release::Release;
use crate::{ClassificationError, ClassificationResult};
fn require_definition(
tx: &Transaction,
model: &Model,
release: Release<'_>,
relation: &'static str,
object: EntityId,
) -> ClassificationResult<()> {
let declared = release.declared(relation, "RelatedObjects")?;
let actual = super::final_type(tx, model, object)
.ok_or(ClassificationError::UnknownEntity { id: object })?;
let (_, schema) = release.bound()?;
let definition =
schema.is_a(actual, "IFCOBJECTDEFINITION") || schema.is_a(actual, "IFCPROPERTYDEFINITION");
if definition && schema.accepts_type(&declared.type_name, actual) {
Ok(())
} else {
Err(ClassificationError::AuthoringReferenceType {
target: object,
expected: declared.type_name.as_str(),
actual: actual.to_owned(),
})
}
}
fn associate(
tx: &mut Transaction,
model: &Model,
draft: AssociationDraft<'_>,
target: EntityId,
kind: &'static str,
target_attribute: &'static str,
owner_history: Option<EntityId>,
) -> ClassificationResult<EntityId> {
let release = Release::of(model);
release.require_entity(kind)?;
if Guid::parse(draft.global_id).is_none() {
return Err(ClassificationError::AuthoringInvalid {
entity: kind,
attribute: "GlobalId",
value: draft.global_id.into(),
});
}
if draft.related_objects.is_empty() {
return Err(ClassificationError::AuthoringInvalid {
entity: kind,
attribute: "RelatedObjects",
value: "empty SET [1:?]".into(),
});
}
let mut seen = std::collections::HashSet::new();
for &object in draft.related_objects {
if !seen.insert(object) {
return Err(ClassificationError::AuthoringInvalid {
entity: kind,
attribute: "RelatedObjects",
value: format!("duplicate {object}"),
});
}
require_definition(tx, model, release, kind, object)?;
}
require_accepts(tx, model, release, kind, target_attribute, target)?;
if let Some(owner_history) = owner_history {
require_accepts(tx, model, release, kind, "OwnerHistory", owner_history)?;
}
let record = release.record(
kind,
vec![
("GlobalId", text(draft.global_id)),
(
"OwnerHistory",
owner_history.map_or(Value::Null, Value::Ref),
),
("Name", optional_text(draft.name)),
("Description", optional_text(draft.description)),
("RelatedObjects", refs(draft.related_objects)),
(target_attribute, Value::Ref(target)),
],
)?;
Ok(tx.create(record))
}
const CLASSIFICATION: (&str, &str) = ("IFCRELASSOCIATESCLASSIFICATION", "RelatingClassification");
const DOCUMENT: (&str, &str) = ("IFCRELASSOCIATESDOCUMENT", "RelatingDocument");
const LIBRARY: (&str, &str) = ("IFCRELASSOCIATESLIBRARY", "RelatingLibrary");
pub fn associate_classification(
tx: &mut Transaction,
model: &Model,
draft: AssociationDraft<'_>,
target: EntityId,
) -> ClassificationResult<EntityId> {
let (kind, attribute) = CLASSIFICATION;
associate(tx, model, draft, target, kind, attribute, None)
}
pub fn associate_classification_with_owner_history(
tx: &mut Transaction,
model: &Model,
draft: AssociationDraft<'_>,
target: EntityId,
owner_history: EntityId,
) -> ClassificationResult<EntityId> {
let (kind, attribute) = CLASSIFICATION;
associate(
tx,
model,
draft,
target,
kind,
attribute,
Some(owner_history),
)
}
pub fn associate_document(
tx: &mut Transaction,
model: &Model,
draft: AssociationDraft<'_>,
target: EntityId,
) -> ClassificationResult<EntityId> {
let (kind, attribute) = DOCUMENT;
associate(tx, model, draft, target, kind, attribute, None)
}
pub fn associate_document_with_owner_history(
tx: &mut Transaction,
model: &Model,
draft: AssociationDraft<'_>,
target: EntityId,
owner_history: EntityId,
) -> ClassificationResult<EntityId> {
let (kind, attribute) = DOCUMENT;
associate(
tx,
model,
draft,
target,
kind,
attribute,
Some(owner_history),
)
}
pub fn associate_library(
tx: &mut Transaction,
model: &Model,
draft: AssociationDraft<'_>,
target: EntityId,
) -> ClassificationResult<EntityId> {
let (kind, attribute) = LIBRARY;
associate(tx, model, draft, target, kind, attribute, None)
}
pub fn associate_library_with_owner_history(
tx: &mut Transaction,
model: &Model,
draft: AssociationDraft<'_>,
target: EntityId,
owner_history: EntityId,
) -> ClassificationResult<EntityId> {
let (kind, attribute) = LIBRARY;
associate(
tx,
model,
draft,
target,
kind,
attribute,
Some(owner_history),
)
}
pub fn relate_documents(
tx: &mut Transaction,
model: &Model,
relating: EntityId,
related: &[EntityId],
relationship_type: Option<&str>,
) -> ClassificationResult<EntityId> {
const ENTITY: &str = "IFCDOCUMENTINFORMATIONRELATIONSHIP";
let release = Release::of(model);
release.require_entity(ENTITY)?;
require_accepts(tx, model, release, ENTITY, "RelatingDocument", relating)?;
if related.is_empty() {
return Err(ClassificationError::AuthoringInvalid {
entity: ENTITY,
attribute: "RelatedDocuments",
value: "expected at least one document, per SET [1:?]".to_owned(),
});
}
for document in related {
require_accepts(tx, model, release, ENTITY, "RelatedDocuments", *document)?;
if *document == relating {
return Err(ClassificationError::AuthoringInvalid {
entity: ENTITY,
attribute: "RelatedDocuments",
value: "expected a document other than the relating one".to_owned(),
});
}
}
let record = release.record(
ENTITY,
vec![
("RelatingDocument", Value::Ref(relating)),
("RelatedDocuments", refs(related)),
("RelationshipType", optional_text(relationship_type)),
],
)?;
Ok(tx.create(record))
}