1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Support {
26 Implemented,
28 Unsupported(&'static str),
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct RuleEntry {
37 pub id: &'static str,
39 pub entity: Option<&'static str>,
41 pub support: Support,
43}
44
45const NEEDS_EXPRESSIONS: &str = "requires an EXPRESS expression evaluator";
47const NEEDS_BOUNDS: &str = "requires aggregate bounds, which the schema parser does not retain";
48const NEEDS_INVERSES: &str =
49 "not implemented uniformly: IFC2X3 requires INVERSE relationship semantics, which validation does not derive";
50const NEEDS_GEOMETRY: &str = "requires geometric evaluation, which validation does not perform";
51
52pub const RULES: &[RuleEntry] = &[
58 RuleEntry {
59 id: "global.IfcSingleProjectInstance",
60 entity: None,
61 support: Support::Implemented,
62 },
63 RuleEntry {
67 id: "global.UniqueGlobalId",
68 entity: None,
69 support: Support::Implemented,
70 },
71 RuleEntry {
72 id: "IfcRelDefinesByProperties.NoRelatedTypeObject",
73 entity: Some("IfcRelDefinesByProperties"),
74 support: Support::Implemented,
75 },
76 RuleEntry {
77 id: "IfcExternalReference.WR1",
78 entity: Some("IfcExternalReference"),
79 support: Support::Implemented,
80 },
81 RuleEntry {
82 id: "IfcRelSequence.WR1",
83 entity: Some("IfcRelSequence"),
84 support: Support::Implemented,
85 },
86 RuleEntry {
87 id: "IfcRelSequence.AvoidInconsistentSequence",
88 entity: Some("IfcRelSequence"),
89 support: Support::Implemented,
90 },
91 RuleEntry {
92 id: "IfcRelAggregates.NoSelfReference",
93 entity: Some("IfcRelAggregates"),
94 support: Support::Implemented,
95 },
96 RuleEntry {
97 id: "IfcRelNests.NoSelfReference",
98 entity: Some("IfcRelNests"),
99 support: Support::Implemented,
100 },
101 RuleEntry {
102 id: "IfcMaterialLayer.NormalizedPriority",
103 entity: Some("IfcMaterialLayer"),
104 support: Support::Implemented,
105 },
106 RuleEntry {
107 id: "IfcRelAssignsToActor.NoSelfReference",
108 entity: Some("IfcRelAssignsToActor"),
109 support: Support::Implemented,
110 },
111 RuleEntry {
112 id: "IfcRelAssignsToProcess.NoSelfReference",
113 entity: Some("IfcRelAssignsToProcess"),
114 support: Support::Implemented,
115 },
116 RuleEntry {
117 id: "IfcRelAssignsToProduct.NoSelfReference",
118 entity: Some("IfcRelAssignsToProduct"),
119 support: Support::Implemented,
120 },
121 RuleEntry {
122 id: "IfcRelAssignsToGroupByFactor.NoSelfReference",
123 entity: Some("IfcRelAssignsToGroupByFactor"),
124 support: Support::Implemented,
125 },
126 RuleEntry {
127 id: "IfcRelConnectsPathElements.NormalizedRelatingPriorities",
128 entity: Some("IfcRelConnectsPathElements"),
129 support: Support::Implemented,
130 },
131 RuleEntry {
132 id: "IfcRelConnectsPathElements.NormalizedRelatedPriorities",
133 entity: Some("IfcRelConnectsPathElements"),
134 support: Support::Implemented,
135 },
136 RuleEntry {
137 id: "IfcRelSpaceBoundary.CorrectPhysOrVirt",
138 entity: Some("IfcRelSpaceBoundary"),
139 support: Support::Implemented,
140 },
141 RuleEntry {
142 id: "IfcDocumentReference.WR1",
143 entity: Some("IfcDocumentReference"),
144 support: Support::Unsupported(NEEDS_INVERSES),
145 },
146 RuleEntry {
147 id: "IfcRepresentationContextSameWCS",
148 entity: None,
149 support: Support::Unsupported(NEEDS_GEOMETRY),
150 },
151 RuleEntry {
152 id: "IfcPolyLoop.WR21",
153 entity: Some("IfcPolyLoop"),
154 support: Support::Unsupported(NEEDS_BOUNDS),
155 },
156 RuleEntry {
157 id: "IfcPhysicalSimpleQuantity.WR21",
158 entity: Some("IfcQuantityLength"),
159 support: Support::Unsupported(NEEDS_EXPRESSIONS),
160 },
161 RuleEntry {
162 id: "IfcZone.WR1",
163 entity: Some("IfcZone"),
164 support: Support::Unsupported(NEEDS_EXPRESSIONS),
165 },
166];
167
168#[must_use]
170pub fn lookup(id: &str) -> Option<&'static RuleEntry> {
171 RULES.iter().find(|entry| entry.id == id)
172}
173
174pub fn unsupported() -> impl Iterator<Item = &'static RuleEntry> {
176 RULES
177 .iter()
178 .filter(|entry| matches!(entry.support, Support::Unsupported(_)))
179}
180
181pub fn implemented() -> impl Iterator<Item = &'static RuleEntry> {
183 RULES
184 .iter()
185 .filter(|entry| matches!(entry.support, Support::Implemented))
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
194 fn rule_ids_are_unique() {
195 let mut ids: Vec<&str> = RULES.iter().map(|entry| entry.id).collect();
196 ids.sort_unstable();
197 let count = ids.len();
198 ids.dedup();
199 assert_eq!(ids.len(), count, "duplicate rule id in the registry");
200 }
201
202 #[test]
209 fn the_registry_admits_what_it_cannot_check() {
210 assert!(
211 unsupported().count() > 0,
212 "a validator claiming full WHERE-rule coverage is lying"
213 );
214 }
215
216 #[test]
217 fn unsupported_boundaries_remain_explicit() {
218 let reasons: Vec<_> = unsupported()
219 .filter_map(|entry| match entry.support {
220 Support::Unsupported(reason) => Some(reason),
221 Support::Implemented => None,
222 })
223 .collect();
224 for required in ["aggregate bounds", "EXPRESS expression", "INVERSE"] {
225 assert!(
226 reasons.iter().any(|reason| reason.contains(required)),
227 "missing explicit unsupported boundary for {required}: {reasons:?}"
228 );
229 }
230 }
231}