use ifc_model::{Entity, Value};
use ifc_schema::Schema;
use crate::check::{aggregate_element, describe_value, is_derived_slot, judge_value, Verdict};
use crate::error::{AuthorError, AuthorResult};
#[derive(Debug, Clone)]
pub struct EntityBuilder<'a> {
schema: &'a Schema,
entity: String,
set: Vec<(String, Value)>,
}
impl<'a> EntityBuilder<'a> {
pub fn new(schema: &'a Schema, entity: impl Into<String>) -> Self {
Self {
schema,
entity: entity.into(),
set: Vec::new(),
}
}
#[must_use]
pub fn set(mut self, attribute: impl Into<String>, value: Value) -> Self {
self.set.push((attribute.into(), value));
self
}
pub fn text(self, attribute: impl Into<String>, text: impl Into<std::sync::Arc<str>>) -> Self {
self.set(attribute, Value::Text(text.into()))
}
pub fn real(self, attribute: impl Into<String>, value: f64) -> Self {
self.set(attribute, Value::Real(value))
}
pub fn reference(self, attribute: impl Into<String>, id: ifc_model::EntityId) -> Self {
self.set(attribute, Value::Ref(id))
}
pub fn enumeration(
self,
attribute: impl Into<String>,
constant: impl Into<std::sync::Arc<str>>,
) -> Self {
self.set(attribute, Value::Enum(constant.into()))
}
}
impl EntityBuilder<'_> {
pub fn build(self) -> AuthorResult<Entity> {
let declared = self.schema.attributes(&self.entity);
if declared.is_empty() && self.schema.entity(&self.entity).is_none() {
return Err(AuthorError::UnknownEntity {
entity: self.entity,
});
}
let mut slots: Vec<Value> = declared
.iter()
.map(|attribute| {
if is_derived_slot(self.schema, &self.entity, &attribute.name) {
Value::Derived
} else {
Value::Null
}
})
.collect();
let mut filled = vec![false; declared.len()];
for (name, value) in &self.set {
let Some(index) = declared
.iter()
.position(|a| a.name.eq_ignore_ascii_case(name))
else {
return Err(AuthorError::UnknownAttribute {
entity: self.entity.clone(),
attribute: name.clone(),
known: declared.iter().map(|a| a.name.clone()).collect(),
});
};
if filled[index] {
return Err(AuthorError::DuplicateAttribute {
entity: self.entity.clone(),
attribute: declared[index].name.clone(),
});
}
check_value(self.schema, &self.entity, declared[index], value)?;
slots[index] = value.clone();
filled[index] = true;
}
for (index, attribute) in declared.iter().enumerate() {
if !filled[index] && !attribute.optional && !matches!(slots[index], Value::Derived) {
return Err(AuthorError::MissingRequired {
entity: self.entity.clone(),
attribute: attribute.name.clone(),
});
}
}
Ok(Entity::new(self.entity.to_ascii_uppercase(), slots))
}
pub fn insert(self, model: &mut ifc_model::Model) -> AuthorResult<ifc_model::EntityId> {
let entity = self.build()?;
Ok(model.push(entity))
}
}
pub(crate) fn check_value(
schema: &Schema,
entity: &str,
attribute: &ifc_schema::Attribute,
value: &Value,
) -> AuthorResult<()> {
let derived = is_derived_slot(schema, entity, &attribute.name);
match (derived, value) {
(true, Value::Derived) => return Ok(()),
(true, other) => {
return Err(AuthorError::DerivedAttribute {
entity: entity.to_owned(),
attribute: attribute.name.clone(),
found: describe_value(other),
})
}
(false, Value::Derived) => {
return Err(AuthorError::NotDerived {
entity: entity.to_owned(),
attribute: attribute.name.clone(),
})
}
(false, _) => {}
}
let aliased = if attribute.aggregate {
None
} else {
aggregate_element(schema, &attribute.type_name)
};
let expected_aggregate = attribute.aggregate || aliased.is_some();
if !matches!(value, Value::Null) {
let supplied_aggregate = matches!(value, Value::List(_));
if supplied_aggregate != expected_aggregate {
return Err(AuthorError::AggregateMismatch {
entity: entity.to_owned(),
attribute: attribute.name.clone(),
expected_aggregate,
});
}
}
if attribute.name.eq_ignore_ascii_case("GlobalId") {
if let Value::Text(text) = value {
if ifc_model::guid::Guid::parse(text).is_none() {
return Err(AuthorError::InvalidGlobalId {
entity: entity.to_owned(),
found: text.to_string(),
});
}
}
}
let element_type = aliased.as_deref().unwrap_or(&attribute.type_name);
let verdicts: Vec<(Verdict, &Value)> = match value {
Value::List(items) => items
.iter()
.map(|item| (judge_value(schema, element_type, item), item))
.collect(),
scalar => vec![(judge_value(schema, &attribute.type_name, scalar), scalar)],
};
if verdicts
.iter()
.any(|(verdict, _)| *verdict == Verdict::WrongType)
{
return Err(AuthorError::TypeMismatch {
entity: entity.to_owned(),
attribute: attribute.name.clone(),
expected: attribute.type_name.clone(),
found: describe_value(value),
});
}
if let Some((Verdict::WrongForm { typed_required }, offending)) = verdicts
.into_iter()
.find(|(verdict, _)| matches!(verdict, Verdict::WrongForm { .. }))
{
return Err(AuthorError::ValueForm {
entity: entity.to_owned(),
attribute: attribute.name.clone(),
declared: element_type.to_owned(),
typed_required,
found: describe_value(offending),
});
}
Ok(())
}