use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::Schema;
use super::issue::TabularIssue;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Need {
Required,
Optional,
}
pub(crate) struct Slots<'m, 'i> {
model: &'m Model,
id: EntityId,
entity: &'m Entity,
names: Vec<&'m str>,
issues: &'i mut Vec<TabularIssue>,
}
impl<'m, 'i> Slots<'m, 'i> {
pub(crate) fn open(
model: &'m Model,
schema: &'m Schema,
id: EntityId,
entity: &'m Entity,
type_name: &'static str,
issues: &'i mut Vec<TabularIssue>,
) -> Self {
let names = schema.attribute_names(type_name);
if names.len() != entity.attributes.len() {
issues.push(TabularIssue::Arity {
entity: id,
type_name,
expected: names.len(),
found: entity.attributes.len(),
});
}
Self {
model,
id,
entity,
names,
issues,
}
}
fn slot(&mut self, attribute: &'static str, need: Need) -> Option<&'m Value> {
let entity = self.entity;
let value = self
.names
.iter()
.position(|name| *name == attribute)
.and_then(|slot| entity.attribute(slot))
.filter(|value| !matches!(value, Value::Null));
if value.is_none() && need == Need::Required {
self.issues.push(TabularIssue::Missing {
entity: self.id,
attribute,
});
}
value
}
fn malformed(&mut self, attribute: &'static str, found: &Value) {
self.issues.push(TabularIssue::Malformed {
entity: self.id,
attribute,
found: format!("{found:?}"),
});
}
pub(crate) fn text(&mut self, attribute: &'static str, need: Need) -> Option<&'m str> {
let value = self.slot(attribute, need)?;
let text = value.unwrap_typed().as_text();
if text.is_none() {
self.malformed(attribute, value);
}
text
}
pub(crate) fn enumeration(&mut self, attribute: &'static str, need: Need) -> Option<&'m str> {
let value = self.slot(attribute, need)?;
if let Value::Enum(token) = value {
Some(token)
} else {
self.malformed(attribute, value);
None
}
}
pub(crate) fn boolean(&mut self, attribute: &'static str, need: Need) -> Option<bool> {
let value = self.slot(attribute, need)?;
let flag = value.unwrap_typed().as_bool();
if flag.is_none() {
self.malformed(attribute, value);
}
flag
}
pub(crate) fn number(&mut self, attribute: &'static str, need: Need) -> Option<f64> {
let value = self.slot(attribute, need)?;
let number = value.unwrap_typed().as_f64();
if number.is_none() {
self.malformed(attribute, value);
}
number
}
pub(crate) fn reference(&mut self, attribute: &'static str, need: Need) -> Option<EntityId> {
let value = self.slot(attribute, need)?;
let Value::Ref(target) = value else {
self.malformed(attribute, value);
return None;
};
if self.model.get(*target).is_none() {
self.issues.push(TabularIssue::Dangling {
entity: self.id,
attribute,
target: *target,
});
}
Some(*target)
}
pub(crate) fn values(&mut self, attribute: &'static str, need: Need) -> Option<&'m [Value]> {
let value = self.slot(attribute, need)?;
let Some(items) = value.as_list() else {
self.malformed(attribute, value);
return None;
};
if items.is_empty() {
self.issues.push(TabularIssue::EmptyList {
entity: self.id,
attribute,
});
}
Some(items)
}
pub(crate) fn records(
&mut self,
attribute: &'static str,
need: Need,
expected: &'static str,
) -> Vec<Option<(EntityId, &'m Entity)>> {
let model = self.model;
let Some(items) = self.values(attribute, need) else {
return Vec::new();
};
let mut out = Vec::with_capacity(items.len());
for item in items {
let Value::Ref(target) = item else {
self.malformed(attribute, item);
out.push(None);
continue;
};
let Some(record) = model.get(*target) else {
self.issues.push(TabularIssue::Dangling {
entity: self.id,
attribute,
target: *target,
});
out.push(None);
continue;
};
if !record.is_type(expected) {
self.issues.push(TabularIssue::WrongReferenceType {
entity: self.id,
attribute,
target: *target,
expected,
actual: record.type_name.to_string(),
});
out.push(None);
continue;
}
out.push(Some((*target, record)));
}
out
}
}