use std::sync::Arc;
use crate::definitions::{Definitions, Element, Kind, Structure, same_primitive, type_url};
#[derive(Debug, Clone)]
pub enum Type {
Unknown,
Node {
structure: Arc<Structure>,
path: Vec<String>,
},
}
impl Type {
pub fn key(&self) -> String {
match self {
Type::Unknown => "?".to_owned(),
Type::Node { structure, path } => {
let mut key = structure.url.clone();
for segment in path {
key.push('.');
key.push_str(segment);
}
key
}
}
}
pub fn display(&self) -> String {
match self {
Type::Unknown => "?".to_owned(),
Type::Node { structure, path } => {
let mut name = structure.type_name.clone();
for segment in path {
name.push('.');
name.push_str(segment);
}
name
}
}
}
pub fn fhir_type(&self) -> Option<String> {
match self {
Type::Unknown => None,
Type::Node { structure, path } if path.is_empty() => Some(structure.type_name.clone()),
Type::Node { .. } => Some("BackboneElement".to_owned()),
}
}
pub fn structure(&self) -> Option<&Arc<Structure>> {
match self {
Type::Unknown => None,
Type::Node { structure, .. } => Some(structure),
}
}
pub fn is_resource(&self) -> bool {
matches!(self, Type::Node { structure, path } if path.is_empty() && structure.kind == Kind::Resource)
}
pub fn is_primitive(&self) -> bool {
matches!(self, Type::Node { structure, path } if path.is_empty() && structure.kind == Kind::PrimitiveType)
}
}
#[derive(Debug, Clone)]
pub struct ElementRef {
pub name: String,
pub element: Element,
pub owner: Arc<Structure>,
pub path: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum Lookup {
Found(Box<ElementRef>),
Missing,
Unknown,
}
#[derive(Clone, Copy)]
pub struct Types<'a> {
pub definitions: &'a dyn Definitions,
}
impl<'a> Types<'a> {
pub fn new(definitions: &'a dyn Definitions) -> Self {
Self { definitions }
}
pub fn structure(&self, url: &str) -> Type {
match self.definitions.structure(url) {
Some(structure) => Type::Node {
structure,
path: Vec::new(),
},
None => Type::Unknown,
}
}
pub fn lookup(&self, ty: &Type, name: &str) -> Lookup {
let Type::Node { structure, path } = ty else {
return Lookup::Unknown;
};
if path.is_empty() {
return self.lookup_in_structure(structure.clone(), name, 0);
}
let Some(node) = self.element_at(structure, path) else {
return Lookup::Unknown;
};
if let Some(element) = node.children.get(name) {
let mut element_path = path.clone();
element_path.push(name.to_owned());
return Lookup::Found(Box::new(ElementRef {
name: name.to_owned(),
element: element.clone(),
owner: structure.clone(),
path: element_path,
}));
}
match node.types.first() {
Some(code) => match self.definitions.structure(&type_url(code, &structure.url)) {
Some(base) => self.lookup_in_structure(base, name, 0),
None => Lookup::Unknown,
},
None => Lookup::Unknown,
}
}
fn lookup_in_structure(&self, structure: Arc<Structure>, name: &str, depth: usize) -> Lookup {
if structure.kind == Kind::PrimitiveType && name == "value" {
return Lookup::Found(Box::new(ElementRef {
name: name.to_owned(),
element: Element::default(),
owner: structure.clone(),
path: vec![name.to_owned()],
}));
}
if let Some(element) = structure.elements.get(name) {
return Lookup::Found(Box::new(ElementRef {
name: name.to_owned(),
element: element.clone(),
owner: structure.clone(),
path: vec![name.to_owned()],
}));
}
let Some(base) = &structure.base else {
return Lookup::Missing;
};
if depth > 16 {
return Lookup::Unknown;
}
match self.definitions.structure(base) {
Some(base) => self.lookup_in_structure(base, name, depth + 1),
None => Lookup::Unknown,
}
}
fn element_at(&self, structure: &Structure, path: &[String]) -> Option<Element> {
let (first, rest) = path.split_first()?;
let mut current = self.resolve_reference(structure.elements.get(first)?)?;
for segment in rest {
current = self.resolve_reference(current.children.get(segment)?)?;
}
Some(current)
}
fn resolve_reference(&self, element: &Element) -> Option<Element> {
match &element.content_reference {
Some((url, target)) => {
let referenced = self.definitions.structure(url)?;
self.element_at(&referenced, target)
}
None => Some(element.clone()),
}
}
pub fn element_type(&self, element: &ElementRef, chosen: Option<&str>) -> Type {
if let Some((url, target)) = &element.element.content_reference {
return match self.definitions.structure(url) {
Some(structure) => Type::Node {
structure,
path: target.clone(),
},
None => Type::Unknown,
};
}
if !element.element.children.is_empty() {
return Type::Node {
structure: element.owner.clone(),
path: element.path.clone(),
};
}
let code = match (chosen, element.element.types.as_slice()) {
(Some(chosen), _) => chosen.to_owned(),
(None, [single]) => single.clone(),
_ => return Type::Unknown,
};
self.structure(&type_url(&code, &element.owner.url))
}
pub fn is_one_of(&self, chosen: &str, types: &[String], context: &str) -> bool {
let matches = |name: &str| {
types
.iter()
.any(|t| same_primitive(t, name) || t == name || t.ends_with(&format!("/{name}")))
};
if matches(chosen) {
return true;
}
let mut url = type_url(chosen, context);
for _ in 0..16 {
let Some(structure) = self.definitions.structure(&url) else {
return !self.definitions.covers(&url);
};
if matches(&structure.type_name) {
return true;
}
match &structure.base {
Some(base) => url = base.clone(),
None => return false,
}
}
true
}
}