use ifc_model::{Model, Value};
use ifc_schema::Schema;
use crate::report::{Finding, Path, Report};
use crate::type_check::aliased_aggregate;
pub fn aggregate_shape(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 declared = schema.attributes(&entity.type_name);
for (index, value) in entity.attributes.iter().enumerate() {
let Some(attribute) = declared.get(index) else {
continue;
};
if matches!(value, Value::Null | Value::Derived) {
continue;
}
let is_list = matches!(value.unwrap_typed(), Value::List(_));
let effective_type = match value {
Value::Typed { type_name, .. } => type_name.as_ref(),
_ => attribute.type_name.as_str(),
};
let declared_is_aggregate =
attribute.aggregate || aliased_aggregate(schema, effective_type).is_some();
let path = || Path::Attribute {
entity: id,
index,
name: Some(attribute.name.clone()),
};
if declared_is_aggregate && !is_list {
report.push(Finding::error(
"structure.cardinality.expected_aggregate",
path(),
format!("{} is an aggregate, a scalar was written", attribute.name),
));
} else if !declared_is_aggregate && is_list {
report.push(Finding::error(
"structure.cardinality.unexpected_aggregate",
path(),
format!("{} is a scalar, an aggregate was written", attribute.name),
));
}
}
}
}