use std::collections::BTreeSet;
use ifc_schema::{Schema, TypeKind};
const MAX_VISITED: usize = 4096;
#[must_use]
pub fn accepts(schema: &Schema, type_name: &str, candidate: &str) -> Option<bool> {
let TypeKind::Select(_) = &schema.type_def(type_name)?.kind else {
return None;
};
let mut frontier = vec![type_name.to_ascii_uppercase()];
let mut seen: BTreeSet<String> = BTreeSet::new();
while let Some(current) = frontier.pop() {
if !seen.insert(current.clone()) {
continue;
}
if seen.len() > MAX_VISITED {
return None;
}
let Some(definition) = schema.type_def(¤t) else {
continue;
};
match &definition.kind {
TypeKind::Select(members) => {
for member in members {
if member.eq_ignore_ascii_case(candidate) {
return Some(true);
}
frontier.push(member.to_ascii_uppercase());
}
}
TypeKind::Defined(target) => {
if target.eq_ignore_ascii_case(candidate) {
return Some(true);
}
frontier.push(target.trim().to_ascii_uppercase());
}
TypeKind::Enumeration(_) => {}
}
}
Some(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_member_behind_a_wide_select_is_found() {
let schema = ifc_schema::ifc4();
assert_eq!(
accepts(schema, "IfcAppliedValueSelect", "IfcMonetaryMeasure"),
Some(true),
);
}
#[test]
fn a_derived_measure_is_a_member_of_ifc_value() {
let schema = ifc_schema::ifc4();
assert_eq!(
accepts(schema, "IfcValue", "IfcVolumetricFlowRateMeasure"),
Some(true),
);
}
#[test]
fn a_non_member_is_still_rejected() {
let schema = ifc_schema::ifc4();
assert_eq!(accepts(schema, "IfcValue", "IfcWall"), Some(false));
}
#[test]
fn a_non_select_returns_none() {
let schema = ifc_schema::ifc4();
assert_eq!(accepts(schema, "IfcLengthMeasure", "REAL"), None);
}
}