use ifc_model::{EntityId, Model};
use ifc_schema::{ifc4, ifc4x3, Schema, SchemaVersion};
use crate::error::{GeorefError, GeorefResult};
#[derive(Debug, Clone, Copy)]
pub struct GeorefView<'m> {
pub(crate) model: &'m Model,
pub(crate) schema: &'static Schema,
pub(crate) version: SchemaVersion,
}
impl<'m> GeorefView<'m> {
pub fn for_model(model: &'m Model) -> GeorefResult<Self> {
let token = match model.header().schema.as_slice() {
[] => return Err(GeorefError::MissingSchema),
[token] => token,
tokens => {
return Err(GeorefError::AmbiguousSchema {
tokens: tokens.to_vec(),
});
}
};
let version = SchemaVersion::from_header_token(token).ok_or_else(|| {
GeorefError::UnsupportedSchema {
token: token.clone(),
}
})?;
let schema = match version {
SchemaVersion::Ifc4 => ifc4(),
SchemaVersion::Ifc4x3 => ifc4x3(),
SchemaVersion::Ifc2x3 => {
return Err(GeorefError::UnsupportedSchema {
token: token.clone(),
});
}
};
Ok(Self {
model,
schema,
version,
})
}
#[must_use]
pub const fn version(&self) -> SchemaVersion {
self.version
}
#[must_use]
pub const fn schema(&self) -> &'static Schema {
self.schema
}
pub(crate) fn require_known_type(&self, id: EntityId) -> GeorefResult<&'m str> {
let entity = self.model.get(id).ok_or(GeorefError::MissingEntity {
referrer: id,
missing: id,
})?;
let type_name = entity.type_name.as_ref();
if self.schema.entity(type_name).is_none() {
return Err(GeorefError::UnsupportedOperation {
entity: id,
actual: format!(
"{type_name} (not declared in {})",
self.version.release_id()
),
});
}
Ok(type_name)
}
}
#[cfg(test)]
mod tests {
use ifc_model::Entity;
use super::*;
fn model_with_schema(token: &str) -> Model {
let mut model = Model::new();
model.header_mut().schema = vec![token.to_owned()];
model
}
#[test]
fn accepts_ifc4() {
let model = model_with_schema("IFC4");
let view = GeorefView::for_model(&model).expect("IFC4 is accepted");
assert_eq!(view.version(), SchemaVersion::Ifc4);
}
#[test]
fn accepts_ifc4x3_and_its_add2_variant() {
for token in ["IFC4X3", "IFC4X3_ADD2"] {
let model = model_with_schema(token);
let view = GeorefView::for_model(&model).expect("IFC4X3 is accepted");
assert_eq!(view.version(), SchemaVersion::Ifc4x3);
}
}
#[test]
fn refuses_ifc2x3_because_it_declares_no_georeferencing_entities() {
let model = model_with_schema("IFC2X3");
assert!(matches!(
GeorefView::for_model(&model),
Err(GeorefError::UnsupportedSchema { token }) if token == "IFC2X3"
));
}
#[test]
fn refuses_an_unrecognized_schema_token() {
let model = model_with_schema("IFC5");
assert!(matches!(
GeorefView::for_model(&model),
Err(GeorefError::UnsupportedSchema { token }) if token == "IFC5"
));
}
#[test]
fn refuses_a_missing_schema_declaration() {
let model = Model::new();
assert!(matches!(
GeorefView::for_model(&model),
Err(GeorefError::MissingSchema)
));
}
#[test]
fn refuses_an_ambiguous_schema_declaration() {
let mut model = Model::new();
model.header_mut().schema = vec!["IFC4".to_owned(), "IFC4X3".to_owned()];
assert!(matches!(
GeorefView::for_model(&model),
Err(GeorefError::AmbiguousSchema { tokens })
if tokens == vec!["IFC4".to_owned(), "IFC4X3".to_owned()]
));
}
#[test]
fn names_an_ifc4x3_only_coordinate_operation_when_read_under_ifc4() {
let mut model = model_with_schema("IFC4");
model.insert(EntityId(1), Entity::new("IFCGEOGRAPHICCRS", vec![]));
let view = GeorefView::for_model(&model).expect("IFC4 is accepted");
assert!(matches!(
view.require_known_type(EntityId(1)),
Err(GeorefError::UnsupportedOperation { entity, actual })
if entity == EntityId(1) && actual.contains("IFCGEOGRAPHICCRS") && actual.contains("IFC4_ADD2_TC1")
));
}
#[test]
fn recognizes_an_ifc4x3_only_coordinate_operation_when_read_under_ifc4x3() {
let mut model = model_with_schema("IFC4X3");
model.insert(EntityId(1), Entity::new("IFCGEOGRAPHICCRS", vec![]));
let view = GeorefView::for_model(&model).expect("IFC4X3 is accepted");
assert_eq!(
view.require_known_type(EntityId(1))
.expect("declared in IFC4X3"),
"IFCGEOGRAPHICCRS"
);
}
}