Skip to main content

haste_x_fhir_query/
lib.rs

1use haste_fhir_model::r4::generated::terminology::IssueType;
2use haste_fhir_operation_error::OperationOutcomeError;
3use haste_fhirpath::{Config, FPEngine};
4use haste_reflect::MetaValue;
5use regex::Regex;
6use std::sync::{Arc, LazyLock};
7
8use crate::conversion::stringify_meta_value;
9
10pub mod conversion;
11
12static FP_EXPRESSION_REGEX: LazyLock<Regex> =
13    LazyLock::new(|| Regex::new(r#"\{\{([^}]*)\}\}"#).expect("Failed to compile regex"));
14
15pub async fn evaluation<'a, 'b>(
16    x_fhir_query: &str,
17    values: Vec<&'a dyn MetaValue>,
18    config: Arc<Config<'b>>,
19) -> Result<String, OperationOutcomeError>
20where
21    'a: 'b,
22{
23    let engine = FPEngine::new();
24
25    let mut result = x_fhir_query.to_string();
26
27    for expression in FP_EXPRESSION_REGEX.captures_iter(x_fhir_query) {
28        let full_match = expression.get(0).map(|m| m.as_str()).unwrap_or("");
29
30        let expr = expression.get(1).map(|m| m.as_str()).unwrap_or("");
31
32        println!("Evaluating FHIRPath expression: '{}'", expr);
33
34        if expr.is_empty() {
35            return Err(OperationOutcomeError::fatal(
36                IssueType::Invalid(None),
37                "FHIRPath expression is empty.".to_string(),
38            ));
39        }
40
41        let fp_result = engine
42            .evaluate_with_config(expr, values.clone(), config.clone())
43            .await
44            .map_err(|e| {
45                OperationOutcomeError::fatal(
46                    IssueType::Invalid(None),
47                    format!("FHIRPath evaluation error: {}", e),
48                )
49            })?;
50
51        let fp_string_result = fp_result
52            .iter()
53            .map(|v| stringify_meta_value(v))
54            .collect::<Result<Vec<String>, OperationOutcomeError>>()?
55            .join(",");
56
57        result = result.replace(full_match, &fp_string_result);
58    }
59
60    Ok(result)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use haste_fhir_model::r4::generated::{
67        resources::Patient,
68        types::{FHIRString, HumanName},
69    };
70    #[tokio::test]
71    async fn test_simple_eval() {
72        let patient = Patient {
73            id: Some("example".to_string()),
74
75            ..Default::default()
76        };
77        let result = evaluation(
78            "Patient/{{$this.id}}",
79            vec![&patient],
80            Arc::new(Config {
81                variable_resolver: None,
82            }),
83        )
84        .await
85        .expect("Evaluation failed");
86
87        assert_eq!(result, "Patient/example");
88    }
89
90    #[tokio::test]
91    async fn test_multiple() {
92        let patient = Patient {
93            id: Some("example".to_string()),
94            name: Some(vec![Box::new(HumanName {
95                family: Some(Box::new(FHIRString {
96                    value: Some("Doe".to_string()),
97                    ..Default::default()
98                })),
99                ..Default::default()
100            })]),
101            ..Default::default()
102        };
103        let result = evaluation(
104            "Patient/{{$this.id}}/{{$this.name.family.value}}",
105            vec![&patient],
106            Arc::new(Config {
107                variable_resolver: None,
108            }),
109        )
110        .await
111        .expect("Evaluation failed");
112
113        assert_eq!(result, "Patient/example/Doe");
114    }
115}