use std::collections::HashMap;
#[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 find(table: &'static [ElementMeta], path: &str) -> Option<&'static ElementMeta> {
table.binary_search_by(|e| e.path.cmp(path)).ok().map(|i| &table[i])
}
#[must_use]
pub fn struct_prefixes(table: &'static [ElementMeta]) -> HashMap<String, &'static str> {
use ::convert_case::{Case, Casing};
let mut map = HashMap::new();
for e in table {
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
}
#[cfg(test)]
mod tests {
use super::*;
static TABLE: &[ElementMeta] = &[
ElementMeta { path: "Patient.active", min: 0, max: "1", is_summary: true, binding: None, types: &[TypeRef { code: "boolean", target_profiles: &[] }] },
ElementMeta { path: "Patient.contact", min: 0, max: "*", is_summary: false, binding: None, types: &[TypeRef { code: "BackboneElement", target_profiles: &[] }] },
ElementMeta { path: "Patient.contact.name", min: 0, max: "1", is_summary: false, binding: None, types: &[TypeRef { code: "HumanName", target_profiles: &[] }] },
ElementMeta { path: "Patient.link.other", min: 1, max: "1", is_summary: false, binding: None, types: &[TypeRef { code: "Reference", target_profiles: &["http://hl7.org/fhir/StructureDefinition/Patient"] }] },
];
#[test]
fn lookup_by_path() {
assert_eq!(find(TABLE, "Patient.active").unwrap().max, "1");
assert!(find(TABLE, "Patient.nope").is_none());
}
#[test]
fn cardinality_helpers() {
let active = find(TABLE, "Patient.active").unwrap();
assert!(!active.is_required());
assert!(!active.is_multiple());
let contact = find(TABLE, "Patient.contact").unwrap();
assert!(contact.is_multiple());
assert!(find(TABLE, "Patient.link.other").unwrap().is_required());
}
#[test]
fn target_names_strip_the_profile_url() {
let other = find(TABLE, "Patient.link.other").unwrap();
let names: Vec<&str> = other.types[0].target_names().collect();
assert_eq!(names, ["Patient"]);
}
#[test]
fn struct_names_map_back_to_paths() {
let prefixes = struct_prefixes(TABLE);
assert_eq!(prefixes.get("Patient").copied(), Some("Patient"));
assert_eq!(prefixes.get("PatientContact").copied(), Some("Patient.contact"));
}
#[test]
fn strength_tokens_parse() {
assert_eq!(BindingStrength::from_token("required"), BindingStrength::Required);
assert_eq!(BindingStrength::from_token("nonsense"), BindingStrength::Example);
}
}