use ifc_model::{Model, Value};
use ifc_schema::Schema;
use crate::report::{Finding, Path, Report};
pub fn required_attributes(model: &Model, schema: &Schema, report: &mut Report) {
let mut ids: Vec<_> = model.iter().map(|(id, _)| id).collect();
ids.sort_unstable();
for id in ids {
let Some(entity) = model.get(id) else {
continue;
};
let Some(definition) = schema.entity(&entity.type_name) else {
continue;
};
let declared = schema.attributes(&entity.type_name);
if entity.attributes.len() != declared.len() {
report.push(Finding::error(
"structure.required.slot_count",
Path::Entity(id),
format!(
"{} declares {} attributes, the record has {}",
definition.name,
declared.len(),
entity.attributes.len()
),
));
continue;
}
for (index, (value, attribute)) in entity.attributes.iter().zip(declared.iter()).enumerate()
{
let is_derived = is_derived_slot(schema, &entity.type_name, &attribute.name);
let path = || Path::Attribute {
entity: id,
index,
name: Some(attribute.name.clone()),
};
match value {
Value::Derived if !is_derived => report.push(Finding::error(
"structure.required.not_derived",
path(),
"written as `*` but the schema does not derive it",
)),
Value::Null if is_derived => report.push(Finding::error(
"structure.required.derived_as_null",
path(),
"a derived slot must be written `*`, not `$`",
)),
Value::Null if !attribute.optional => report.push(Finding::error(
"structure.required.missing",
path(),
"required attribute is not stated",
)),
_ if is_derived && !matches!(value, Value::Derived) => report.push(Finding::error(
"structure.required.derived_has_value",
path(),
"a derived slot must be written `*`, not a value",
)),
_ => {}
}
}
}
}
fn is_derived_slot(schema: &Schema, type_name: &str, attribute: &str) -> bool {
if schema
.entity(type_name)
.is_some_and(|definition| definition.is_derived(attribute))
{
return true;
}
schema
.supertypes(type_name)
.iter()
.filter_map(|name| schema.entity(name))
.any(|definition| definition.is_derived(attribute))
}