1use std::collections::HashMap;
17use std::sync::Arc;
18
19use serde_json::Value;
20
21const XML_NAME: &str = "http://hl7.org/fhir/tools/StructureDefinition/xml-name";
22const XML_NAMESPACE: &[&str] = &[
23 "http://hl7.org/fhir/tools/StructureDefinition/xml-namespace",
24 "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace",
25];
26const CHOICE_GROUP: &str = "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group";
27const DEFAULT_TYPE: &str = "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype";
28const DATE_FORMAT: &str =
29 "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-format";
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum Representation {
34 Element,
36 Attribute,
38 Text,
40 Markup,
42}
43
44#[derive(Debug, Clone)]
46pub struct Element {
47 pub name: String,
49 pub xml_name: String,
51 pub namespace: Option<String>,
54 pub explicit_namespace: bool,
57 pub representation: Representation,
58 pub type_attribute: bool,
60 pub types: Vec<String>,
62 pub default_type: Option<String>,
64 pub many: bool,
65 pub choice_group: bool,
67 pub date_format: bool,
69 pub children: Vec<Element>,
71}
72
73impl Element {
74 pub fn single_type(&self) -> Option<&str> {
76 match self.types.as_slice() {
77 [single] => Some(single),
78 _ => None,
79 }
80 }
81
82 pub fn child(&self, name: &str) -> Option<&Element> {
83 self.children.iter().find(|c| c.name == name)
84 }
85}
86
87#[derive(Debug, Clone)]
89pub struct TypeDef {
90 pub url: String,
91 pub name: String,
93 pub namespace: Option<String>,
94 pub xml_name: Option<String>,
96 pub base: Option<String>,
97 pub is_abstract: bool,
98 pub elements: Vec<Element>,
100}
101
102impl TypeDef {
103 pub fn element(&self, name: &str) -> Option<&Element> {
104 self.elements.iter().find(|e| e.name == name)
105 }
106}
107
108#[derive(Debug, Clone, Copy)]
111pub enum Root<'m> {
112 Type(&'m TypeDef),
113 Element(&'m TypeDef, &'m Element),
114}
115
116impl<'m> Root<'m> {
117 pub fn spec(&self) -> String {
119 match self {
120 Root::Type(ty) => ty.url.clone(),
121 Root::Element(ty, element) => format!("{}.{}", ty.url, element.name),
122 }
123 }
124
125 pub fn path(&self) -> String {
127 match self {
128 Root::Type(ty) => ty.name.clone(),
129 Root::Element(ty, element) => format!("{}.{}", ty.name, element.name),
130 }
131 }
132}
133
134#[derive(Debug, Default, Clone)]
136pub struct Model {
137 types: HashMap<String, Arc<TypeDef>>,
138 by_name: HashMap<(Option<String>, String), String>,
140 by_bare_name: HashMap<String, Vec<String>>,
141 roots: HashMap<(Option<String>, String), Vec<String>>,
143}
144
145impl Model {
146 pub fn new() -> Self {
147 Self::default()
148 }
149
150 pub fn add(&mut self, resource: &Value) -> bool {
153 if resource["resourceType"] != "StructureDefinition"
154 || resource["derivation"] == "constraint"
155 {
156 return false;
157 }
158 let Some(url) = resource["url"].as_str() else {
159 return false;
160 };
161 let Some(snapshot) = resource["snapshot"]["element"].as_array() else {
162 return false;
163 };
164 let Some(root) = snapshot.first() else {
165 return false;
166 };
167 let root_path = root["path"].as_str().unwrap_or_default();
168 let namespace = namespace_of(resource);
169 let xml_name = extension_str(resource, XML_NAME).map(str::to_owned);
170 let mut elements: Vec<Element> = Vec::new();
171 for definition in &snapshot[1..] {
172 let Some(path) = definition["path"].as_str() else {
173 continue;
174 };
175 if definition["sliceName"].is_string() {
177 continue;
178 }
179 let Some(relative) = path
180 .strip_prefix(root_path)
181 .and_then(|p| p.strip_prefix('.'))
182 else {
183 continue;
184 };
185 let segments: Vec<&str> = relative.split('.').collect();
186 let element = element_of(definition, segments[segments.len() - 1], &namespace);
187 insert(&mut elements, &segments[..segments.len() - 1], element);
188 }
189 let name = resource["name"].as_str().unwrap_or(root_path).to_owned();
190 let def = TypeDef {
191 url: url.to_owned(),
192 name: name.clone(),
193 namespace: namespace.clone(),
194 xml_name: xml_name.clone(),
195 base: resource["baseDefinition"].as_str().map(str::to_owned),
196 is_abstract: resource["abstract"].as_bool().unwrap_or(false),
197 elements,
198 };
199 self.by_name
200 .insert((namespace.clone(), name.clone()), url.to_owned());
201 self.by_bare_name
202 .entry(name)
203 .or_default()
204 .push(url.to_owned());
205 if let Some(xml_name) = xml_name
206 && !def.is_abstract
207 {
208 self.roots
209 .entry((namespace, xml_name))
210 .or_default()
211 .push(url.to_owned());
212 }
213 self.types.insert(url.to_owned(), Arc::new(def));
214 true
215 }
216
217 pub fn add_folder(&mut self, folder: &std::path::Path) -> std::io::Result<usize> {
219 let mut added = 0;
220 let mut paths: Vec<_> = std::fs::read_dir(folder)?
221 .filter_map(|entry| entry.ok().map(|e| e.path()))
222 .filter(|path| path.extension().is_some_and(|e| e == "json"))
223 .collect();
224 paths.sort();
225 for path in paths {
226 let Ok(text) = std::fs::read_to_string(&path) else {
227 continue;
228 };
229 let Ok(resource) = serde_json::from_str::<Value>(&text) else {
230 continue;
231 };
232 if self.add(&resource) {
233 added += 1;
234 }
235 }
236 Ok(added)
237 }
238
239 pub fn len(&self) -> usize {
240 self.types.len()
241 }
242
243 pub fn types(&self) -> impl Iterator<Item = &Arc<TypeDef>> {
245 self.types.values()
246 }
247
248 pub fn is_empty(&self) -> bool {
249 self.types.is_empty()
250 }
251
252 pub fn get(&self, url: &str) -> Option<&Arc<TypeDef>> {
254 self.types.get(url.split('|').next().unwrap_or(url))
255 }
256
257 pub fn contains(&self, url: &str) -> bool {
259 self.get(url).is_some()
260 }
261
262 pub fn by_type_name(&self, namespace: Option<&str>, name: &str) -> Option<&Arc<TypeDef>> {
264 if let Some(url) = self
265 .by_name
266 .get(&(namespace.map(str::to_owned), name.to_owned()))
267 {
268 return self.get(url);
269 }
270 match self.by_bare_name.get(name).map(Vec::as_slice) {
271 Some([single]) => self.get(single),
272 _ => None,
273 }
274 }
275
276 pub fn root(&self, spec: &str) -> Option<Root<'_>> {
278 if let Some(ty) = self.get(spec).or_else(|| self.by_type_name(None, spec)) {
279 return Some(Root::Type(ty));
280 }
281 let (ty, element) = spec.rsplit_once('.')?;
282 let ty = self.get(ty).or_else(|| self.by_type_name(None, ty))?;
283 let element = ty.element(element)?;
284 Some(Root::Element(ty, element))
285 }
286
287 pub fn roots(&self, namespace: Option<&str>, name: &str) -> Vec<&Arc<TypeDef>> {
289 self.roots
290 .get(&(namespace.map(str::to_owned), name.to_owned()))
291 .map(|urls| urls.iter().filter_map(|url| self.get(url)).collect())
292 .unwrap_or_default()
293 }
294
295 pub fn type_of(&self, code: &str, context: &str) -> Option<&Arc<TypeDef>> {
298 if let Some(found) = self.get(code) {
299 return Some(found);
300 }
301 let prefix = context.rsplit_once('/').map(|(p, _)| p)?;
302 self.get(&format!("{prefix}/{code}"))
303 }
304
305 pub fn derives_from(&self, ty: &TypeDef, ancestor: &str) -> bool {
307 let mut current = Some(ty.url.clone());
308 for _ in 0..32 {
309 let Some(url) = current else { return false };
310 if url == ancestor {
311 return true;
312 }
313 current = self.get(&url).and_then(|t| t.base.clone());
314 }
315 false
316 }
317}
318
319fn insert(elements: &mut Vec<Element>, parents: &[&str], element: Element) {
320 match parents.split_first() {
321 None => elements.push(element),
322 Some((first, rest)) => {
323 if let Some(parent) = elements.iter_mut().rev().find(|e| e.name == *first) {
324 insert(&mut parent.children, rest, element);
325 }
326 }
327 }
328}
329
330fn element_of(definition: &Value, segment: &str, type_namespace: &Option<String>) -> Element {
331 let name = segment.trim_end_matches("[x]").to_owned();
332 let explicit = XML_NAMESPACE
333 .iter()
334 .find_map(|url| extension_str(definition, url))
335 .map(str::to_owned);
336 let representation = definition["representation"]
337 .as_array()
338 .map(|codes| codes.iter().filter_map(Value::as_str).collect::<Vec<_>>())
339 .unwrap_or_default();
340 let has = |code: &str| representation.contains(&code);
341 let kind = if has("xmlAttr") {
342 Representation::Attribute
343 } else if has("xmlText") {
344 Representation::Text
345 } else if has("cdaText") || has("xhtml") {
346 Representation::Markup
347 } else {
348 Representation::Element
349 };
350 let types = definition["type"]
351 .as_array()
352 .map(|types| {
353 types
354 .iter()
355 .filter_map(|t| t["code"].as_str().map(str::to_owned))
356 .collect()
357 })
358 .unwrap_or_default();
359 let many = match definition["max"].as_str() {
360 Some("*") => true,
361 Some(max) => max.parse::<u32>().is_ok_and(|m| m > 1),
362 None => false,
363 };
364 Element {
365 xml_name: extension_str(definition, XML_NAME)
366 .map(str::to_owned)
367 .unwrap_or_else(|| name.clone()),
368 name,
369 namespace: explicit.clone().or_else(|| type_namespace.clone()),
370 explicit_namespace: explicit.is_some(),
371 representation: kind,
372 type_attribute: has("typeAttr"),
373 types,
374 default_type: extension_str(definition, DEFAULT_TYPE).map(str::to_owned),
375 many,
376 choice_group: definition["extension"].as_array().is_some_and(|exts| {
377 exts.iter()
378 .any(|e| e["url"] == CHOICE_GROUP && e["valueBoolean"] == true)
379 }),
380 date_format: extension_str(definition, DATE_FORMAT).is_some(),
381 children: Vec::new(),
382 }
383}
384
385fn namespace_of(resource: &Value) -> Option<String> {
386 XML_NAMESPACE
387 .iter()
388 .find_map(|url| extension_str(resource, url))
389 .map(str::to_owned)
390}
391
392fn extension_str<'a>(holder: &'a Value, url: &str) -> Option<&'a str> {
394 holder["extension"].as_array()?.iter().find_map(|ext| {
395 if ext["url"] != url {
396 return None;
397 }
398 [
399 "valueUri",
400 "valueString",
401 "valueCode",
402 "valueCanonical",
403 "valueUrl",
404 ]
405 .iter()
406 .find_map(|key| ext[*key].as_str())
407 })
408}