axioval_openbim/
lib.rs

1#![allow(clippy::doc_markdown)]
2
3//! Source-neutral `OpenBIM` semantic adapter contracts and production IFC seam.
4//!
5//! [`import_ifc_session`] parses strict IFC4 STEP bytes into an immutable,
6//! fingerprint-bound Axioval evidence session. The older importer trait remains
7//! available for host-defined OpenBIM sources.
8
9mod ifc;
10pub use ifc::{IfcSessionError, import_ifc_session};
11
12use std::collections::BTreeSet;
13
14use thiserror::Error;
15
16/// A source's stable identity and declared semantic schema.
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct SourceDescriptor {
19    source_id: String,
20    schema: String,
21}
22
23impl SourceDescriptor {
24    /// Creates a descriptor for one independently-addressable semantic source.
25    #[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    /// Returns the stable source identifier used to qualify entity identities.
34    #[must_use]
35    pub fn source_id(&self) -> &str {
36        &self.source_id
37    }
38
39    /// Returns the schema declaration supplied by the source.
40    #[must_use]
41    pub fn schema(&self) -> &str {
42        &self.schema
43    }
44}
45
46/// One semantic entity exposed by an OpenBIM source.
47#[derive(Clone, Debug, Eq, PartialEq)]
48pub struct SemanticEntity {
49    local_id: String,
50    type_name: String,
51}
52
53impl SemanticEntity {
54    /// Creates an entity with an identifier unique within its containing source.
55    #[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    /// Returns the source-local identifier.
64    #[must_use]
65    pub fn local_id(&self) -> &str {
66        &self.local_id
67    }
68
69    /// Returns the source-qualified identifier set by its containing source.
70    #[must_use]
71    pub fn qualified_id(&self) -> &str {
72        &self.local_id
73    }
74
75    /// Returns the declared OpenBIM entity type.
76    #[must_use]
77    pub fn type_name(&self) -> &str {
78        &self.type_name
79    }
80}
81
82/// Read-only semantic view that preserves input ordering and identity scope.
83pub trait SemanticSource {
84    /// Describes the source and its semantic schema.
85    fn descriptor(&self) -> &SourceDescriptor;
86
87    /// Iterates entities in the source's declared order.
88    fn entities(&self) -> Box<dyn Iterator<Item = &SemanticEntity> + '_>;
89}
90
91/// In-memory conformance double for semantic sources.
92#[derive(Clone, Debug)]
93pub struct InMemorySemanticSource {
94    descriptor: SourceDescriptor,
95    entities: Vec<SemanticEntity>,
96}
97
98impl InMemorySemanticSource {
99    /// Builds a source after qualifying each local entity identity with its source ID.
100    ///
101    /// # Errors
102    ///
103    /// Returns [`OpenBimError::EmptyEntityId`] for an empty local ID or
104    /// [`OpenBimError::DuplicateEntityId`] for an ambiguous local identity.
105    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/// A request for an external OpenBIM import implementation.
147#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct OpenBimImportRequest {
149    locator: String,
150}
151
152impl OpenBimImportRequest {
153    /// Creates a request whose locator is interpreted by the concrete importer.
154    #[must_use]
155    pub fn new(locator: impl Into<String>) -> Self {
156        Self {
157            locator: locator.into(),
158        }
159    }
160
161    /// Returns the opaque source locator.
162    #[must_use]
163    pub fn locator(&self) -> &str {
164        &self.locator
165    }
166}
167
168/// Integration seam for IFC/STEP or other OpenBIM parser implementations.
169pub trait OpenBimImporter {
170    /// Imports one source or returns an explicit integration failure.
171    ///
172    /// # Errors
173    ///
174    /// Returns an error reported by the external importer, including
175    /// [`OpenBimError::IntegrationUnavailable`].
176    fn import(
177        &self,
178        request: &OpenBimImportRequest,
179    ) -> Result<InMemorySemanticSource, OpenBimError>;
180}
181
182/// Explicit placeholder used while no external OpenBIM parser is wired in.
183#[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/// Errors produced while forming or importing a semantic source.
198#[derive(Debug, Error, Eq, PartialEq)]
199pub enum OpenBimError {
200    /// A source ID is required to prevent cross-source identity collisions.
201    #[error("semantic source ID must not be empty")]
202    EmptySourceId,
203    /// An entity must have a nonempty source-local ID.
204    #[error("semantic entity ID must not be empty")]
205    EmptyEntityId,
206    /// Two entities shared the same local ID in one source.
207    #[error("duplicate entity ID {local_id:?} in source {source_id:?}")]
208    DuplicateEntityId {
209        /// Source whose local identity was duplicated.
210        source_id: String,
211        /// Ambiguous identifier within `source_id`.
212        local_id: String,
213    },
214    /// A concrete external parser has not been selected or linked.
215    #[error("external integration unavailable: {integration}")]
216    IntegrationUnavailable {
217        /// Named external component that has not been linked.
218        integration: &'static str,
219    },
220}