Skip to main content

ifc_classification/authoring/
records.rs

1//! Classification, document and library records, laid out by name in the
2//! bound release.
3
4use std::sync::Arc;
5
6use ifc_model::{EntityId, Model, Transaction, Value};
7
8use super::{
9    optional_ref, optional_text, refs, require_accepts, require_enum, require_external_identity,
10    require_set, text, ClassificationDraft, ClassificationReferenceDraft, DocumentDraft,
11    DocumentReferenceDraft, LibraryDraft, LibraryReferenceDraft,
12};
13use crate::release::Release;
14use crate::{ClassificationError, ClassificationResult};
15
16fn optional_enum(value: Option<&str>) -> Value {
17    value.map_or(Value::Null, |v| Value::Enum(Arc::from(v)))
18}
19
20/// Validate and stage one `IfcClassification` in the IFC4 layout; fails if
21/// `reference_tokens` is `Some` and empty.
22///
23/// Takes no model, so it cannot see the model's release: it writes the IFC4
24/// ADD2 layout, which IFC4X3 ADD2 shares position for position (IFC4X3
25/// names the sixth attribute `Specification`). For an IFC2X3 model, or to
26/// have the header's release checked, use [`create_classification_in`].
27pub fn create_classification(
28    tx: &mut Transaction,
29    draft: ClassificationDraft<'_>,
30) -> ClassificationResult<EntityId> {
31    stage_classification(tx, Release::LEGACY, draft)
32}
33
34/// Validate and stage one `IfcClassification` in the layout of `model`'s
35/// declared release.
36///
37/// # Errors
38///
39/// Those of [`create_classification`], and for IFC2X3:
40/// `AuthoringRequired` without `source` or `edition`, `AuthoringValueType`
41/// for an `edition_date` (an `IfcCalendarDate` record there), and
42/// `AuthoringNotInSchema` for a `description`, `location` or
43/// `reference_tokens`. `MultipleSchemas` or `UnsupportedSchema` when the
44/// header binds no single known release. Nothing is staged on an error.
45pub fn create_classification_in(
46    tx: &mut Transaction,
47    model: &Model,
48    draft: ClassificationDraft<'_>,
49) -> ClassificationResult<EntityId> {
50    stage_classification(tx, Release::of(model), draft)
51}
52
53fn stage_classification(
54    tx: &mut Transaction,
55    release: Release<'_>,
56    draft: ClassificationDraft<'_>,
57) -> ClassificationResult<EntityId> {
58    const ENTITY: &str = "IFCCLASSIFICATION";
59    release.require_entity(ENTITY)?;
60    if draft
61        .reference_tokens
62        .is_some_and(|tokens| tokens.is_empty())
63    {
64        return Err(ClassificationError::AuthoringInvalid {
65            entity: ENTITY,
66            attribute: "ReferenceTokens",
67            value: "empty LIST [1:?]".into(),
68        });
69    }
70    let tokens = draft.reference_tokens.map_or(Value::Null, |items| {
71        Value::List(items.iter().map(|v| text(v)).collect())
72    });
73    let record = release.record(
74        ENTITY,
75        vec![
76            ("Source", optional_text(draft.source)),
77            ("Edition", optional_text(draft.edition)),
78            ("EditionDate", optional_text(draft.edition_date)),
79            ("Name", text(draft.name)),
80            ("Description", optional_text(draft.description)),
81            ("Location", optional_text(draft.location)),
82            ("ReferenceTokens", tokens),
83        ],
84    )?;
85    Ok(tx.create(record))
86}
87
88/// Validate and stage one `IfcClassificationReference` in the layout of
89/// `model`'s declared release.
90///
91/// # Errors
92///
93/// Location, identification and name all unstated (WR1); a
94/// `referenced_source` the release does not accept
95/// (`IfcClassificationReferenceSelect`, or only `IfcClassification` in
96/// IFC2X3); a `description` or `sort` in IFC2X3 (`AuthoringNotInSchema`);
97/// and a header binding no single known release. Nothing is staged on an
98/// error.
99pub fn create_classification_reference(
100    tx: &mut Transaction,
101    model: &Model,
102    draft: ClassificationReferenceDraft<'_>,
103) -> ClassificationResult<EntityId> {
104    const ENTITY: &str = "IFCCLASSIFICATIONREFERENCE";
105    let release = Release::of(model);
106    release.require_entity(ENTITY)?;
107    require_external_identity(ENTITY, draft.location, draft.identification, draft.name)?;
108    if let Some(source) = draft.referenced_source {
109        require_accepts(tx, model, release, ENTITY, "ReferencedSource", source)?;
110    }
111    let record = release.record(
112        ENTITY,
113        vec![
114            ("Location", optional_text(draft.location)),
115            ("Identification", optional_text(draft.identification)),
116            ("Name", optional_text(draft.name)),
117            ("ReferencedSource", optional_ref(draft.referenced_source)),
118            ("Description", optional_text(draft.description)),
119            ("Sort", optional_text(draft.sort)),
120        ],
121    )?;
122    Ok(tx.create(record))
123}
124
125/// Validate and stage one `IfcDocumentInformation` in the layout of
126/// `model`'s declared release.
127///
128/// # Errors
129///
130/// A `Confidentiality`/`Status` value outside the release's enumeration; a
131/// `document_owner`/`editors` entry that is not an `IfcActorSelect`; a
132/// duplicate or empty `editors` set; for IFC2X3, a `location`
133/// (`AuthoringNotInSchema`) or a text date, time or electronic format, which
134/// IFC2X3 types as records (`AuthoringValueType`); and a header binding no
135/// single known release. Nothing is staged on an error.
136pub fn create_document(
137    tx: &mut Transaction,
138    model: &Model,
139    draft: DocumentDraft<'_>,
140) -> ClassificationResult<EntityId> {
141    const ENTITY: &str = "IFCDOCUMENTINFORMATION";
142    let release = Release::of(model);
143    release.require_entity(ENTITY)?;
144    require_enum(release, ENTITY, "Confidentiality", draft.confidentiality)?;
145    require_enum(release, ENTITY, "Status", draft.status)?;
146    if let Some(owner) = draft.document_owner {
147        require_accepts(tx, model, release, ENTITY, "DocumentOwner", owner)?;
148    }
149    if let Some(editors) = draft.editors {
150        require_set(tx, model, release, ENTITY, "Editors", editors)?;
151    }
152    let record = release.record(
153        ENTITY,
154        vec![
155            ("Identification", text(draft.identification)),
156            ("Name", text(draft.name)),
157            ("Description", optional_text(draft.description)),
158            ("Location", optional_text(draft.location)),
159            ("Purpose", optional_text(draft.purpose)),
160            ("IntendedUse", optional_text(draft.intended_use)),
161            ("Scope", optional_text(draft.scope)),
162            ("Revision", optional_text(draft.revision)),
163            ("DocumentOwner", optional_ref(draft.document_owner)),
164            ("Editors", draft.editors.map_or(Value::Null, refs)),
165            ("CreationTime", optional_text(draft.creation_time)),
166            ("LastRevisionTime", optional_text(draft.last_revision_time)),
167            ("ElectronicFormat", optional_text(draft.electronic_format)),
168            ("ValidFrom", optional_text(draft.valid_from)),
169            ("ValidUntil", optional_text(draft.valid_until)),
170            ("Confidentiality", optional_enum(draft.confidentiality)),
171            ("Status", optional_enum(draft.status)),
172        ],
173    )?;
174    Ok(tx.create(record))
175}
176
177/// Validate and stage one `IfcDocumentReference` in the layout of `model`'s
178/// declared release.
179///
180/// # Errors
181///
182/// Location, identification and name all unstated; `name` and
183/// `referenced_document` not exactly one (WR1); a `referenced_document`
184/// that is not an `IfcDocumentInformation`; for IFC2X3, a `description` or
185/// `referenced_document` (`AuthoringNotInSchema`: IFC2X3 links a document
186/// to its references from the other side, so a reference authored here
187/// needs a `name`); and a header binding no single known release. Nothing
188/// is staged on an error.
189pub fn create_document_reference(
190    tx: &mut Transaction,
191    model: &Model,
192    draft: DocumentReferenceDraft<'_>,
193) -> ClassificationResult<EntityId> {
194    const ENTITY: &str = "IFCDOCUMENTREFERENCE";
195    let release = Release::of(model);
196    release.require_entity(ENTITY)?;
197    require_external_identity(ENTITY, draft.location, draft.identification, draft.name)?;
198    if !(draft.name.is_some() ^ draft.referenced_document.is_some()) {
199        return Err(ClassificationError::AuthoringInvalid {
200            entity: ENTITY,
201            attribute: "WR1",
202            value: "exactly one of Name and ReferencedDocument must be stated".into(),
203        });
204    }
205    if let Some(document) = draft.referenced_document {
206        require_accepts(tx, model, release, ENTITY, "ReferencedDocument", document)?;
207    }
208    let record = release.record(
209        ENTITY,
210        vec![
211            ("Location", optional_text(draft.location)),
212            ("Identification", optional_text(draft.identification)),
213            ("Name", optional_text(draft.name)),
214            ("Description", optional_text(draft.description)),
215            (
216                "ReferencedDocument",
217                optional_ref(draft.referenced_document),
218            ),
219        ],
220    )?;
221    Ok(tx.create(record))
222}
223
224/// Validate and stage one `IfcLibraryInformation` in the layout of
225/// `model`'s declared release.
226///
227/// # Errors
228///
229/// A `publisher` the release does not accept (`IfcActorSelect`, or only
230/// `IfcOrganization` in IFC2X3); for IFC2X3, a `location` or `description`
231/// (`AuthoringNotInSchema`) or a text `version_date` (`AuthoringValueType`);
232/// and a header binding no single known release. Nothing is staged on an
233/// error.
234pub fn create_library(
235    tx: &mut Transaction,
236    model: &Model,
237    draft: LibraryDraft<'_>,
238) -> ClassificationResult<EntityId> {
239    const ENTITY: &str = "IFCLIBRARYINFORMATION";
240    let release = Release::of(model);
241    release.require_entity(ENTITY)?;
242    if let Some(publisher) = draft.publisher {
243        require_accepts(tx, model, release, ENTITY, "Publisher", publisher)?;
244    }
245    let record = release.record(
246        ENTITY,
247        vec![
248            ("Name", text(draft.name)),
249            ("Version", optional_text(draft.version)),
250            ("Publisher", optional_ref(draft.publisher)),
251            ("VersionDate", optional_text(draft.version_date)),
252            ("Location", optional_text(draft.location)),
253            ("Description", optional_text(draft.description)),
254        ],
255    )?;
256    Ok(tx.create(record))
257}
258
259/// Validate and stage one `IfcLibraryReference` in the layout of `model`'s
260/// declared release.
261///
262/// # Errors
263///
264/// Location, identification and name all unstated; a `referenced_library`
265/// that is not an `IfcLibraryInformation`; for IFC2X3, a `description`,
266/// `language` or `referenced_library` (`AuthoringNotInSchema`); and a
267/// header binding no single known release. Nothing is staged on an error.
268pub fn create_library_reference(
269    tx: &mut Transaction,
270    model: &Model,
271    draft: LibraryReferenceDraft<'_>,
272) -> ClassificationResult<EntityId> {
273    const ENTITY: &str = "IFCLIBRARYREFERENCE";
274    let release = Release::of(model);
275    release.require_entity(ENTITY)?;
276    require_external_identity(ENTITY, draft.location, draft.identification, draft.name)?;
277    if let Some(library) = draft.referenced_library {
278        require_accepts(tx, model, release, ENTITY, "ReferencedLibrary", library)?;
279    }
280    let record = release.record(
281        ENTITY,
282        vec![
283            ("Location", optional_text(draft.location)),
284            ("Identification", optional_text(draft.identification)),
285            ("Name", optional_text(draft.name)),
286            ("Description", optional_text(draft.description)),
287            ("Language", optional_text(draft.language)),
288            ("ReferencedLibrary", optional_ref(draft.referenced_library)),
289        ],
290    )?;
291    Ok(tx.create(record))
292}