use std::collections::BTreeSet;
use ifc_model::{EntityId, Model, Value};
use crate::error::PropertyAnomaly;
pub(crate) const MAX_COMPLEX_DEPTH: usize = 16;
pub(crate) const MAX_COMPLEX_MEMBERS: usize = 10_000;
pub(crate) struct Nesting<'a> {
path: Vec<EntityId>,
members: usize,
truncated: BTreeSet<EntityId>,
cycles: BTreeSet<(EntityId, EntityId)>,
anomalies: &'a mut Vec<PropertyAnomaly>,
}
impl<'a> Nesting<'a> {
pub(crate) fn new(anomalies: &'a mut Vec<PropertyAnomaly>) -> Self {
Self {
path: Vec::new(),
members: 0,
truncated: BTreeSet::new(),
cycles: BTreeSet::new(),
anomalies,
}
}
pub(crate) fn enter(&mut self, complex: EntityId) -> bool {
if self.path.len() >= MAX_COMPLEX_DEPTH {
self.anomalies.push(PropertyAnomaly::ComplexTooDeep {
complex,
limit: MAX_COMPLEX_DEPTH,
});
return false;
}
self.path.push(complex);
true
}
pub(crate) fn leave(&mut self) {
self.path.pop();
}
pub(crate) fn admit(&mut self, model: &Model, container: EntityId, member: EntityId) -> bool {
if !self.path.is_empty() {
self.members += 1;
if self.members > MAX_COMPLEX_MEMBERS {
if self.truncated.insert(container) {
self.anomalies.push(PropertyAnomaly::ComplexBudgetExceeded {
complex: container,
limit: MAX_COMPLEX_MEMBERS,
});
}
return false;
}
}
if self.path.contains(&member) {
if self.cycles.insert((container, member)) {
self.anomalies.push(PropertyAnomaly::ComplexCycle {
complex: container,
member,
});
}
return false;
}
if model.get(member).is_none() {
self.anomalies
.push(PropertyAnomaly::MissingMember { container, member });
return false;
}
true
}
pub(crate) fn members(
&mut self,
container: EntityId,
attribute: &'static str,
value: Option<&Value>,
) -> Vec<EntityId> {
let Some(Value::List(items)) = value else {
return Vec::new();
};
let mut seen = BTreeSet::new();
let mut members = Vec::with_capacity(items.len());
for item in items {
let Value::Ref(member) = item.unwrap_typed() else {
self.anomalies.push(PropertyAnomaly::MemberNotReference {
container,
attribute,
found: format!("{item:?}"),
});
continue;
};
if seen.insert(*member) {
members.push(*member);
} else {
self.anomalies.push(PropertyAnomaly::DuplicateMember {
container,
attribute,
member: *member,
});
}
}
members
}
pub(crate) fn report(&mut self, anomaly: PropertyAnomaly) {
self.anomalies.push(anomaly);
}
}