1#![allow(clippy::doc_markdown)]
2
3mod ifc;
10pub use ifc::{IfcSessionError, import_ifc_session};
11
12use std::collections::BTreeSet;
13
14use thiserror::Error;
15
16#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct SourceDescriptor {
19 source_id: String,
20 schema: String,
21}
22
23impl SourceDescriptor {
24 #[must_use]
26 pub fn new(source_id: impl Into<String>, schema: impl Into<String>) -> Self {
27 Self {
28 source_id: source_id.into(),
29 schema: schema.into(),
30 }
31 }
32
33 #[must_use]
35 pub fn source_id(&self) -> &str {
36 &self.source_id
37 }
38
39 #[must_use]
41 pub fn schema(&self) -> &str {
42 &self.schema
43 }
44}
45
46#[derive(Clone, Debug, Eq, PartialEq)]
48pub struct SemanticEntity {
49 local_id: String,
50 type_name: String,
51}
52
53impl SemanticEntity {
54 #[must_use]
56 pub fn new(local_id: impl Into<String>, type_name: impl Into<String>) -> Self {
57 Self {
58 local_id: local_id.into(),
59 type_name: type_name.into(),
60 }
61 }
62
63 #[must_use]
65 pub fn local_id(&self) -> &str {
66 &self.local_id
67 }
68
69 #[must_use]
71 pub fn qualified_id(&self) -> &str {
72 &self.local_id
73 }
74
75 #[must_use]
77 pub fn type_name(&self) -> &str {
78 &self.type_name
79 }
80}
81
82pub trait SemanticSource {
84 fn descriptor(&self) -> &SourceDescriptor;
86
87 fn entities(&self) -> Box<dyn Iterator<Item = &SemanticEntity> + '_>;
89}
90
91#[derive(Clone, Debug)]
93pub struct InMemorySemanticSource {
94 descriptor: SourceDescriptor,
95 entities: Vec<SemanticEntity>,
96}
97
98impl InMemorySemanticSource {
99 pub fn new(
106 descriptor: SourceDescriptor,
107 entities: impl IntoIterator<Item = SemanticEntity>,
108 ) -> Result<Self, OpenBimError> {
109 if descriptor.source_id.is_empty() {
110 return Err(OpenBimError::EmptySourceId);
111 }
112 let mut ids = BTreeSet::new();
113 let mut qualified = Vec::new();
114 for entity in entities {
115 if entity.local_id.is_empty() {
116 return Err(OpenBimError::EmptyEntityId);
117 }
118 if !ids.insert(entity.local_id.clone()) {
119 return Err(OpenBimError::DuplicateEntityId {
120 source_id: descriptor.source_id.clone(),
121 local_id: entity.local_id,
122 });
123 }
124 qualified.push(SemanticEntity {
125 local_id: format!("{}:{}", descriptor.source_id, entity.local_id),
126 type_name: entity.type_name,
127 });
128 }
129 Ok(Self {
130 descriptor,
131 entities: qualified,
132 })
133 }
134}
135
136impl SemanticSource for InMemorySemanticSource {
137 fn descriptor(&self) -> &SourceDescriptor {
138 &self.descriptor
139 }
140
141 fn entities(&self) -> Box<dyn Iterator<Item = &SemanticEntity> + '_> {
142 Box::new(self.entities.iter())
143 }
144}
145
146#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct OpenBimImportRequest {
149 locator: String,
150}
151
152impl OpenBimImportRequest {
153 #[must_use]
155 pub fn new(locator: impl Into<String>) -> Self {
156 Self {
157 locator: locator.into(),
158 }
159 }
160
161 #[must_use]
163 pub fn locator(&self) -> &str {
164 &self.locator
165 }
166}
167
168pub trait OpenBimImporter {
170 fn import(
177 &self,
178 request: &OpenBimImportRequest,
179 ) -> Result<InMemorySemanticSource, OpenBimError>;
180}
181
182#[derive(Clone, Debug, Default)]
184pub struct UnavailableOpenBimImporter;
185
186impl OpenBimImporter for UnavailableOpenBimImporter {
187 fn import(
188 &self,
189 _request: &OpenBimImportRequest,
190 ) -> Result<InMemorySemanticSource, OpenBimError> {
191 Err(OpenBimError::IntegrationUnavailable {
192 integration: "OpenBIM IFC/STEP importer",
193 })
194 }
195}
196
197#[derive(Debug, Error, Eq, PartialEq)]
199pub enum OpenBimError {
200 #[error("semantic source ID must not be empty")]
202 EmptySourceId,
203 #[error("semantic entity ID must not be empty")]
205 EmptyEntityId,
206 #[error("duplicate entity ID {local_id:?} in source {source_id:?}")]
208 DuplicateEntityId {
209 source_id: String,
211 local_id: String,
213 },
214 #[error("external integration unavailable: {integration}")]
216 IntegrationUnavailable {
217 integration: &'static str,
219 },
220}