1use std::collections::HashSet;
29
30use openbim_step::express::{Attribute, EntityDef, ParsedSchema, TypeDef, TypeKind};
31use openbim_step::SchemaGraph;
32
33use crate::version::SchemaVersion;
34
35#[derive(Debug, Clone)]
37pub struct Schema {
38 graph: SchemaGraph,
39}
40
41impl Schema {
42 #[must_use]
44 pub fn from_parsed(parsed: ParsedSchema) -> Self {
45 Self {
46 graph: SchemaGraph::new(parsed),
47 }
48 }
49
50 #[must_use]
52 pub fn from_express(source: &str) -> Self {
53 Self {
54 graph: SchemaGraph::from_express(source),
55 }
56 }
57
58 #[must_use]
64 pub fn from_express_bytes(bytes: &[u8]) -> Self {
65 let text: String = bytes.iter().map(|&byte| byte as char).collect();
66 Self::from_express(&text)
67 }
68
69 #[must_use]
71 pub fn name(&self) -> &str {
72 self.graph.name()
73 }
74
75 #[must_use]
80 pub fn version(&self) -> Option<SchemaVersion> {
81 SchemaVersion::from_header_token(self.graph.name())
82 }
83
84 #[must_use]
86 pub fn entity_count(&self) -> usize {
87 self.graph.entity_count()
88 }
89
90 #[must_use]
92 pub fn type_count(&self) -> usize {
93 self.graph.type_count()
94 }
95
96 #[must_use]
98 pub fn entity(&self, name: &str) -> Option<&EntityDef> {
99 self.graph.entity(name)
100 }
101
102 #[must_use]
104 pub fn type_def(&self, name: &str) -> Option<&TypeDef> {
105 self.graph.type_def(name)
106 }
107
108 pub fn entity_names(&self) -> impl Iterator<Item = &str> {
110 self.graph.entity_names()
111 }
112
113 #[must_use]
118 pub fn accepts_type(&self, declared: &str, candidate: &str) -> bool {
119 self.accepts_type_inner(declared, candidate, &mut HashSet::new(), 32)
120 }
121
122 fn accepts_type_inner(
123 &self,
124 declared: &str,
125 candidate: &str,
126 seen: &mut HashSet<(String, String)>,
127 depth: usize,
128 ) -> bool {
129 if declared.eq_ignore_ascii_case(candidate) {
130 return self.entity(declared).is_some() || self.type_def(declared).is_some();
131 }
132 if depth == 0
133 || !seen.insert((
134 declared.to_ascii_uppercase(),
135 candidate.to_ascii_uppercase(),
136 ))
137 {
138 return false;
139 }
140 if self.entity(declared).is_some() && self.entity(candidate).is_some() {
141 return self.is_a(candidate, declared);
142 }
143 if let Some(definition) = self.type_def(declared) {
144 match &definition.kind {
145 TypeKind::Defined(alias) => {
146 if self.accepts_type_inner(alias, candidate, seen, depth - 1) {
147 return true;
148 }
149 }
150 TypeKind::Select(members) => {
151 if members
152 .iter()
153 .any(|member| self.accepts_type_inner(member, candidate, seen, depth - 1))
154 {
155 return true;
156 }
157 }
158 TypeKind::Enumeration(_) => {}
159 }
160 }
161 if let Some(definition) = self.type_def(candidate) {
162 if let TypeKind::Defined(alias) = &definition.kind {
163 return self.accepts_type_inner(declared, alias, seen, depth - 1);
164 }
165 }
166 false
167 }
168
169 #[must_use]
171 pub fn is_a(&self, name: &str, ancestor: &str) -> bool {
172 self.graph.is_a(name, ancestor)
173 }
174
175 #[must_use]
177 pub fn supertypes(&self, name: &str) -> Vec<&str> {
178 self.graph.supertypes(name)
179 }
180
181 #[must_use]
183 pub fn direct_subtypes(&self, name: &str) -> Vec<&str> {
184 self.graph.direct_subtypes(name)
185 }
186
187 #[must_use]
191 pub fn subtypes(&self, name: &str) -> Vec<&str> {
192 self.graph.subtypes(name)
193 }
194
195 #[must_use]
197 pub fn attributes(&self, name: &str) -> Vec<&Attribute> {
198 self.graph.attributes(name)
199 }
200
201 #[must_use]
203 pub fn attribute_names(&self, name: &str) -> Vec<&str> {
204 self.graph.attribute_names(name)
205 }
206
207 #[must_use]
211 pub fn resolve_defined(&self, name: &str) -> String {
212 self.graph.resolve_defined(name)
213 }
214
215 #[must_use]
220 pub fn graph(&self) -> &SchemaGraph {
221 &self.graph
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 const CHAIN: &str = "\
230SCHEMA IFC4;
231ENTITY IfcRoot
232 ABSTRACT SUPERTYPE OF (ONEOF(IfcObjectDefinition));
233 GlobalId : IfcGloballyUniqueId;
234 OwnerHistory : OPTIONAL IfcOwnerHistory;
235 Name : OPTIONAL IfcLabel;
236 Description : OPTIONAL IfcText;
237END_ENTITY;
238ENTITY IfcObjectDefinition
239 ABSTRACT SUPERTYPE OF (ONEOF(IfcObject))
240 SUBTYPE OF (IfcRoot);
241END_ENTITY;
242ENTITY IfcObject
243 SUBTYPE OF (IfcObjectDefinition);
244 ObjectType : OPTIONAL IfcLabel;
245END_ENTITY;
246TYPE IfcLengthMeasure = REAL; END_TYPE;
247TYPE IfcPositiveLengthMeasure = IfcLengthMeasure; END_TYPE;
248END_SCHEMA;";
249
250 #[test]
251 fn the_declared_schema_name_maps_onto_a_known_ifc_version() {
252 let schema = Schema::from_express(CHAIN);
253 assert_eq!(schema.name(), "IFC4");
254 assert_eq!(schema.version(), Some(SchemaVersion::Ifc4));
255 }
256
257 #[test]
259 fn an_unrecognized_schema_name_has_no_version_but_still_works() {
260 let schema = Schema::from_express(
261 "SCHEMA AP242; ENTITY Product; Id : Identifier; END_ENTITY; END_SCHEMA;",
262 );
263 assert_eq!(schema.version(), None, "not an IFC schema");
264 assert_eq!(schema.attribute_names("Product"), ["Id"]);
265 }
266
267 #[test]
268 fn inherited_attributes_come_first_in_positional_order() {
269 assert_eq!(
270 Schema::from_express(CHAIN).attribute_names("IFCOBJECT"),
271 [
272 "GlobalId",
273 "OwnerHistory",
274 "Name",
275 "Description",
276 "ObjectType"
277 ],
278 );
279 }
280
281 #[test]
282 fn defined_types_resolve_through_the_alias_chain() {
283 assert_eq!(
284 Schema::from_express(CHAIN).resolve_defined("IfcPositiveLengthMeasure"),
285 "REAL"
286 );
287 }
288}