use std::collections::HashMap;
use std::sync::Arc;
use serde_json::Value;
const XML_NAME: &str = "http://hl7.org/fhir/tools/StructureDefinition/xml-name";
const XML_NAMESPACE: &[&str] = &[
"http://hl7.org/fhir/tools/StructureDefinition/xml-namespace",
"http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace",
];
const CHOICE_GROUP: &str = "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group";
const DEFAULT_TYPE: &str = "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype";
const DATE_FORMAT: &str =
"http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-format";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Representation {
Element,
Attribute,
Text,
Markup,
}
#[derive(Debug, Clone)]
pub struct Element {
pub name: String,
pub xml_name: String,
pub namespace: Option<String>,
pub explicit_namespace: bool,
pub representation: Representation,
pub type_attribute: bool,
pub types: Vec<String>,
pub default_type: Option<String>,
pub many: bool,
pub choice_group: bool,
pub date_format: bool,
pub children: Vec<Element>,
}
impl Element {
pub fn single_type(&self) -> Option<&str> {
match self.types.as_slice() {
[single] => Some(single),
_ => None,
}
}
pub fn child(&self, name: &str) -> Option<&Element> {
self.children.iter().find(|c| c.name == name)
}
}
#[derive(Debug, Clone)]
pub struct TypeDef {
pub url: String,
pub name: String,
pub namespace: Option<String>,
pub xml_name: Option<String>,
pub base: Option<String>,
pub is_abstract: bool,
pub elements: Vec<Element>,
}
impl TypeDef {
pub fn element(&self, name: &str) -> Option<&Element> {
self.elements.iter().find(|e| e.name == name)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Root<'m> {
Type(&'m TypeDef),
Element(&'m TypeDef, &'m Element),
}
impl<'m> Root<'m> {
pub fn spec(&self) -> String {
match self {
Root::Type(ty) => ty.url.clone(),
Root::Element(ty, element) => format!("{}.{}", ty.url, element.name),
}
}
pub fn path(&self) -> String {
match self {
Root::Type(ty) => ty.name.clone(),
Root::Element(ty, element) => format!("{}.{}", ty.name, element.name),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct Model {
types: HashMap<String, Arc<TypeDef>>,
by_name: HashMap<(Option<String>, String), String>,
by_bare_name: HashMap<String, Vec<String>>,
roots: HashMap<(Option<String>, String), Vec<String>>,
}
impl Model {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, resource: &Value) -> bool {
if resource["resourceType"] != "StructureDefinition"
|| resource["derivation"] == "constraint"
{
return false;
}
let Some(url) = resource["url"].as_str() else {
return false;
};
let Some(snapshot) = resource["snapshot"]["element"].as_array() else {
return false;
};
let Some(root) = snapshot.first() else {
return false;
};
let root_path = root["path"].as_str().unwrap_or_default();
let namespace = namespace_of(resource);
let xml_name = extension_str(resource, XML_NAME).map(str::to_owned);
let mut elements: Vec<Element> = Vec::new();
for definition in &snapshot[1..] {
let Some(path) = definition["path"].as_str() else {
continue;
};
if definition["sliceName"].is_string() {
continue;
}
let Some(relative) = path
.strip_prefix(root_path)
.and_then(|p| p.strip_prefix('.'))
else {
continue;
};
let segments: Vec<&str> = relative.split('.').collect();
let element = element_of(definition, segments[segments.len() - 1], &namespace);
insert(&mut elements, &segments[..segments.len() - 1], element);
}
let name = resource["name"].as_str().unwrap_or(root_path).to_owned();
let def = TypeDef {
url: url.to_owned(),
name: name.clone(),
namespace: namespace.clone(),
xml_name: xml_name.clone(),
base: resource["baseDefinition"].as_str().map(str::to_owned),
is_abstract: resource["abstract"].as_bool().unwrap_or(false),
elements,
};
self.by_name
.insert((namespace.clone(), name.clone()), url.to_owned());
self.by_bare_name
.entry(name)
.or_default()
.push(url.to_owned());
if let Some(xml_name) = xml_name
&& !def.is_abstract
{
self.roots
.entry((namespace, xml_name))
.or_default()
.push(url.to_owned());
}
self.types.insert(url.to_owned(), Arc::new(def));
true
}
pub fn add_folder(&mut self, folder: &std::path::Path) -> std::io::Result<usize> {
let mut added = 0;
let mut paths: Vec<_> = std::fs::read_dir(folder)?
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter(|path| path.extension().is_some_and(|e| e == "json"))
.collect();
paths.sort();
for path in paths {
let Ok(text) = std::fs::read_to_string(&path) else {
continue;
};
let Ok(resource) = serde_json::from_str::<Value>(&text) else {
continue;
};
if self.add(&resource) {
added += 1;
}
}
Ok(added)
}
pub fn len(&self) -> usize {
self.types.len()
}
pub fn types(&self) -> impl Iterator<Item = &Arc<TypeDef>> {
self.types.values()
}
pub fn is_empty(&self) -> bool {
self.types.is_empty()
}
pub fn get(&self, url: &str) -> Option<&Arc<TypeDef>> {
self.types.get(url.split('|').next().unwrap_or(url))
}
pub fn contains(&self, url: &str) -> bool {
self.get(url).is_some()
}
pub fn by_type_name(&self, namespace: Option<&str>, name: &str) -> Option<&Arc<TypeDef>> {
if let Some(url) = self
.by_name
.get(&(namespace.map(str::to_owned), name.to_owned()))
{
return self.get(url);
}
match self.by_bare_name.get(name).map(Vec::as_slice) {
Some([single]) => self.get(single),
_ => None,
}
}
pub fn root(&self, spec: &str) -> Option<Root<'_>> {
if let Some(ty) = self.get(spec).or_else(|| self.by_type_name(None, spec)) {
return Some(Root::Type(ty));
}
let (ty, element) = spec.rsplit_once('.')?;
let ty = self.get(ty).or_else(|| self.by_type_name(None, ty))?;
let element = ty.element(element)?;
Some(Root::Element(ty, element))
}
pub fn roots(&self, namespace: Option<&str>, name: &str) -> Vec<&Arc<TypeDef>> {
self.roots
.get(&(namespace.map(str::to_owned), name.to_owned()))
.map(|urls| urls.iter().filter_map(|url| self.get(url)).collect())
.unwrap_or_default()
}
pub fn type_of(&self, code: &str, context: &str) -> Option<&Arc<TypeDef>> {
if let Some(found) = self.get(code) {
return Some(found);
}
let prefix = context.rsplit_once('/').map(|(p, _)| p)?;
self.get(&format!("{prefix}/{code}"))
}
pub fn derives_from(&self, ty: &TypeDef, ancestor: &str) -> bool {
let mut current = Some(ty.url.clone());
for _ in 0..32 {
let Some(url) = current else { return false };
if url == ancestor {
return true;
}
current = self.get(&url).and_then(|t| t.base.clone());
}
false
}
}
fn insert(elements: &mut Vec<Element>, parents: &[&str], element: Element) {
match parents.split_first() {
None => elements.push(element),
Some((first, rest)) => {
if let Some(parent) = elements.iter_mut().rev().find(|e| e.name == *first) {
insert(&mut parent.children, rest, element);
}
}
}
}
fn element_of(definition: &Value, segment: &str, type_namespace: &Option<String>) -> Element {
let name = segment.trim_end_matches("[x]").to_owned();
let explicit = XML_NAMESPACE
.iter()
.find_map(|url| extension_str(definition, url))
.map(str::to_owned);
let representation = definition["representation"]
.as_array()
.map(|codes| codes.iter().filter_map(Value::as_str).collect::<Vec<_>>())
.unwrap_or_default();
let has = |code: &str| representation.contains(&code);
let kind = if has("xmlAttr") {
Representation::Attribute
} else if has("xmlText") {
Representation::Text
} else if has("cdaText") || has("xhtml") {
Representation::Markup
} else {
Representation::Element
};
let types = definition["type"]
.as_array()
.map(|types| {
types
.iter()
.filter_map(|t| t["code"].as_str().map(str::to_owned))
.collect()
})
.unwrap_or_default();
let many = match definition["max"].as_str() {
Some("*") => true,
Some(max) => max.parse::<u32>().is_ok_and(|m| m > 1),
None => false,
};
Element {
xml_name: extension_str(definition, XML_NAME)
.map(str::to_owned)
.unwrap_or_else(|| name.clone()),
name,
namespace: explicit.clone().or_else(|| type_namespace.clone()),
explicit_namespace: explicit.is_some(),
representation: kind,
type_attribute: has("typeAttr"),
types,
default_type: extension_str(definition, DEFAULT_TYPE).map(str::to_owned),
many,
choice_group: definition["extension"].as_array().is_some_and(|exts| {
exts.iter()
.any(|e| e["url"] == CHOICE_GROUP && e["valueBoolean"] == true)
}),
date_format: extension_str(definition, DATE_FORMAT).is_some(),
children: Vec::new(),
}
}
fn namespace_of(resource: &Value) -> Option<String> {
XML_NAMESPACE
.iter()
.find_map(|url| extension_str(resource, url))
.map(str::to_owned)
}
fn extension_str<'a>(holder: &'a Value, url: &str) -> Option<&'a str> {
holder["extension"].as_array()?.iter().find_map(|ext| {
if ext["url"] != url {
return None;
}
[
"valueUri",
"valueString",
"valueCode",
"valueCanonical",
"valueUrl",
]
.iter()
.find_map(|key| ext[*key].as_str())
})
}