use crate::evaluator::EvaluationContext;
use crate::fhir_type_hierarchy::{capitalize_first_letter, is_fhir_primitive_type};
use crate::parser::TypeSpecifier;
use helios_fhir::{FhirComplexTypeProvider, FhirResourceTypeProvider, FhirVersion};
use helios_fhirpath_support::{EvaluationError, EvaluationResult};
pub fn is_resource_type_for_version(type_name: &str, fhir_version: &FhirVersion) -> bool {
match fhir_version {
#[cfg(feature = "R4")]
FhirVersion::R4 => helios_fhir::r4::Resource::is_resource_type(type_name),
#[cfg(feature = "R4B")]
FhirVersion::R4B => helios_fhir::r4b::Resource::is_resource_type(type_name),
#[cfg(feature = "R5")]
FhirVersion::R5 => helios_fhir::r5::Resource::is_resource_type(type_name),
#[cfg(feature = "R6")]
FhirVersion::R6 => helios_fhir::r6::Resource::is_resource_type(type_name),
#[allow(unreachable_patterns)]
_ => false, }
}
pub fn is_complex_type_for_version(type_name: &str, fhir_version: &FhirVersion) -> bool {
match fhir_version {
#[cfg(feature = "R4")]
FhirVersion::R4 => helios_fhir::r4::ComplexTypes::is_complex_type(type_name),
#[cfg(feature = "R4B")]
FhirVersion::R4B => helios_fhir::r4b::ComplexTypes::is_complex_type(type_name),
#[cfg(feature = "R5")]
FhirVersion::R5 => helios_fhir::r5::ComplexTypes::is_complex_type(type_name),
#[cfg(feature = "R6")]
FhirVersion::R6 => helios_fhir::r6::ComplexTypes::is_complex_type(type_name),
#[allow(unreachable_patterns)]
_ => false, }
}
pub fn is_of_type_with_context(
value: &EvaluationResult,
type_spec: &TypeSpecifier,
context: &EvaluationContext,
) -> Result<bool, EvaluationError> {
let (target_namespace, target_type) =
extract_namespace_and_type_with_context(type_spec, context)?;
match value {
EvaluationResult::Boolean(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Boolean",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Integer(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Integer",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Decimal(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Decimal",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::String(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"String",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Date(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Date",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::DateTime(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"DateTime",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Time(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Time",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Quantity(_, _, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Quantity",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Object { map, type_info, .. } => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else if let Some(resource_type_value) = map.get("resourceType") {
if let EvaluationResult::String(resource_type, _, _) = resource_type_value {
check_type_match(
&Some("FHIR".to_string()),
resource_type,
&target_namespace,
&target_type,
)
} else {
Ok(false)
}
} else {
check_type_match(
&Some("System".to_string()),
"Object",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Collection { type_info, .. } => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Collection",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Empty => {
Ok(false)
}
#[cfg(not(any(feature = "R4", feature = "R4B")))]
EvaluationResult::Integer64(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Integer64",
&target_namespace,
&target_type,
)
}
}
#[cfg(any(feature = "R4", feature = "R4B"))]
EvaluationResult::Integer64(_, _, _) => {
check_type_match(
&Some("System".to_string()),
"Integer",
&target_namespace,
&target_type,
)
}
}
}
#[allow(dead_code)]
pub fn is_of_type_for_of_type(
value: &EvaluationResult,
type_spec: &TypeSpecifier,
) -> Result<bool, EvaluationError> {
let (target_namespace, target_type) = extract_namespace_and_type_without_context(type_spec)?;
match value {
EvaluationResult::Boolean(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Boolean",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::Integer(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Integer",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::Decimal(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Decimal",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::String(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"String",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::Date(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Date",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::DateTime(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"DateTime",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::Time(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Time",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::Quantity(_, _, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Quantity",
&target_namespace,
&target_type,
true,
)
}
}
EvaluationResult::Collection { .. } => {
Ok(false)
}
EvaluationResult::Empty => {
Ok(false)
}
#[cfg(not(any(feature = "R4", feature = "R4B")))]
EvaluationResult::Integer64(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Integer64",
&target_namespace,
&target_type,
true,
)
}
}
#[cfg(any(feature = "R4", feature = "R4B"))]
EvaluationResult::Integer64(_, _, _) => {
check_type_match_with_cross_namespace(
&Some("System".to_string()),
"Integer",
&target_namespace,
&target_type,
true,
)
}
EvaluationResult::Object { map, type_info, .. } => {
if let Some(type_info) = type_info {
check_type_match_with_cross_namespace(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
true,
)
} else if let Some(resource_type_value) = map.get("resourceType") {
if let EvaluationResult::String(resource_type, _, _) = resource_type_value {
check_type_match_with_cross_namespace(
&Some("FHIR".to_string()),
resource_type,
&target_namespace,
&target_type,
true,
)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
}
}
#[allow(dead_code)]
pub fn is_of_type(
value: &EvaluationResult,
type_spec: &TypeSpecifier,
) -> Result<bool, EvaluationError> {
let (target_namespace, target_type) = extract_namespace_and_type_without_context(type_spec)?;
match value {
EvaluationResult::Boolean(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Boolean",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Integer(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Integer",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Decimal(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Decimal",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::String(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"String",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Date(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Date",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::DateTime(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"DateTime",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Time(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Time",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Quantity(_, _, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Quantity",
&target_namespace,
&target_type,
)
}
}
EvaluationResult::Collection { .. } => {
Ok(false)
}
EvaluationResult::Empty => {
Ok(false)
}
#[cfg(not(any(feature = "R4", feature = "R4B")))]
EvaluationResult::Integer64(_, type_info, _) => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else {
check_type_match(
&Some("System".to_string()),
"Integer64",
&target_namespace,
&target_type,
)
}
}
#[cfg(any(feature = "R4", feature = "R4B"))]
EvaluationResult::Integer64(_, _, _) => {
check_type_match(
&Some("System".to_string()),
"Integer",
&target_namespace,
&target_type,
)
}
EvaluationResult::Object { map, type_info, .. } => {
if let Some(type_info) = type_info {
check_type_match(
&Some(type_info.namespace.clone()),
&type_info.name,
&target_namespace,
&target_type,
)
} else if let Some(resource_type_value) = map.get("resourceType") {
if let EvaluationResult::String(resource_type, _, _) = resource_type_value {
check_type_match(
&Some("FHIR".to_string()),
resource_type,
&target_namespace,
&target_type,
)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
}
}
fn check_type_match(
value_namespace: &Option<String>,
value_type: &str,
target_namespace: &Option<String>,
target_type: &str,
) -> Result<bool, EvaluationError> {
if let (Some(value_ns), Some(target_ns)) = (value_namespace, target_namespace) {
if value_ns.eq_ignore_ascii_case("FHIR") && target_ns.eq_ignore_ascii_case("FHIR") {
#[cfg(feature = "R4")]
if helios_fhir::r4::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
#[cfg(feature = "R4B")]
if helios_fhir::r4b::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
#[cfg(feature = "R5")]
if helios_fhir::r5::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
#[cfg(feature = "R6")]
if helios_fhir::r6::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
}
}
if target_namespace.is_none() {
if let Some(ns) = value_namespace {
if ns.eq_ignore_ascii_case("FHIR") {
#[cfg(feature = "R4")]
if helios_fhir::r4::type_hierarchy::is_subtype_of(value_type, target_type) {
if target_type.eq_ignore_ascii_case("string") {
eprintln!("Matched FHIR string subtype: {} -> string", value_type);
}
return Ok(true);
}
#[cfg(feature = "R4B")]
if helios_fhir::r4b::type_hierarchy::is_subtype_of(value_type, target_type) {
if target_type.eq_ignore_ascii_case("string") {
eprintln!("Matched FHIR string subtype: {} -> string", value_type);
}
return Ok(true);
}
#[cfg(feature = "R5")]
if helios_fhir::r5::type_hierarchy::is_subtype_of(value_type, target_type) {
if target_type.eq_ignore_ascii_case("string") {
eprintln!("Matched FHIR string subtype: {} -> string", value_type);
}
return Ok(true);
}
#[cfg(feature = "R6")]
if helios_fhir::r6::type_hierarchy::is_subtype_of(value_type, target_type) {
if target_type.eq_ignore_ascii_case("string") {
eprintln!("Matched FHIR string subtype: {} -> string", value_type);
}
return Ok(true);
}
let direct_matches = [
("boolean", "boolean"),
("decimal", "decimal"),
("date", "date"),
("datetime", "datetime"),
("time", "time"),
("instant", "datetime"), ];
for (fhir_type, system_type) in &direct_matches {
if value_type.eq_ignore_ascii_case(fhir_type)
&& target_type.eq_ignore_ascii_case(system_type)
{
return Ok(true);
}
}
}
}
}
let allow_cross_namespace = if target_type.eq_ignore_ascii_case("quantity") {
#[cfg(feature = "R4")]
if helios_fhir::r4::type_hierarchy::is_subtype_of(value_type, "Quantity") {
return check_type_match_with_cross_namespace(
value_namespace,
value_type,
target_namespace,
target_type,
true,
);
}
#[cfg(feature = "R4B")]
if helios_fhir::r4b::type_hierarchy::is_subtype_of(value_type, "Quantity") {
return check_type_match_with_cross_namespace(
value_namespace,
value_type,
target_namespace,
target_type,
true,
);
}
#[cfg(feature = "R5")]
if helios_fhir::r5::type_hierarchy::is_subtype_of(value_type, "Quantity") {
return check_type_match_with_cross_namespace(
value_namespace,
value_type,
target_namespace,
target_type,
true,
);
}
#[cfg(feature = "R6")]
if helios_fhir::r6::type_hierarchy::is_subtype_of(value_type, "Quantity") {
return check_type_match_with_cross_namespace(
value_namespace,
value_type,
target_namespace,
target_type,
true,
);
}
false
} else {
false
};
check_type_match_with_cross_namespace(
value_namespace,
value_type,
target_namespace,
target_type,
allow_cross_namespace,
)
}
fn check_type_match_with_cross_namespace(
value_namespace: &Option<String>,
value_type: &str,
target_namespace: &Option<String>,
target_type: &str,
allow_cross_namespace: bool,
) -> Result<bool, EvaluationError> {
let type_matches = value_type.eq_ignore_ascii_case(target_type);
let type_hierarchy_matches = if allow_cross_namespace
|| (value_namespace.as_deref() == target_namespace.as_deref()
&& value_namespace.as_deref() == Some("FHIR"))
{
#[cfg(feature = "R4")]
if helios_fhir::r4::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
#[cfg(feature = "R4B")]
if helios_fhir::r4b::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
#[cfg(feature = "R5")]
if helios_fhir::r5::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
#[cfg(feature = "R6")]
if helios_fhir::r6::type_hierarchy::is_subtype_of(value_type, target_type) {
return Ok(true);
}
false
} else {
false
};
let type_or_hierarchy_matches = type_matches || type_hierarchy_matches;
if target_namespace.is_none() {
return Ok(type_or_hierarchy_matches);
}
if type_or_hierarchy_matches {
match (value_namespace, target_namespace) {
(Some(value_ns), Some(target_ns)) => {
let namespace_matches = value_ns.eq_ignore_ascii_case(target_ns);
let value_type_lower = value_type.to_lowercase();
let is_complex_type = matches!(
value_type_lower.as_str(),
"quantity" | "date" | "datetime" | "time"
);
let is_quantity_subtype = matches!(
value_type_lower.as_str(),
"age" | "distance" | "duration" | "count"
) && target_type.eq_ignore_ascii_case("quantity");
let is_primitive_type = allow_cross_namespace
&& matches!(
value_type.to_lowercase().as_str(),
"boolean"
| "string"
| "integer"
| "decimal"
| "date"
| "datetime"
| "time"
| "instant"
| "id"
| "oid"
| "url"
| "uuid"
| "uri"
| "canonical"
| "code"
| "markdown"
| "base64binary"
| "positiveint"
| "unsignedint"
);
let is_cross_matchable_type =
is_complex_type || is_primitive_type || is_quantity_subtype;
let cross_namespace_match = is_cross_matchable_type
&& ((value_ns.eq_ignore_ascii_case("FHIR")
&& target_ns.eq_ignore_ascii_case("System"))
|| (value_ns.eq_ignore_ascii_case("System")
&& target_ns.eq_ignore_ascii_case("FHIR")));
Ok(namespace_matches || cross_namespace_match)
}
(None, Some(_)) => {
Ok(false)
}
_ => Ok(type_matches), }
} else {
Ok(false)
}
}
pub fn extract_namespace_and_type_with_context(
type_spec: &TypeSpecifier,
context: &EvaluationContext,
) -> Result<(Option<String>, String), EvaluationError> {
match type_spec {
TypeSpecifier::QualifiedIdentifier(ns, Some(name)) => {
let clean_name = clean_identifier(name);
let clean_ns = clean_identifier(ns);
if clean_ns.contains('.') {
let primary_ns = clean_ns.split('.').next().unwrap_or(&clean_ns).to_string();
return Ok((Some(primary_ns), clean_name));
}
let normalized_ns = match clean_ns.to_lowercase().as_str() {
"system" => "System".to_string(),
"fhir" => "FHIR".to_string(),
_ => clean_ns,
};
Ok((Some(normalized_ns), clean_name))
}
TypeSpecifier::QualifiedIdentifier(name, _) => {
let clean_name = clean_identifier(name);
if clean_name.contains('.') {
let parts: Vec<&str> = clean_name.split('.').collect();
if parts.len() >= 2 {
let raw_namespace = parts[0].to_string();
let type_name = parts[parts.len() - 1].to_string();
let normalized_ns = match raw_namespace.to_lowercase().as_str() {
"system" => "System".to_string(),
"fhir" => "FHIR".to_string(),
_ => raw_namespace,
};
return Ok((Some(normalized_ns), clean_identifier(&type_name)));
}
}
let first_char = clean_name.chars().next().unwrap_or('_');
let is_likely_system_type = first_char.is_uppercase();
let system_primitives = [
"Boolean", "String", "Integer", "Decimal", "Date", "DateTime", "Time", "Quantity",
];
let fhir_primitives = [
"boolean",
"string",
"integer",
"decimal",
"date",
"dateTime",
"time",
"code",
"id",
"uri",
"url",
"canonical",
"markdown",
"base64Binary",
"instant",
"positiveInt",
"unsignedInt",
"uuid",
];
if is_likely_system_type {
if system_primitives
.iter()
.any(|&t| t.eq_ignore_ascii_case(&clean_name))
{
return Ok((Some("System".to_string()), clean_name.clone()));
}
} else {
if fhir_primitives
.iter()
.any(|&t| t.eq_ignore_ascii_case(&clean_name))
{
return Ok((Some("FHIR".to_string()), clean_name.to_lowercase()));
}
}
if system_primitives
.iter()
.any(|&t| t.eq_ignore_ascii_case(&clean_name))
{
let normalized_type = if is_likely_system_type {
clean_name.clone()
} else {
capitalize_first_letter(&clean_name)
};
Ok((Some("System".to_string()), normalized_type))
} else if fhir_primitives
.iter()
.any(|&t| t.eq_ignore_ascii_case(&clean_name))
{
let normalized_type = if is_likely_system_type {
clean_name.clone()
} else {
clean_name.to_lowercase()
};
Ok((Some("FHIR".to_string()), normalized_type))
}
else if is_resource_type_for_version(&clean_name, &context.fhir_version) {
Ok((
Some("FHIR".to_string()),
capitalize_first_letter(&clean_name),
))
}
else if is_complex_type_for_version(&clean_name, &context.fhir_version) {
Ok((
Some("FHIR".to_string()),
capitalize_first_letter(&clean_name),
))
}
else if !is_valid_type_name(&clean_name, &context.fhir_version) {
Err(EvaluationError::InvalidTypeSpecifier(format!(
"The type '{}' is unknown",
clean_name
)))
} else if is_likely_system_type {
Ok((Some("System".to_string()), clean_name))
} else {
Ok((Some("FHIR".to_string()), clean_name))
}
}
}
}
fn is_primitive_type(type_name: &str) -> bool {
let system_primitives = [
"Boolean", "String", "Integer", "Decimal", "Date", "DateTime", "Time", "Quantity",
"boolean", "string", "integer", "decimal", "date", "datetime", "time", "quantity",
];
if system_primitives
.iter()
.any(|&t| t.eq_ignore_ascii_case(type_name))
{
return true;
}
is_fhir_primitive_type(type_name)
}
fn is_valid_type_name(type_name: &str, fhir_version: &FhirVersion) -> bool {
if is_primitive_type(type_name) {
return true;
}
if is_resource_type_for_version(type_name, fhir_version) {
return true;
}
is_complex_type_for_version(type_name, fhir_version)
}
pub fn is_valid_fhir_type_suffix(suffix: &str, fhir_version: &FhirVersion) -> bool {
if is_fhir_primitive_type(suffix) {
return true;
}
if is_complex_type_for_version(suffix, fhir_version) {
return true;
}
if suffix.eq_ignore_ascii_case("SimpleQuantity") {
return true;
}
false
}
#[allow(dead_code)]
pub fn extract_namespace_and_type_without_context(
type_spec: &TypeSpecifier,
) -> Result<(Option<String>, String), EvaluationError> {
match type_spec {
TypeSpecifier::QualifiedIdentifier(ns, Some(name)) => {
let clean_name = clean_identifier(name);
let clean_ns = clean_identifier(ns);
if clean_ns.contains('.') {
let primary_ns = clean_ns.split('.').next().unwrap_or(&clean_ns).to_string();
return Ok((Some(primary_ns), clean_name));
}
let normalized_ns = match clean_ns.to_lowercase().as_str() {
"system" => "System".to_string(),
"fhir" => "FHIR".to_string(),
_ => clean_ns,
};
Ok((Some(normalized_ns), clean_name))
}
TypeSpecifier::QualifiedIdentifier(name, _) => {
let clean_name = clean_identifier(name);
if clean_name.contains('.') {
let parts: Vec<&str> = clean_name.split('.').collect();
if parts.len() >= 2 {
let raw_namespace = parts[0].to_string();
let type_name = parts[parts.len() - 1].to_string();
let normalized_ns = match raw_namespace.to_lowercase().as_str() {
"system" => "System".to_string(),
"fhir" => "FHIR".to_string(),
_ => raw_namespace,
};
return Ok((Some(normalized_ns), clean_identifier(&type_name)));
}
}
let first_char = clean_name.chars().next().unwrap_or('_');
let is_likely_system_type = first_char.is_uppercase();
let system_primitives = [
"Boolean", "String", "Integer", "Decimal", "Date", "DateTime", "Time", "Quantity",
];
let fhir_primitives = [
"boolean",
"string",
"integer",
"decimal",
"date",
"dateTime",
"time",
"code",
"id",
"uri",
"url",
"canonical",
"markdown",
"base64Binary",
"instant",
"positiveInt",
"unsignedInt",
"uuid",
];
if system_primitives
.iter()
.any(|&t| t.eq_ignore_ascii_case(&clean_name))
{
let normalized_type = if is_likely_system_type {
clean_name.clone()
} else {
capitalize_first_letter(&clean_name)
};
Ok((Some("System".to_string()), normalized_type))
}
else if fhir_primitives
.iter()
.any(|&t| t.eq_ignore_ascii_case(&clean_name))
{
let normalized_type = if is_likely_system_type {
clean_name.clone()
} else {
clean_name.to_lowercase()
};
Ok((Some("FHIR".to_string()), normalized_type))
}
else if !is_likely_system_type && clean_name.chars().any(|c| c.is_ascii_digit()) {
Err(EvaluationError::InvalidTypeSpecifier(format!(
"The type '{}' is unknown",
clean_name
)))
} else if is_likely_system_type {
Ok((Some("System".to_string()), clean_name))
} else {
Ok((Some("FHIR".to_string()), clean_name))
}
}
}
}
fn clean_identifier(ident: &str) -> String {
if ident.starts_with('`') && ident.ends_with('`') && ident.len() > 2 {
ident[1..ident.len() - 1].to_string()
} else {
ident.to_string()
}
}
pub fn is_fhir_domain_resource_for_version(
resource_type: &str,
fhir_version: &FhirVersion,
) -> bool {
match fhir_version {
#[cfg(feature = "R4")]
FhirVersion::R4 => {
helios_fhir::r4::type_hierarchy::is_subtype_of(resource_type, "DomainResource")
}
#[cfg(feature = "R4B")]
FhirVersion::R4B => {
helios_fhir::r4b::type_hierarchy::is_subtype_of(resource_type, "DomainResource")
}
#[cfg(feature = "R5")]
FhirVersion::R5 => {
helios_fhir::r5::type_hierarchy::is_subtype_of(resource_type, "DomainResource")
}
#[cfg(feature = "R6")]
FhirVersion::R6 => {
helios_fhir::r6::type_hierarchy::is_subtype_of(resource_type, "DomainResource")
}
#[allow(unreachable_patterns)]
_ => false, }
}
pub fn is_fhir_domain_resource(resource_type: &str) -> bool {
FhirVersion::enabled_versions()
.iter()
.any(|version| is_fhir_domain_resource_for_version(resource_type, version))
}
#[allow(dead_code)]
pub fn as_type(
value: &EvaluationResult,
type_spec: &TypeSpecifier,
) -> Result<EvaluationResult, EvaluationError> {
if is_of_type(value, type_spec)? {
Ok(value.clone())
} else {
Ok(EvaluationResult::Empty)
}
}
pub fn as_type_with_context(
value: &EvaluationResult,
type_spec: &TypeSpecifier,
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
if is_of_type_with_context(value, type_spec, context)? {
Ok(value.clone())
} else {
Ok(EvaluationResult::Empty)
}
}
#[allow(dead_code)]
fn try_convert_for_of_type(
value: &EvaluationResult,
type_spec: &TypeSpecifier,
) -> Result<Option<EvaluationResult>, EvaluationError> {
let (_target_namespace, target_type) = extract_namespace_and_type_without_context(type_spec)?;
match value {
EvaluationResult::String(s, type_info, _) => {
if let Some(type_info) = type_info {
if type_info.namespace == "FHIR" && type_info.name == "string" {
match target_type.to_lowercase().as_str() {
"datetime" => {
if s.len() == 10
&& s.chars().nth(4) == Some('-')
&& s.chars().nth(7) == Some('-')
{
Ok(Some(EvaluationResult::datetime(s.clone())))
} else if s.contains('T') || s.len() > 10 {
Ok(Some(EvaluationResult::datetime(s.clone())))
} else {
Ok(None)
}
}
"time" => {
if s.contains(':') {
Ok(Some(EvaluationResult::time(s.clone())))
} else {
Ok(None)
}
}
"date" => {
if s.len() >= 7 && s.chars().nth(4) == Some('-') {
Ok(Some(EvaluationResult::date(s.clone())))
} else {
Ok(None)
}
}
"instant" => {
if s.contains('T')
&& (s.contains('+') || s.contains('-') || s.ends_with('Z'))
{
Ok(Some(EvaluationResult::fhir_string(s.clone(), "instant")))
} else {
Ok(None)
}
}
"id" => {
if s.starts_with("http://")
|| s.starts_with("https://")
|| s.starts_with("urn:")
{
Ok(None)
} else {
Ok(Some(EvaluationResult::fhir_string(s.clone(), "id")))
}
}
"oid" => {
if s.starts_with("urn:oid:") {
Ok(Some(EvaluationResult::fhir_string(s.clone(), "oid")))
} else {
Ok(None)
}
}
"url" => {
if s.starts_with("http://") || s.starts_with("https://") {
Ok(Some(EvaluationResult::fhir_string(s.clone(), "url")))
} else {
Ok(None)
}
}
"uuid" => {
if s.starts_with("urn:uuid:") {
Ok(Some(EvaluationResult::fhir_string(s.clone(), "uuid")))
} else {
Ok(None)
}
}
"uri" => {
if s.starts_with("urn:")
|| s.starts_with("http://")
|| s.starts_with("https://")
{
Ok(Some(EvaluationResult::fhir_string(s.clone(), "uri")))
} else {
Ok(None)
}
}
"canonical" => {
if s.starts_with("http://") || s.starts_with("https://") {
Ok(Some(EvaluationResult::fhir_string(s.clone(), "canonical")))
} else {
Ok(None)
}
}
"code" => {
Ok(Some(EvaluationResult::fhir_string(s.clone(), "code")))
}
"markdown" => {
Ok(Some(EvaluationResult::fhir_string(s.clone(), "markdown")))
}
"base64binary" => {
Ok(Some(EvaluationResult::fhir_string(
s.clone(),
"base64Binary",
)))
}
_ => Ok(None),
}
} else {
Ok(None)
}
} else {
Ok(None)
}
}
_ => Ok(None),
}
}
pub fn of_type_with_context(
collection: &EvaluationResult,
type_spec: &TypeSpecifier,
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
let apply_type_filter =
|items: &[EvaluationResult]| -> Result<EvaluationResult, EvaluationError> {
let mut result = Vec::new();
for item in items {
if is_of_type_with_context(item, type_spec, context)? {
result.push(item.clone());
}
}
if result.is_empty() {
Ok(EvaluationResult::Empty)
} else if result.len() == 1 {
Ok(result[0].clone())
} else {
let input_was_unordered = matches!(
collection,
EvaluationResult::Collection {
has_undefined_order: true,
..
}
);
Ok(EvaluationResult::Collection {
items: result,
has_undefined_order: input_was_unordered,
type_info: None,
})
}
};
match collection {
EvaluationResult::Collection { items, .. } => apply_type_filter(items), EvaluationResult::Empty => Ok(EvaluationResult::Empty),
_ => {
if is_of_type_with_context(collection, type_spec, context)? {
Ok(collection.clone())
} else {
Ok(EvaluationResult::Empty)
}
}
}
}
#[allow(dead_code)]
pub fn of_type(
collection: &EvaluationResult,
type_spec: &TypeSpecifier,
) -> Result<EvaluationResult, EvaluationError> {
let apply_type_filter =
|items: &[EvaluationResult]| -> Result<EvaluationResult, EvaluationError> {
let mut result = Vec::new();
for item in items {
if is_of_type_for_of_type(item, type_spec)? {
result.push(item.clone());
} else if let Some(converted) = try_convert_for_of_type(item, type_spec)? {
result.push(converted);
}
}
if result.is_empty() {
Ok(EvaluationResult::Empty)
} else if result.len() == 1 {
Ok(result[0].clone())
} else {
let input_was_unordered = matches!(
collection,
EvaluationResult::Collection {
has_undefined_order: true,
..
}
);
Ok(EvaluationResult::Collection {
items: result,
has_undefined_order: input_was_unordered,
type_info: None,
})
}
};
match collection {
EvaluationResult::Collection { items, .. } => apply_type_filter(items), EvaluationResult::Empty => Ok(EvaluationResult::Empty),
_ => {
if is_of_type_for_of_type(collection, type_spec)? {
Ok(collection.clone())
} else if let Some(converted) = try_convert_for_of_type(collection, type_spec)? {
Ok(converted)
} else {
Ok(EvaluationResult::Empty)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::str::FromStr;
fn create_patient() -> EvaluationResult {
let mut patient = HashMap::new();
patient.insert(
"resourceType".to_string(),
EvaluationResult::string("Patient".to_string()),
);
patient.insert(
"id".to_string(),
EvaluationResult::string("123".to_string()),
);
patient.insert("active".to_string(), EvaluationResult::boolean(true));
let mut name = HashMap::new();
name.insert(
"use".to_string(),
EvaluationResult::string("official".to_string()),
);
name.insert(
"family".to_string(),
EvaluationResult::string("Smith".to_string()),
);
let given = vec![
EvaluationResult::string("John".to_string()),
EvaluationResult::string("Q".to_string()),
];
name.insert(
"given".to_string(),
EvaluationResult::Collection {
items: given,
has_undefined_order: false,
type_info: None,
},
);
let names = vec![EvaluationResult::Object {
map: name,
type_info: None,
}];
patient.insert(
"name".to_string(),
EvaluationResult::Collection {
items: names,
has_undefined_order: false,
type_info: None,
},
);
patient.insert(
"birthDate".to_string(),
EvaluationResult::string("1974-12-25".to_string()),
);
EvaluationResult::Object {
map: patient,
type_info: None,
}
}
fn create_observation() -> EvaluationResult {
let mut obs = HashMap::new();
obs.insert(
"resourceType".to_string(),
EvaluationResult::string("Observation".to_string()),
);
obs.insert(
"id".to_string(),
EvaluationResult::string("456".to_string()),
);
obs.insert(
"status".to_string(),
EvaluationResult::string("final".to_string()),
);
let mut quantity = HashMap::new();
quantity.insert(
"value".to_string(),
EvaluationResult::decimal(rust_decimal::Decimal::from(185)),
);
quantity.insert(
"unit".to_string(),
EvaluationResult::string("lbs".to_string()),
);
quantity.insert(
"system".to_string(),
EvaluationResult::string("http://unitsofmeasure.org".to_string()),
);
quantity.insert(
"code".to_string(),
EvaluationResult::string("lb_av".to_string()),
);
obs.insert(
"valueQuantity".to_string(),
EvaluationResult::Object {
map: quantity,
type_info: None,
},
);
EvaluationResult::Object {
map: obs,
type_info: None,
}
}
#[test]
fn test_is_of_type_system_types() {
let bool_val = EvaluationResult::boolean(true);
let int_val = EvaluationResult::integer(42);
let dec_val = EvaluationResult::decimal(rust_decimal::Decimal::from_str("3.14").unwrap());
let str_val = EvaluationResult::string("test".to_string());
let bool_type = TypeSpecifier::QualifiedIdentifier("Boolean".to_string(), None);
let int_type = TypeSpecifier::QualifiedIdentifier("Integer".to_string(), None);
let dec_type = TypeSpecifier::QualifiedIdentifier("Decimal".to_string(), None);
let str_type = TypeSpecifier::QualifiedIdentifier("String".to_string(), None);
assert!(is_of_type(&bool_val, &bool_type).unwrap());
assert!(is_of_type(&int_val, &int_type).unwrap());
assert!(is_of_type(&dec_val, &dec_type).unwrap());
assert!(is_of_type(&str_val, &str_type).unwrap());
assert!(!is_of_type(&bool_val, &int_type).unwrap());
assert!(!is_of_type(&int_val, &str_type).unwrap());
assert!(!is_of_type(&dec_val, &bool_type).unwrap());
assert!(!is_of_type(&str_val, &dec_type).unwrap());
}
#[test]
fn test_is_of_type_fhir_resources() {
let patient = create_patient();
let observation = create_observation();
if let EvaluationResult::Object {
map: obj,
type_info: None,
} = &patient
{
eprintln!("Patient object:");
for (key, value) in obj {
eprintln!(" {}: {:?}", key, value);
}
}
let patient_type =
TypeSpecifier::QualifiedIdentifier("FHIR".to_string(), Some("Patient".to_string()));
eprintln!("Patient type: {:?}", patient_type);
let is_result = is_of_type(&patient, &patient_type);
eprintln!("is_of_type result: {:?}", is_result);
assert!(is_result.unwrap());
let obs_type =
TypeSpecifier::QualifiedIdentifier("FHIR".to_string(), Some("Observation".to_string()));
let fhir_patient_type =
TypeSpecifier::QualifiedIdentifier("FHIR".to_string(), Some("Patient".to_string()));
assert!(is_of_type(&observation, &obs_type).unwrap());
assert!(is_of_type(&patient, &fhir_patient_type).unwrap());
let patient_type_lowercase =
TypeSpecifier::QualifiedIdentifier("fhir".to_string(), Some("patient".to_string()));
assert!(is_of_type(&patient, &patient_type_lowercase).unwrap());
assert!(!is_of_type(&patient, &obs_type).unwrap());
assert!(!is_of_type(&observation, &patient_type).unwrap());
}
#[test]
fn test_as_type() {
let bool_val = EvaluationResult::boolean(true);
let patient = create_patient();
let observation = create_observation();
let bool_type = TypeSpecifier::QualifiedIdentifier("Boolean".to_string(), None);
let patient_type =
TypeSpecifier::QualifiedIdentifier("FHIR".to_string(), Some("Patient".to_string()));
let obs_type =
TypeSpecifier::QualifiedIdentifier("FHIR".to_string(), Some("Observation".to_string()));
assert_eq!(as_type(&bool_val, &bool_type).unwrap(), bool_val);
assert_eq!(as_type(&patient, &patient_type).unwrap(), patient);
assert_eq!(as_type(&observation, &obs_type).unwrap(), observation);
let patient_type_lowercase =
TypeSpecifier::QualifiedIdentifier("fhir".to_string(), Some("patient".to_string()));
assert_eq!(as_type(&patient, &patient_type_lowercase).unwrap(), patient);
assert_eq!(
as_type(&bool_val, &patient_type).unwrap(),
EvaluationResult::Empty
);
assert_eq!(
as_type(&patient, &bool_type).unwrap(),
EvaluationResult::Empty
);
assert_eq!(
as_type(&observation, &patient_type).unwrap(),
EvaluationResult::Empty
);
}
#[test]
fn test_of_type() {
let collection = EvaluationResult::Collection {
items: vec![
EvaluationResult::boolean(true),
EvaluationResult::integer(42),
EvaluationResult::boolean(false),
EvaluationResult::string("test".to_string()),
],
has_undefined_order: false,
type_info: None,
};
let bool_type = TypeSpecifier::QualifiedIdentifier("Boolean".to_string(), None);
let int_type = TypeSpecifier::QualifiedIdentifier("Integer".to_string(), None);
let str_type = TypeSpecifier::QualifiedIdentifier("String".to_string(), None);
let collection_with_only_booleans = EvaluationResult::Collection {
items: vec![
EvaluationResult::boolean(true),
EvaluationResult::boolean(false),
],
has_undefined_order: false,
type_info: None,
};
let bool_result = of_type(&collection, &bool_type).unwrap();
assert_eq!(bool_result, collection_with_only_booleans);
let int_result = of_type(&collection, &int_type).unwrap();
assert_eq!(int_result, EvaluationResult::integer(42));
let str_result = of_type(&collection, &str_type).unwrap();
assert_eq!(str_result, EvaluationResult::string("test".to_string()));
let decimal_type = TypeSpecifier::QualifiedIdentifier("Decimal".to_string(), None);
let decimal_result = of_type(&collection, &decimal_type).unwrap();
assert_eq!(decimal_result, EvaluationResult::Empty);
}
}