use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::Schema;
use crate::report::{Finding, Path, Report};
use crate::type_check::describe_value;
pub(super) struct Site<'a> {
pub rule: &'a str,
pub id: EntityId,
pub entity: &'a Entity,
pub schema: &'a Schema,
}
impl<'a> Site<'a> {
pub fn slot(&self, attribute: &str, report: &mut Report) -> Option<usize> {
let index = self.lookup(attribute);
if index.is_none() {
self.undeclared(attribute, report);
}
index
}
pub fn lookup(&self, attribute: &str) -> Option<usize> {
self.schema
.attribute_names(&self.entity.type_name)
.iter()
.position(|name| name.eq_ignore_ascii_case(attribute))
}
pub fn undeclared(&self, attribute: &str, report: &mut Report) {
report.push(Finding::evaluation_error(
self.rule,
Path::Entity(self.id),
format!(
"schema {} declares no attribute {attribute} on {}, so the rule \
cannot be evaluated",
self.schema.name(),
self.entity.type_name
),
));
}
pub fn value(&self, index: usize) -> Option<&'a Value> {
self.entity
.attribute(index)
.filter(|value| !matches!(value, Value::Null | Value::Derived))
}
pub fn path(&self, index: usize, attribute: &str) -> Path {
Path::Attribute {
entity: self.id,
index,
name: Some(attribute.into()),
}
}
pub fn unreadable(
&self,
index: usize,
attribute: &str,
expected: &str,
found: &Value,
report: &mut Report,
) {
report.push(Finding::evaluation_error(
self.rule,
self.path(index, attribute),
format!(
"{attribute} must be {expected} for the rule to be evaluated; the file wrote {}",
describe_value(found)
),
));
}
pub fn reference(
&self,
index: usize,
attribute: &str,
report: &mut Report,
) -> Option<EntityId> {
let value = self.value(index)?;
let reference = value.unwrap_typed().as_ref_id();
if reference.is_none() {
self.unreadable(index, attribute, "an entity reference", value, report);
}
reference
}
pub fn target(
&self,
model: &'a Model,
target: EntityId,
index: usize,
attribute: &str,
report: &mut Report,
) -> Option<&'a Entity> {
let entity = model.get(target);
if entity.is_none() {
report.push(Finding::evaluation_error(
self.rule,
self.path(index, attribute),
format!(
"{attribute} names {target}, which the file does not contain, so its \
type cannot be tested"
),
));
}
entity
}
}