use ifc_model::{Model, Value};
use ifc_schema::{for_version, Schema, SchemaVersion, TypeKind};
use crate::error::GeometryError;
use super::invalid;
#[derive(Debug, Clone, Copy)]
pub(super) struct Release {
version: SchemaVersion,
schema: &'static Schema,
}
pub(super) fn bind(model: &Model, type_name: &'static str) -> Result<Release, GeometryError> {
let unbound = |detail: String| GeometryError::AuthoringSchemaUnbound { type_name, detail };
let version = match model.header().schema.as_slice() {
[] => SchemaVersion::Ifc4,
[token] => SchemaVersion::from_header_token(token)
.ok_or_else(|| unbound(format!("FILE_SCHEMA declares unknown release {token:?}")))?,
tokens => {
return Err(unbound(format!(
"FILE_SCHEMA declares {} releases, not one",
tokens.len()
)))
}
};
let schema =
for_version(version).ok_or_else(|| unbound(format!("no bundled table for {version:?}")))?;
if schema
.entity(type_name)
.is_none_or(|entity| entity.abstract_)
{
return Err(GeometryError::AuthoringEntityNotInSchema {
type_name,
schema: version,
});
}
Ok(Release { version, schema })
}
impl Release {
pub(super) fn version(self) -> SchemaVersion {
self.version
}
pub(super) fn schema(self) -> &'static Schema {
self.schema
}
pub(super) fn parameter(
self,
type_name: &'static str,
attribute: &'static str,
value: Option<f64>,
) -> Result<Value, GeometryError> {
let Some(value) = value else {
return Ok(Value::Null);
};
let declared = self
.schema
.attributes(type_name)
.into_iter()
.find(|declared| declared.name.eq_ignore_ascii_case(attribute))
.ok_or_else(|| {
invalid(
type_name,
attribute,
format!("{:?} declares no such attribute", self.version),
)
})?;
let declared = declared.type_name.as_str();
if !self.schema.accepts_type(declared, "IfcParameterValue") {
return Err(invalid(
type_name,
attribute,
format!("{declared} does not admit an IfcParameterValue"),
));
}
let select = self
.schema
.type_def(declared)
.is_some_and(|definition| matches!(definition.kind, TypeKind::Select(_)));
Ok(if select {
Value::Typed {
type_name: "IFCPARAMETERVALUE".into(),
value: Box::new(Value::Real(value)),
}
} else {
Value::Real(value)
})
}
pub(super) fn require(
self,
type_name: &'static str,
attrs: &[Value],
) -> Result<(), GeometryError> {
let declared = self.schema.attributes(type_name);
if declared.len() != attrs.len() {
return Err(invalid(
type_name,
"*",
format!(
"{:?} declares {} attributes, the record has {}",
self.version,
declared.len(),
attrs.len()
),
));
}
for (declaration, value) in declared.into_iter().zip(attrs) {
if !declaration.optional && *value == Value::Null {
return Err(invalid(
type_name,
declaration.name.as_str(),
format!("{:?} requires it", self.version),
));
}
}
Ok(())
}
}