use fhir_types::codec::Object;
use fhir_types::codec::Value;
use fhir_types::schema::FieldSchema;
use fhir_types::schema::Kind;
use fhir_types::schema::Schemas;
use fhir_types::schema::TypeSchema;
use crate::issue::IssueType;
use crate::issue::MessageId;
use crate::issue::Severity;
use crate::release::Release;
use crate::report::Reason;
use crate::validate::resolver::Resolution;
use crate::validate::slicing::path::Step;
use crate::validate::walk::Form;
use crate::validate::walk::Occurrence;
use crate::validate::walk::Walk;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Node<'i> {
pub(crate) value: Option<&'i Value>,
pub(crate) element: Option<&'i Value>,
pub(crate) kind: NodeKind,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum NodeKind {
Primitive(&'static str),
Complex(&'static TypeSchema),
Resource(&'static TypeSchema),
}
impl<'i> Node<'i> {
pub(crate) fn item(
schemas: &'static Schemas,
field: &'static FieldSchema,
form: &Form,
occurrence: &Occurrence<'i>,
) -> Option<Self> {
let code = form.choice.or_else(|| field.types.first().copied());
node(
schemas,
form.kind,
code,
occurrence.value,
occurrence.element,
)
}
pub(crate) const fn type_name(&self) -> &'static str {
match self.kind {
NodeKind::Primitive(code) => code,
NodeKind::Complex(schema) | NodeKind::Resource(schema) => schema.name,
}
}
fn object(&self) -> Option<&'i Object> {
match self.kind {
NodeKind::Primitive(_) => self.element.and_then(Value::as_object),
NodeKind::Complex(_) | NodeKind::Resource(_) => self.value.and_then(Value::as_object),
}
}
}
fn node<'i>(
schemas: &'static Schemas,
kind: Kind,
code: Option<&'static str>,
value: Option<&'i Value>,
element: Option<&'i Value>,
) -> Option<Node<'i>> {
let kind = match kind {
Kind::Attribute | Kind::Primitive(_) => NodeKind::Primitive(code?),
Kind::Xhtml => NodeKind::Primitive("xhtml"),
Kind::Complex(name) => NodeKind::Complex(schemas.type_named(name)?),
Kind::Resource => {
let name = value?.get("resourceType")?.as_str()?;
NodeKind::Resource(
schemas
.is_resource(name)
.then(|| schemas.type_named(name))??,
)
}
Kind::Choice(_) => return None,
};
Some(Node {
value,
element,
kind,
})
}
fn members<'i>(
schemas: &'static Schemas,
object: &'i Object,
key: &str,
kind: Kind,
code: Option<&'static str>,
many: bool,
) -> Vec<Node<'i>> {
let value = object.get(key);
let element = if matches!(kind, Kind::Primitive(_)) {
object.get(&format!("_{key}"))
} else {
None
};
if !many {
if value.is_none() && element.is_none() {
return Vec::new();
}
return node(schemas, kind, code, value, element)
.into_iter()
.collect();
}
let values = value.and_then(Value::as_array).unwrap_or_default();
let elements = element.and_then(Value::as_array).unwrap_or_default();
(0..values.len().max(elements.len()))
.filter_map(|index| {
let value = values.get(index).filter(|item| !item.is_null());
let element = elements.get(index).filter(|item| !item.is_null());
node(schemas, kind, code, value, element)
})
.collect()
}
fn children<'i>(schemas: &'static Schemas, node: &Node<'i>, name: &str) -> Vec<Node<'i>> {
let Some(object) = node.object() else {
return Vec::new();
};
let schema = match node.kind {
NodeKind::Complex(schema) | NodeKind::Resource(schema) => schema,
NodeKind::Primitive(_) => {
return match (name, schemas.type_named("Extension")) {
("extension", Some(extension)) => object
.get("extension")
.and_then(Value::as_array)
.unwrap_or_default()
.iter()
.map(|item| Node {
value: Some(item),
element: None,
kind: NodeKind::Complex(extension),
})
.collect(),
_ => Vec::new(),
};
}
};
let Some(field) = schema.fields.iter().find(|field| field.name == name) else {
return Vec::new();
};
match field.kind {
Kind::Choice(_) => field
.choices()
.flat_map(|choice| {
members(
schemas,
object,
&format!("{}{}", field.name, choice.suffix),
choice.kind,
Some(choice.code),
field.many,
)
})
.collect(),
kind => members(
schemas,
object,
field.name,
kind,
field.types.first().copied(),
field.many,
),
}
}
pub(super) fn is_type(schemas: &Schemas, node: &Node<'_>, name: &str) -> bool {
node.type_name() == name
|| (matches!(node.kind, NodeKind::Resource(_))
&& schemas.specializes(node.type_name(), name))
}
impl<'i, R: Release> Walk<'_, '_, 'i, R> {
pub(crate) fn follow(
&mut self,
start: Node<'i>,
steps: &[Step],
path: &str,
location: &str,
) -> Result<Vec<Node<'i>>, Reason> {
let mut nodes = vec![start];
for step in steps {
nodes = match step {
Step::Member(name) => nodes
.iter()
.flat_map(|node| children(R::schemas(), node, name))
.collect(),
Step::Extension(url) => nodes
.iter()
.flat_map(|node| children(R::schemas(), node, "extension"))
.filter(|extension| {
extension
.value
.and_then(|value| value.get("url"))
.and_then(Value::as_str)
== Some(url.as_str())
})
.collect(),
Step::OfType(name) => nodes
.into_iter()
.filter(|node| is_type(R::schemas(), node, name))
.collect(),
Step::Resolve => {
let mut resolved = Vec::new();
for node in &nodes {
resolved.push(self.resolve(node, path, location)?);
}
resolved
}
};
}
Ok(nodes)
}
fn resolve(&mut self, node: &Node<'i>, path: &str, location: &str) -> Result<Node<'i>, Reason> {
let reference = match node.kind {
NodeKind::Complex(schema) if schema.name == "Reference" => node
.value
.and_then(|value| value.get("reference"))
.and_then(Value::as_str),
_ => None,
};
let Some(reference) = reference else {
return Err(Reason::ReferenceOutOfScope);
};
match self.resolve_reference(reference) {
Resolution::Found { target, .. } => {
node_of_resource(R::schemas(), target).ok_or(Reason::UnresolvedReference)
}
Resolution::OutOfScope => Err(Reason::ReferenceOutOfScope),
Resolution::Ambiguous => Err(Reason::AmbiguousReference),
Resolution::Missing => {
self.out().issue(
Severity::Warning,
IssueType::NotFound,
MessageId::UnresolvedReference,
location,
format!(
"the reference resolves to nothing, so the discriminator {path} is not evaluated"
),
);
Err(Reason::UnresolvedReference)
}
}
}
}
fn node_of_resource<'i>(schemas: &'static Schemas, value: &'i Value) -> Option<Node<'i>> {
node(schemas, Kind::Resource, None, Some(value), None)
}