use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::{for_version, Schema, SchemaVersion};
use crate::error::CostError;
const fn proven(version: SchemaVersion) -> bool {
matches!(
version,
SchemaVersion::Ifc2x3 | SchemaVersion::Ifc4 | SchemaVersion::Ifc4x3
)
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ReadRelease {
version: SchemaVersion,
schema: &'static Schema,
}
impl ReadRelease {
pub(crate) fn of_version(version: SchemaVersion) -> Result<Self, CostError> {
if !proven(version) {
return Err(CostError::UnsupportedSchema {
schema: version.release_id().to_owned(),
});
}
let schema = for_version(version).map_err(|_| CostError::UnsupportedSchema {
schema: version.release_id().to_owned(),
})?;
Ok(Self { version, schema })
}
pub(crate) fn of(model: &Model) -> Result<Self, CostError> {
match model.header().schema.as_slice() {
[] => Self::of_version(SchemaVersion::Ifc4),
[token] => SchemaVersion::from_header_token(token)
.filter(|version| proven(*version))
.ok_or_else(|| CostError::UnsupportedSchema {
schema: token.clone(),
})
.and_then(Self::of_version),
tokens => Err(CostError::MultipleSchemas {
schemas: tokens.len(),
}),
}
}
pub(crate) const fn version(self) -> SchemaVersion {
self.version
}
pub(crate) fn value<'m>(
self,
entity_type: &str,
record: &'m Entity,
attribute: &str,
) -> Option<&'m Value> {
let slot = self
.schema
.attribute_names(entity_type)
.iter()
.position(|name| name.eq_ignore_ascii_case(attribute))?;
match record.attribute(slot)? {
Value::Null => None,
value => Some(value),
}
}
pub(crate) fn text<'m>(
self,
entity_type: &str,
record: &'m Entity,
attribute: &str,
) -> Option<&'m str> {
self.value(entity_type, record, attribute)?
.unwrap_typed()
.as_text()
}
pub(crate) fn date_time<'m>(
self,
entity_type: &str,
record: &'m Entity,
attribute: &str,
) -> Option<AuthoredDateTime<'m>> {
match self.value(entity_type, record, attribute)? {
Value::Ref(target) => Some(AuthoredDateTime::Record(*target)),
value => value.unwrap_typed().as_text().map(AuthoredDateTime::Text),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AuthoredDateTime<'m> {
Text(&'m str),
Record(EntityId),
}
impl<'m> AuthoredDateTime<'m> {
#[must_use]
pub const fn text(self) -> Option<&'m str> {
match self {
Self::Text(text) => Some(text),
Self::Record(_) => None,
}
}
#[must_use]
pub const fn record(self) -> Option<EntityId> {
match self {
Self::Record(id) => Some(id),
Self::Text(_) => None,
}
}
}