haste-testscript-runner 0.21.0

FHIR TestScript Runner for Haste Health
use haste_fhir_model::r4::generated::types::{
    FHIRBoolean, FHIRDecimal, FHIRInteger, FHIRPositiveInt, FHIRUnsignedInt,
};
use haste_reflect::MetaValue;

#[derive(Debug, PartialEq)]
pub enum ConvertedValue {
    String(String),
    Boolean(bool),
    Number(f64),
}

impl std::fmt::Display for ConvertedValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConvertedValue::String(s) => write!(f, "{s}"),
            ConvertedValue::Boolean(b) => write!(f, "{b}"),
            ConvertedValue::Number(n) => write!(f, "{n}"),
        }
    }
}

fn downcast_string(value: &dyn MetaValue) -> Option<String> {
    match value.fhir_type() {
        "canonical" | "base64Binary" | "code" | "string" | "oid" | "id" | "uri" | "url"
        | "uuid" | "xhtml" => downcast_string(value.get_field("value").unwrap_or(&String::new())),

        "date" => value
            .as_any()
            .downcast_ref::<haste_fhir_model::r4::generated::types::FHIRDate>()
            .and_then(|dt| dt.value.as_ref())
            .map(std::string::ToString::to_string),

        "dateTime" => value
            .as_any()
            .downcast_ref::<haste_fhir_model::r4::generated::types::FHIRDateTime>()
            .and_then(|dt| dt.value.as_ref())
            .map(std::string::ToString::to_string),

        "instant" => value
            .as_any()
            .downcast_ref::<haste_fhir_model::r4::generated::types::FHIRInstant>()
            .and_then(|dt| dt.value.as_ref())
            .map(std::string::ToString::to_string),

        "http://hl7.org/fhirpath/System.String" => value.as_any().downcast_ref::<String>().cloned(),
        _ => None,
    }
}

fn downcast_number(value: &dyn MetaValue) -> Option<f64> {
    match value.fhir_type() {
        "integer" => value
            .as_any()
            .downcast_ref::<FHIRInteger>()
            .and_then(|fp_int| downcast_number(fp_int.value.as_ref().unwrap_or(&0))),
        "decimal" => value
            .as_any()
            .downcast_ref::<FHIRDecimal>()
            .and_then(|fp_dec| downcast_number(fp_dec.value.as_ref().unwrap_or(&f64::from(0)))),
        "positiveInt" => value
            .as_any()
            .downcast_ref::<FHIRPositiveInt>()
            .and_then(|fp_pint| downcast_number(fp_pint.value.as_ref().unwrap_or(&0_u64))),

        "unsignedInt" => value
            .as_any()
            .downcast_ref::<FHIRUnsignedInt>()
            .and_then(|fp_uint| downcast_number(fp_uint.value.as_ref().unwrap_or(&0_u64))),
        "http://hl7.org/fhirpath/System.Integer" =>
        {
            #[allow(clippy::cast_precision_loss)]
            value.as_any().downcast_ref::<i64>().map(|v| *v as f64)
        }

        "http://hl7.org/fhirpath/System.Decimal" => value.as_any().downcast_ref::<f64>().copied(),
        _ => None,
    }
}

fn downcast_bool(value: &dyn MetaValue) -> Option<bool> {
    match value.fhir_type() {
        "http://hl7.org/fhirpath/System.Boolean" => value.as_any().downcast_ref::<bool>().copied(),

        "boolean" => value
            .as_any()
            .downcast_ref::<FHIRBoolean>()
            .and_then(|fp_bool| downcast_bool(fp_bool.value.as_ref().unwrap_or(&false))),

        _ => None,
    }
}

pub fn convert_meta_value(value: &dyn MetaValue) -> ConvertedValue {
    if let Some(s) = downcast_string(value) {
        return ConvertedValue::String(s);
    } else if let Some(i) = downcast_number(value) {
        return ConvertedValue::Number(i);
    } else if let Some(b) = downcast_bool(value) {
        return ConvertedValue::Boolean(b);
    }
    ConvertedValue::String(format!("{value:?}"))
}

pub fn convert_string_value(value: &str) -> Option<ConvertedValue> {
    if value == "true" || value == "false" {
        if let Ok(b) = value.parse::<bool>() {
            return Some(ConvertedValue::Boolean(b));
        }
        None
    } else if let Ok(i) = value.parse::<f64>() {
        Some(ConvertedValue::Number(i))
    } else {
        Some(ConvertedValue::String(value.to_string()))
    }
}