mod generated;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BindingStrength {
Required,
Extensible,
Preferred,
Example,
}
impl BindingStrength {
#[must_use]
pub fn from_token(token: &str) -> Self {
match token {
"required" => Self::Required,
"extensible" => Self::Extensible,
"preferred" => Self::Preferred,
_ => Self::Example,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BindingMeta {
pub strength: BindingStrength,
pub value_set: Option<&'static str>,
}
#[derive(Debug, Clone, Copy)]
pub struct TypeRef {
pub code: &'static str,
pub target_profiles: &'static [&'static str],
}
impl TypeRef {
pub fn target_names(&self) -> impl Iterator<Item = &'static str> {
self.target_profiles
.iter()
.map(|url| url.rsplit(['/', '#']).next().unwrap_or(url))
}
}
#[derive(Debug, Clone, Copy)]
pub struct ElementMeta {
pub path: &'static str,
pub min: u32,
pub max: &'static str,
pub is_summary: bool,
pub binding: Option<BindingMeta>,
pub types: &'static [TypeRef],
}
impl ElementMeta {
#[must_use]
pub fn is_required(&self) -> bool {
self.min >= 1
}
#[must_use]
pub fn is_multiple(&self) -> bool {
self.max != "0" && self.max != "1"
}
#[must_use]
pub fn is_choice(&self) -> bool {
self.path.ends_with("[x]")
}
pub fn type_codes(&self) -> impl Iterator<Item = &'static str> {
self.types.iter().map(|t| t.code)
}
}
#[must_use]
pub fn element(path: &str) -> Option<&'static ElementMeta> {
generated::ELEMENTS
.binary_search_by(|e| e.path.cmp(path))
.ok()
.map(|i| &generated::ELEMENTS[i])
}
#[must_use]
pub fn elements() -> &'static [ElementMeta] {
generated::ELEMENTS
}
pub fn elements_of(type_name: &str) -> impl Iterator<Item = &'static ElementMeta> {
let prefix = format!("{type_name}.");
generated::ELEMENTS
.iter()
.filter(move |e| e.path.starts_with(&prefix))
}
#[must_use]
pub fn struct_prefix(struct_name: &str) -> Option<&'static str> {
use ::convert_case::{Case, Casing};
use std::collections::HashMap;
use std::sync::LazyLock;
static PREFIXES: LazyLock<HashMap<String, &'static str>> = LazyLock::new(|| {
let mut map = HashMap::new();
for e in generated::ELEMENTS {
let seg_count = e.path.split('.').count();
for take in 1..seg_count {
let name: String =
e.path.split('.').take(take).map(|s| s.to_case(Case::Pascal)).collect();
if let Some((end, _)) = e.path.match_indices('.').nth(take - 1) {
map.entry(name).or_insert(&e.path[..end]);
}
}
}
map
});
PREFIXES.get(struct_name).copied()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table_is_non_empty_and_sorted() {
let all = elements();
assert!(all.len() > 5000, "expected thousands of elements, got {}", all.len());
assert!(
all.windows(2).all(|w| w[0].path <= w[1].path),
"ELEMENTS must be sorted by path for binary search"
);
}
#[test]
fn patient_gender_binding() {
let gender = element("Patient.gender").expect("Patient.gender");
let binding = gender.binding.expect("gender has a binding");
assert_eq!(binding.strength, BindingStrength::Required);
assert!(
binding.value_set.unwrap().contains("administrative-gender"),
"value_set was {:?}",
binding.value_set
);
}
#[test]
fn observation_value_choice_types() {
let value = element("Observation.value[x]").expect("Observation.value[x]");
assert!(value.is_choice());
assert_eq!(value.types.len(), 13, "R5 Observation.value[x] type count");
for code in ["Quantity", "CodeableConcept", "string", "Attachment", "Reference"] {
assert!(value.type_codes().any(|c| c == code), "missing type {code}");
}
}
#[test]
fn observation_subject_targets() {
let subject = element("Observation.subject").expect("Observation.subject");
let targets: Vec<&str> = subject
.types
.iter()
.flat_map(TypeRef::target_names)
.collect();
for want in ["Patient", "Group", "Device", "Location"] {
assert!(targets.contains(&want), "subject should target {want}: {targets:?}");
}
}
#[test]
fn cardinality_helpers() {
let status = element("Observation.status").expect("Observation.status");
assert!(status.is_required());
assert!(!status.is_multiple());
let identifier = element("Observation.identifier").expect("Observation.identifier");
assert!(!identifier.is_required());
assert!(identifier.is_multiple());
}
#[test]
fn elements_of_a_type() {
let count = elements_of("Patient").count();
assert!(count > 20, "Patient should have many elements, got {count}");
assert!(elements_of("Patient").all(|e| e.path.starts_with("Patient.")));
}
}