use std::sync::OnceLock;
use crate::artifact::decode_schema;
use crate::registry::Schema;
use crate::version::SchemaVersion;
static IFC4: OnceLock<Schema> = OnceLock::new();
static IFC4X3: OnceLock<Schema> = OnceLock::new();
static IFC2X3: OnceLock<Schema> = OnceLock::new();
#[must_use]
pub fn ifc2x3() -> &'static Schema {
IFC2X3.get_or_init(|| {
let parsed = decode_schema(include_bytes!("../data/ifc2x3-tc1.bin")).expect(
"the bundled IFC2x3 artifact is produced and verified by this crate's own build",
);
Schema::from_parsed(parsed)
})
}
#[must_use]
pub fn ifc4() -> &'static Schema {
IFC4.get_or_init(|| {
let parsed = decode_schema(include_bytes!("../data/ifc4-add2-tc1.bin"))
.expect("the bundled IFC4 artifact is produced and verified by this crate's own build");
Schema::from_parsed(parsed)
})
}
#[must_use]
pub fn ifc4x3() -> &'static Schema {
IFC4X3.get_or_init(|| {
let parsed = decode_schema(include_bytes!("../data/ifc4x3-add2.bin")).expect(
"the bundled IFC4X3 artifact is produced and verified by this crate's own build",
);
Schema::from_parsed(parsed)
})
}
#[must_use]
pub fn for_version(version: SchemaVersion) -> Option<&'static Schema> {
match version {
SchemaVersion::Ifc2x3 => Some(ifc2x3()),
SchemaVersion::Ifc4 => Some(ifc4()),
SchemaVersion::Ifc4x3 => Some(ifc4x3()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bundled_ifc4_matches_the_normative_entity_and_type_counts() {
let schema = ifc4();
assert_eq!(schema.entity_count(), 776, "IFC4 ADD2 TC1 entity count");
assert_eq!(schema.type_count(), 397, "IFC4 ADD2 TC1 type count");
}
#[test]
fn bundled_ifc4x3_matches_the_normative_entity_and_type_counts() {
let schema = ifc4x3();
assert_eq!(schema.entity_count(), 876, "IFC4X3 ADD2 entity count");
assert_eq!(schema.type_count(), 436, "IFC4X3 ADD2 type count");
assert!(schema.entity("IfcBuiltElement").is_some());
assert!(schema.entity("IfcBuildingElement").is_none());
assert!(std::ptr::eq(schema, ifc4x3()), "constructor must cache");
}
#[test]
fn bundled_ifc2x3_matches_the_normative_entity_and_type_counts() {
let schema = ifc2x3();
assert_eq!(schema.entity_count(), 653, "IFC2x3 TC1 entity count");
assert_eq!(schema.type_count(), 327, "IFC2x3 TC1 type count");
}
#[test]
fn bundled_ifc4_resolves_the_deep_inheritance_chain() {
let schema = ifc4();
assert!(schema.is_a("IFCWALL", "IfcRoot"), "wall is a root");
assert!(schema.is_a("IFCWALL", "IfcProduct"), "wall is a product");
assert_eq!(
&schema.attribute_names("IFCWALL")[..4],
["GlobalId", "OwnerHistory", "Name", "Description"],
"IfcRoot's slots must come first"
);
}
#[test]
fn version_lookup_returns_the_matching_table() {
assert_eq!(
for_version(SchemaVersion::Ifc4).map(|s| s.entity_count()),
Some(776)
);
assert_eq!(
for_version(SchemaVersion::Ifc2x3).map(|s| s.entity_count()),
Some(653)
);
assert_eq!(
for_version(SchemaVersion::Ifc4x3).map(|s| s.entity_count()),
Some(876),
"IFC4X3 must select its own bundled tables"
);
}
#[test]
fn the_ifc2x3_and_ifc4_bundles_are_not_the_same_table() {
assert_eq!(
ifc2x3().attribute_names("IFCWALLSTANDARDCASE"),
[
"GlobalId",
"OwnerHistory",
"Name",
"Description",
"ObjectType",
"ObjectPlacement",
"Representation",
"Tag"
],
);
assert_eq!(
ifc4().attribute_names("IFCWALLSTANDARDCASE"),
[
"GlobalId",
"OwnerHistory",
"Name",
"Description",
"ObjectType",
"ObjectPlacement",
"Representation",
"Tag",
"PredefinedType"
],
);
}
#[test]
fn version_specific_entities_resolve_in_their_own_schema() {
assert!(
!ifc2x3().attributes("IFC2DCOMPOSITECURVE").is_empty(),
"Ifc2DCompositeCurve exists in IFC2x3"
);
assert!(
ifc4().type_def("IfcHeatFluxDensityMeasure").is_some(),
"IFC4 keeps the derived measure types"
);
}
#[test]
fn repeated_calls_return_the_same_cached_schema() {
let first = ifc4() as *const _;
let second = ifc4() as *const _;
assert_eq!(first, second, "ifc4() must not reparse on every call");
let first = ifc2x3() as *const _;
let second = ifc2x3() as *const _;
assert_eq!(first, second, "ifc2x3() must not reparse on every call");
}
#[test]
fn type_product_subtypes_keep_their_full_slot_layout() {
let schema = ifc4();
assert_eq!(
schema.attribute_names("IFCTYPEPRODUCT"),
[
"GlobalId",
"OwnerHistory",
"Name",
"Description",
"ApplicableOccurrence",
"HasPropertySets",
"RepresentationMaps",
"Tag"
],
);
assert_eq!(
schema.attribute_names("IFCWALLTYPE"),
[
"GlobalId",
"OwnerHistory",
"Name",
"Description",
"ApplicableOccurrence",
"HasPropertySets",
"RepresentationMaps",
"Tag",
"ElementType",
"PredefinedType"
],
"ElementType sits at 8, not 6"
);
}
#[test]
fn ifc2x3_type_product_keeps_its_full_slot_layout() {
assert_eq!(
ifc2x3().attribute_names("IFCTYPEPRODUCT"),
[
"GlobalId",
"OwnerHistory",
"Name",
"Description",
"ApplicableOccurrence",
"HasPropertySets",
"RepresentationMaps",
"Tag"
],
);
}
#[test]
fn every_inline_unique_aggregate_survives_parsing() {
let schema = ifc4();
assert!(
schema
.attribute_names("IFCGRID")
.contains(&"PredefinedType"),
"IfcGrid declares UAxes/VAxes/WAxes with inline UNIQUE"
);
assert_eq!(schema.attribute_names("IFCPOLYLOOP"), ["Polygon"]);
assert_eq!(
schema.attribute_names("IFCPROPERTYTABLEVALUE")[7],
"CurveInterpolation",
"slot 7 after two inherited IfcProperty slots"
);
}
#[test]
fn the_generator_guards_match_the_bundled_artifacts() {
let source = include_str!("../tools/generate.rs");
let expected: Vec<(usize, usize)> = source
.split("Target {")
.skip(1)
.filter_map(|block| {
let number = |key: &str| -> Option<usize> {
let start = block.find(key)? + key.len();
let rest = &block[start..];
let end = rest.find(',')?;
rest[..end].trim().parse().ok()
};
Some((number("entities:")?, number("types:")?))
})
.collect();
assert_eq!(expected.len(), 3, "three schemas are generated");
assert_eq!(
expected[0],
(ifc2x3().entity_count(), ifc2x3().type_count()),
"ifc2x3 generator guard vs the committed artifact"
);
assert_eq!(
expected[1],
(ifc4().entity_count(), ifc4().type_count()),
"ifc4 generator guard vs the committed artifact"
);
assert_eq!(
expected[2],
(ifc4x3().entity_count(), ifc4x3().type_count()),
"ifc4x3 generator guard vs the committed artifact"
);
}
}