1use ifc_model::guid::Guid;
21use ifc_model::{EntityId, Model, Transaction, Value};
22
23use super::{optional_text, refs, require_accepts, text, AssociationDraft};
24use crate::release::Release;
25use crate::{ClassificationError, ClassificationResult};
26
27fn require_definition(
31 tx: &Transaction,
32 model: &Model,
33 release: Release<'_>,
34 relation: &'static str,
35 object: EntityId,
36) -> ClassificationResult<()> {
37 let declared = release.declared(relation, "RelatedObjects")?;
38 let actual = super::final_type(tx, model, object)
39 .ok_or(ClassificationError::UnknownEntity { id: object })?;
40 let (_, schema) = release.bound()?;
41 let definition =
42 schema.is_a(actual, "IFCOBJECTDEFINITION") || schema.is_a(actual, "IFCPROPERTYDEFINITION");
43 if definition && schema.accepts_type(&declared.type_name, actual) {
44 Ok(())
45 } else {
46 Err(ClassificationError::AuthoringReferenceType {
47 target: object,
48 expected: declared.type_name.as_str(),
49 actual: actual.to_owned(),
50 })
51 }
52}
53
54fn associate(
55 tx: &mut Transaction,
56 model: &Model,
57 draft: AssociationDraft<'_>,
58 target: EntityId,
59 kind: &'static str,
60 target_attribute: &'static str,
61 owner_history: Option<EntityId>,
62) -> ClassificationResult<EntityId> {
63 let release = Release::of(model);
64 release.require_entity(kind)?;
65 if Guid::parse(draft.global_id).is_none() {
66 return Err(ClassificationError::AuthoringInvalid {
67 entity: kind,
68 attribute: "GlobalId",
69 value: draft.global_id.into(),
70 });
71 }
72 if draft.related_objects.is_empty() {
73 return Err(ClassificationError::AuthoringInvalid {
74 entity: kind,
75 attribute: "RelatedObjects",
76 value: "empty SET [1:?]".into(),
77 });
78 }
79 let mut seen = std::collections::HashSet::new();
80 for &object in draft.related_objects {
81 if !seen.insert(object) {
82 return Err(ClassificationError::AuthoringInvalid {
83 entity: kind,
84 attribute: "RelatedObjects",
85 value: format!("duplicate {object}"),
86 });
87 }
88 require_definition(tx, model, release, kind, object)?;
89 }
90 require_accepts(tx, model, release, kind, target_attribute, target)?;
91 if let Some(owner_history) = owner_history {
92 require_accepts(tx, model, release, kind, "OwnerHistory", owner_history)?;
93 }
94 let record = release.record(
95 kind,
96 vec![
97 ("GlobalId", text(draft.global_id)),
98 (
99 "OwnerHistory",
100 owner_history.map_or(Value::Null, Value::Ref),
101 ),
102 ("Name", optional_text(draft.name)),
103 ("Description", optional_text(draft.description)),
104 ("RelatedObjects", refs(draft.related_objects)),
105 (target_attribute, Value::Ref(target)),
106 ],
107 )?;
108 Ok(tx.create(record))
109}
110
111const CLASSIFICATION: (&str, &str) = ("IFCRELASSOCIATESCLASSIFICATION", "RelatingClassification");
112const DOCUMENT: (&str, &str) = ("IFCRELASSOCIATESDOCUMENT", "RelatingDocument");
113const LIBRARY: (&str, &str) = ("IFCRELASSOCIATESLIBRARY", "RelatingLibrary");
114
115pub fn associate_classification(
129 tx: &mut Transaction,
130 model: &Model,
131 draft: AssociationDraft<'_>,
132 target: EntityId,
133) -> ClassificationResult<EntityId> {
134 let (kind, attribute) = CLASSIFICATION;
135 associate(tx, model, draft, target, kind, attribute, None)
136}
137
138pub fn associate_classification_with_owner_history(
147 tx: &mut Transaction,
148 model: &Model,
149 draft: AssociationDraft<'_>,
150 target: EntityId,
151 owner_history: EntityId,
152) -> ClassificationResult<EntityId> {
153 let (kind, attribute) = CLASSIFICATION;
154 associate(
155 tx,
156 model,
157 draft,
158 target,
159 kind,
160 attribute,
161 Some(owner_history),
162 )
163}
164
165pub fn associate_document(
175 tx: &mut Transaction,
176 model: &Model,
177 draft: AssociationDraft<'_>,
178 target: EntityId,
179) -> ClassificationResult<EntityId> {
180 let (kind, attribute) = DOCUMENT;
181 associate(tx, model, draft, target, kind, attribute, None)
182}
183
184pub fn associate_document_with_owner_history(
192 tx: &mut Transaction,
193 model: &Model,
194 draft: AssociationDraft<'_>,
195 target: EntityId,
196 owner_history: EntityId,
197) -> ClassificationResult<EntityId> {
198 let (kind, attribute) = DOCUMENT;
199 associate(
200 tx,
201 model,
202 draft,
203 target,
204 kind,
205 attribute,
206 Some(owner_history),
207 )
208}
209
210pub fn associate_library(
220 tx: &mut Transaction,
221 model: &Model,
222 draft: AssociationDraft<'_>,
223 target: EntityId,
224) -> ClassificationResult<EntityId> {
225 let (kind, attribute) = LIBRARY;
226 associate(tx, model, draft, target, kind, attribute, None)
227}
228
229pub fn associate_library_with_owner_history(
237 tx: &mut Transaction,
238 model: &Model,
239 draft: AssociationDraft<'_>,
240 target: EntityId,
241 owner_history: EntityId,
242) -> ClassificationResult<EntityId> {
243 let (kind, attribute) = LIBRARY;
244 associate(
245 tx,
246 model,
247 draft,
248 target,
249 kind,
250 attribute,
251 Some(owner_history),
252 )
253}
254
255pub fn relate_documents(
270 tx: &mut Transaction,
271 model: &Model,
272 relating: EntityId,
273 related: &[EntityId],
274 relationship_type: Option<&str>,
275) -> ClassificationResult<EntityId> {
276 const ENTITY: &str = "IFCDOCUMENTINFORMATIONRELATIONSHIP";
277 let release = Release::of(model);
278 release.require_entity(ENTITY)?;
279 require_accepts(tx, model, release, ENTITY, "RelatingDocument", relating)?;
280 if related.is_empty() {
281 return Err(ClassificationError::AuthoringInvalid {
282 entity: ENTITY,
283 attribute: "RelatedDocuments",
284 value: "expected at least one document, per SET [1:?]".to_owned(),
285 });
286 }
287 for document in related {
288 require_accepts(tx, model, release, ENTITY, "RelatedDocuments", *document)?;
289 if *document == relating {
291 return Err(ClassificationError::AuthoringInvalid {
292 entity: ENTITY,
293 attribute: "RelatedDocuments",
294 value: "expected a document other than the relating one".to_owned(),
295 });
296 }
297 }
298 let record = release.record(
299 ENTITY,
300 vec![
301 ("RelatingDocument", Value::Ref(relating)),
302 ("RelatedDocuments", refs(related)),
303 ("RelationshipType", optional_text(relationship_type)),
304 ],
305 )?;
306 Ok(tx.create(record))
307}