Skip to main content

haste_fhirpath/
lib.rs

1mod error;
2mod parser;
3use crate::{
4    error::{FunctionError, OperationError},
5    parser::{
6        Expression, FunctionInvocation, Identifier, Invocation, Literal, Operation, Polarity,
7        QualifiedIdentifier, Term, Unit,
8    },
9};
10use dashmap::DashMap;
11pub use error::FHIRPathError;
12use haste_fhir_model::r4::{
13    conversion::{
14        BOOLEAN_TYPES, DATE_TIME_TYPES, NUMBER_TYPES, STRING_TYPES, downcast_bool,
15        downcast_datetime, downcast_number, downcast_string,
16    },
17    datetime::{Date, DateTime, Time, parse_date, parse_partial_time, parse_time},
18    generated::{
19        resources::ResourceType,
20        types::{
21            FHIRBoolean, FHIRCode, FHIRDate, FHIRDateTime, FHIRDecimal, FHIRId, FHIRInteger,
22            FHIRPositiveInt, FHIRString, FHIRTime, FHIRUnsignedInt, FHIRUri, Quantity, Reference,
23        },
24    },
25};
26use haste_reflect::MetaValue;
27use haste_reflect_derive::Reflect;
28use regex::{Regex, RegexBuilder};
29use std::{cmp::Ordering, pin::Pin};
30use std::{
31    collections::{HashMap, HashSet},
32    sync::{Arc, LazyLock, Mutex},
33};
34
35mod allocators;
36mod date_arithmetic;
37mod date_comparison;
38mod fhir_functions;
39mod partial_datetime;
40mod quantity_comparison;
41pub use fhir_functions::{Code, FHIRServices, ServiceFuture};
42pub use partial_datetime::PartialDateTime;
43
44#[cfg(test)]
45mod proptests;
46#[cfg(test)]
47mod spec_tests;
48use allocators::AllocatorTrait;
49use date_arithmetic::{Shifted, Temporal};
50use date_comparison::{TemporalOrder, temporal_order};
51use partial_datetime::{AnyDateTime, parse_any_datetime};
52use quantity_comparison::{time_equivalent, time_order};
53
54fn evaluate_literal<'b>(
55    literal: &Literal,
56    context: &Context<'b>,
57) -> Result<Context<'b>, FHIRPathError> {
58    match literal {
59        Literal::String(string) => Ok(context.new_context_from(vec![context.allocate_literal(
60            FHIRString {
61                value: Some(string.clone()),
62                ..Default::default()
63            },
64        )])),
65        Literal::Integer(int) => Ok(context.new_context_from(vec![context.allocate_literal(
66            FHIRInteger {
67                value: Some(*int),
68                ..Default::default()
69            },
70        )])),
71        Literal::Float(decimal) => Ok(context.new_context_from(vec![context.allocate_literal(
72            FHIRDecimal {
73                value: Some(*decimal),
74                ..Default::default()
75            },
76        )])),
77        Literal::Boolean(bool) => Ok(context.new_context_from(vec![context.allocate_literal(
78            FHIRBoolean {
79                value: Some(*bool),
80                ..Default::default()
81            },
82        )])),
83        Literal::Null => Ok(context.new_context_from(vec![])),
84        Literal::Quantity(value, unit) => {
85            let (system, code) = match unit {
86                Unit::Ucum(code) => ("http://unitsofmeasure.org", code),
87                Unit::Calendar(code) => ("http://hl7.org/fhirpath/CodeSystem/calendar-units", code),
88            };
89            Ok(
90                context.new_context_from(vec![context.allocate_literal(Quantity {
91                    value: Some(Box::new(FHIRDecimal {
92                        value: Some(*value),
93                        ..Default::default()
94                    })),
95                    unit: Some(Box::new(FHIRString {
96                        value: Some(code.clone()),
97                        ..Default::default()
98                    })),
99                    system: Some(Box::new(FHIRUri {
100                        value: Some(system.to_string()),
101                        ..Default::default()
102                    })),
103                    code: Some(Box::new(FHIRCode {
104                        value: Some(code.clone()),
105                        ..Default::default()
106                    })),
107                    ..Default::default()
108                })]),
109            )
110        }
111        Literal::Date(date) => temporal_literal(
112            literal,
113            context,
114            parse_date(date).ok().map(|value| FHIRDate {
115                value: Some(value),
116                ..Default::default()
117            }),
118        ),
119        // `@2020-01-01T` is a DateTime of the date's precision.
120        Literal::DateTime(datetime) => {
121            parse_any_datetime(datetime.strip_suffix('T').unwrap_or(datetime))
122                .map(|value| datetime_result(context, value))
123                .ok_or_else(|| FHIRPathError::InvalidLiteral(literal.to_owned()))
124        }
125        Literal::Time(time) => temporal_literal(
126            literal,
127            context,
128            parse_partial_time(time).ok().map(|value| FHIRTime {
129                value: Some(value),
130                ..Default::default()
131            }),
132        ),
133    }
134}
135
136/// The singleton for a parsed `Date`, `DateTime` or `Time` literal.
137fn temporal_literal<'b, T: MetaValue + 'static>(
138    literal: &Literal,
139    context: &Context<'b>,
140    value: Option<T>,
141) -> Result<Context<'b>, FHIRPathError> {
142    value
143        .map(|value| context.new_context_from(vec![context.allocate_literal(value)]))
144        .ok_or_else(|| FHIRPathError::InvalidLiteral(literal.to_owned()))
145}
146
147/// A `FHIRPath` `DateTime`: a FHIR `dateTime` when FHIR's type can hold it.
148fn datetime_result<'b>(context: &Context<'b>, value: AnyDateTime) -> Context<'b> {
149    context.new_context_from(vec![match value {
150        AnyDateTime::Fhir(value) => context.allocate_literal(FHIRDateTime {
151            value: Some(value),
152            ..Default::default()
153        }),
154        AnyDateTime::Partial(value) => context.allocate_literal(value),
155    }])
156}
157
158async fn evaluate_invocation<'a>(
159    invocation: &Invocation,
160    context: Context<'a>,
161    config: Option<Arc<Config<'a>>>,
162) -> Result<Context<'a>, FHIRPathError> {
163    match invocation {
164        Invocation::This => Ok(context),
165        Invocation::Index(index_expression) => {
166            let index = evaluate_expression(index_expression, context.clone(), config).await?;
167            if index.values.len() != 1 {
168                return Err(FHIRPathError::OperationError(
169                    OperationError::InvalidCardinality,
170                ));
171            }
172
173            let float_index = downcast_number(index.values[0])?;
174
175            // Ensure the index is non-negative and has no fractional part
176            if float_index < 0.0 || float_index.fract() != 0.0 {
177                return Err(FHIRPathError::OperationError(OperationError::InvalidIndex));
178            }
179
180            // Suppress clippy warnings because the float is guaranteed to be a valid,
181            // non-negative integer index by the safety checks immediately above.
182            #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
183            let index: usize = (float_index as u64)
184                .try_into()
185                .map_err(|_| FHIRPathError::OperationError(OperationError::IndexOutOfBounds))?;
186
187            if let Some(value) = context.values.get(index) {
188                Ok(context.new_context_from(vec![*value]))
189            } else {
190                Ok(context.new_context_from(vec![]))
191            }
192        }
193        Invocation::IndexAccessor => Ok(context.new_context_from(
194            context
195                .index
196                .map(|index| {
197                    context.allocate_literal(FHIRInteger {
198                        value: Some(i64::try_from(index).unwrap_or(i64::MAX)),
199                        ..Default::default()
200                    })
201                })
202                .into_iter()
203                .collect(),
204        )),
205        Invocation::Total => Err(FHIRPathError::NotImplemented("total".to_string())),
206        Invocation::Identifier(Identifier(id)) => Ok(context.new_context_from(
207            context
208                .values
209                .iter()
210                .flat_map(|v| v.get_field(id).map_or_else(Vec::new, |v| v.flatten()))
211                .collect(),
212        )),
213        Invocation::Function(function) => evaluate_function(function, context, config).await,
214    }
215}
216
217async fn evaluate_term<'a>(
218    term: &Term,
219    context: Context<'a>,
220    config: Option<Arc<Config<'a>>>,
221) -> Result<Context<'a>, FHIRPathError> {
222    match term {
223        Term::Literal(literal) => evaluate_literal(literal, &context),
224        Term::ExternalConstant(constant) => {
225            resolve_external_constant(
226                constant,
227                config.as_ref().and_then(|c| c.variable_resolver.as_ref()),
228                context,
229            )
230            .await
231        }
232        Term::Parenthesized(expression) => evaluate_expression(expression, context, config).await,
233        Term::Invocation(invocation) => evaluate_invocation(invocation, context, config).await,
234    }
235}
236
237/// Need special handling as the first term could start with a type filter.
238/// for example Patient.name
239async fn evaluate_first_term<'a>(
240    term: &Term,
241    context: Context<'a>,
242    config: Option<Arc<Config<'a>>>,
243) -> Result<Context<'a>, FHIRPathError> {
244    match term {
245        Term::Invocation(invocation) => match invocation {
246            Invocation::Identifier(identifier) => {
247                let spec = TypeSpecifier {
248                    model: TypeModel::Fhir,
249                    name: &identifier.0,
250                };
251                let type_filter = filter_by_type(&spec, &context);
252                if type_filter.values.is_empty() {
253                    evaluate_invocation(invocation, context, config).await
254                } else {
255                    Ok(type_filter)
256                }
257            }
258            _ => evaluate_invocation(invocation, context, config).await,
259        },
260        _ => evaluate_term(term, context, config).await,
261    }
262}
263
264async fn evaluate_singular<'a>(
265    expression: &[Term],
266    context: Context<'a>,
267    config: Option<Arc<Config<'a>>>,
268) -> Result<Context<'a>, FHIRPathError> {
269    let mut current_context = context;
270
271    let mut term_iterator = expression.iter();
272    let first_term = term_iterator.next();
273    if let Some(first_term) = first_term {
274        current_context = evaluate_first_term(first_term, current_context, config.clone()).await?;
275    }
276
277    for term in term_iterator {
278        current_context = evaluate_term(term, current_context, config.clone()).await?;
279    }
280
281    Ok(current_context)
282}
283
284async fn operation_2<'a>(
285    left: &Expression,
286    right: &Expression,
287    context: Context<'a>,
288    config: Option<Arc<Config<'a>>>,
289    executor: impl Fn(
290        Context<'a>,
291        Context<'a>,
292    )
293        -> Pin<Box<dyn Future<Output = Result<Context<'a>, FHIRPathError>> + Send + 'a>>,
294) -> Result<Context<'a>, FHIRPathError> {
295    let left = evaluate_expression(left, context.clone(), config.clone()).await?;
296    let right = evaluate_expression(right, context, config).await?;
297
298    // If one of operands is empty per spec return an empty collection
299    if left.values.is_empty() || right.values.is_empty() {
300        return Ok(left.new_context_from(vec![]));
301    }
302
303    if left.values.len() != 1 || right.values.len() != 1 {
304        return Err(FHIRPathError::OperationError(
305            OperationError::InvalidCardinality,
306        ));
307    }
308
309    executor(left, right).await
310}
311
312async fn operation_n<'a>(
313    left: &Expression,
314    right: &Expression,
315    context: Context<'a>,
316    config: Option<Arc<Config<'a>>>,
317    executor: impl Fn(Context<'a>, Context<'a>) -> Result<Context<'a>, FHIRPathError>,
318) -> Result<Context<'a>, FHIRPathError> {
319    let left = evaluate_expression(left, context.clone(), config.clone()).await?;
320    let right = evaluate_expression(right, context, config).await?;
321    executor(left, right)
322}
323
324enum Cardinality {
325    Zero,
326    One,
327    Many,
328    Custom(usize, usize),
329}
330
331fn validate_arguments(
332    ast_arguments: &[Expression],
333    cardinality: &Cardinality,
334) -> Result<(), FHIRPathError> {
335    match cardinality {
336        Cardinality::Zero => {
337            if !ast_arguments.is_empty() {
338                return Err(FHIRPathError::OperationError(
339                    OperationError::InvalidCardinality,
340                ));
341            }
342        }
343        Cardinality::Custom(min, max) => {
344            if ast_arguments.len() < *min || ast_arguments.len() > *max {
345                return Err(FHIRPathError::OperationError(
346                    OperationError::InvalidCardinality,
347                ));
348            }
349        }
350        Cardinality::One => {
351            if ast_arguments.len() != 1 {
352                return Err(FHIRPathError::OperationError(
353                    OperationError::InvalidCardinality,
354                ));
355            }
356        }
357        Cardinality::Many => {}
358    }
359    Ok(())
360}
361
362/// Which model a type specifier names: <https://hl7.org/fhirpath/N1/#models>.
363#[derive(Clone, Copy)]
364enum TypeModel {
365    /// Unqualified: `FHIR` is searched first, then `System`.
366    Any,
367    Fhir,
368    System,
369    /// A qualifier that is neither, so no type matches.
370    Unknown,
371}
372
373struct TypeSpecifier<'n> {
374    model: TypeModel,
375    name: &'n str,
376}
377
378impl<'n> TypeSpecifier<'n> {
379    /// From the parts of a possibly qualified name, as in `FHIR.boolean`, read in place by
380    /// `name_of`. `None` when a part is not a name.
381    fn new<T>(parts: &'n [T], name_of: impl Fn(&'n T) -> Option<&'n str>) -> Option<Self> {
382        let (name, qualifier) = parts.split_last()?;
383        let model = match qualifier {
384            [] => TypeModel::Any,
385            [qualifier] => match name_of(qualifier)? {
386                "FHIR" => TypeModel::Fhir,
387                "System" => TypeModel::System,
388                _ => TypeModel::Unknown,
389            },
390            _ => TypeModel::Unknown,
391        };
392        Some(Self {
393            model,
394            name: name_of(name)?,
395        })
396    }
397}
398
399fn derive_typename(expression_ast: &Expression) -> Result<TypeSpecifier<'_>, FHIRPathError> {
400    match expression_ast {
401        Expression::Singular(terms) => TypeSpecifier::new(terms, |term| match term {
402            Term::Invocation(Invocation::Identifier(id)) => Some(id.0.as_str()),
403            _ => None,
404        }),
405        Expression::Operation(_) => None,
406    }
407    .ok_or(FHIRPathError::FailedTypeNameDerivation)
408}
409
410/// The `System` type a value's type maps to, as in `boolean` to `Boolean`.
411fn system_type_name(type_name: &str) -> Option<&str> {
412    if let Some(name) = type_name.strip_prefix("http://hl7.org/fhirpath/System.") {
413        return Some(name);
414    }
415    match type_name {
416        "boolean" => Some("Boolean"),
417        "integer" | "positiveInt" | "unsignedInt" => Some("Integer"),
418        "decimal" => Some("Decimal"),
419        "string" | "code" | "id" | "markdown" => Some("String"),
420        "date" => Some("Date"),
421        "dateTime" | "instant" => Some("DateTime"),
422        "time" => Some("Time"),
423        "Quantity" => Some("Quantity"),
424        _ => None,
425    }
426}
427
428/// Whether a value of type `type_name` is of the type `spec` names. A FHIR primitive is also of
429/// the `System` type it maps to, so `Patient.active is Boolean` is true. The spec reserves
430/// `System` types for literals and results, but the engine stores those as FHIR types too and
431/// does not tell them apart from the input data.
432fn check_type_name(type_name: &str, spec: &TypeSpecifier) -> bool {
433    let fhir = || match spec.name {
434        "Resource" | "DomainResource" => ResourceType::try_from(type_name).is_ok(),
435        name => type_name == name,
436    };
437    let system = || system_type_name(type_name) == Some(spec.name);
438    match spec.model {
439        TypeModel::Any => fhir() || system(),
440        TypeModel::Fhir => fhir(),
441        TypeModel::System => system(),
442        TypeModel::Unknown => false,
443    }
444}
445
446fn check_type(value: &dyn MetaValue, spec: &TypeSpecifier) -> bool {
447    let fhir_type = value.fhir_type();
448    check_type_name(fhir_type, spec)
449        // A reference is also of the type of the resource it points to.
450        || fhir_type == "Reference"
451            && value
452                .as_any()
453                .downcast_ref::<Reference>()
454                .and_then(|reference| reference.reference.as_ref()?.value.as_deref())
455                .and_then(|reference| reference.split('/').next())
456                .is_some_and(|resource_type| check_type_name(resource_type, spec))
457}
458
459fn filter_by_type<'a>(spec: &TypeSpecifier, context: &Context<'a>) -> Context<'a> {
460    context.new_context_from(
461        context
462            .values
463            .iter()
464            .filter(|v| check_type(**v, spec))
465            .copied()
466            .collect(),
467    )
468}
469
470#[derive(Debug, Reflect)]
471#[fhir_type = "Element"]
472struct Reflection {
473    name: String,
474}
475
476async fn evaluate_function<'a>(
477    function: &FunctionInvocation,
478    context: Context<'a>,
479    config: Option<Arc<Config<'a>>>,
480) -> Result<Context<'a>, FHIRPathError> {
481    match function.name.0.as_str() {
482        "resolve" => fhir_functions::evaluate_resolve(context, config).await,
483        "extension" => fhir_functions::evaluate_extension(function, context, config).await,
484        "hasValue" => fhir_functions::evaluate_has_value(function, &context),
485        "getValue" => fhir_functions::evaluate_get_value(function, &context),
486        "htmlChecks" => fhir_functions::evaluate_html_checks(function, &context),
487        "memberOf" => fhir_functions::evaluate_member_of(function, context, config).await,
488        "subsumes" | "subsumedBy" => {
489            fhir_functions::evaluate_subsumes(function, context, config).await
490        }
491        "conformsTo" => fhir_functions::evaluate_conforms_to(function, context, config).await,
492        "where" => evaluate_where(function, context, config).await,
493        // R4 search parameters apply as() to collections, so it filters like ofType.
494        "ofType" | "as" => evaluate_of_type(function, &context),
495        "is" => evaluate_is_function(function, &context),
496        "count" => evaluate_count(function, &context),
497        "upper" | "lower" => evaluate_case(function, &context),
498        "empty" => evaluate_empty(function, &context),
499        "join" => evaluate_join(function, context, config).await,
500        "exists" => evaluate_exists(function, context, config).await,
501        "children" => evaluate_children(function, &context),
502        "repeat" => evaluate_repeat(function, context, config).await,
503        "descendants" => Ok(evaluate_descendants(&context)),
504        "type" => evaluate_type(function, &context),
505        "first" => evaluate_first(function, &context),
506        "single" | "last" | "tail" => evaluate_subsetting(function, &context),
507        "skip" | "take" => evaluate_skip_take(function, context, config).await,
508        "not" => evaluate_not(function, &context),
509        "all" => evaluate_all(function, context, config).await,
510        "allTrue" | "anyTrue" | "allFalse" | "anyFalse" => {
511            evaluate_boolean_aggregate(function, &context)
512        }
513        "subsetOf" | "supersetOf" => evaluate_subset(function, context, config).await,
514        "distinct" | "isDistinct" => evaluate_distinct(function, &context),
515        "select" => evaluate_select(function, context, config).await,
516        "union" | "combine" | "intersect" | "exclude" => {
517            evaluate_combining(function, context, config).await
518        }
519        "iif" => evaluate_iif(function, context, config).await,
520        "abs" | "ceiling" | "exp" | "floor" | "ln" | "log" | "power" | "round" | "sqrt"
521        | "truncate" => evaluate_math(function, context, config).await,
522        "startsWith" => evaluate_string_match(StringMatch::StartsWith, function, &context),
523        "endsWith" => evaluate_string_match(StringMatch::EndsWith, function, &context),
524        "contains" => evaluate_string_match(StringMatch::Contains, function, &context),
525        "getReferenceKey" => evaluate_get_reference_key(function, &context),
526        "getResourceKey" => evaluate_get_resource_key(function, &context, config),
527        "indexOf" | "substring" | "length" | "toChars" | "trim" | "replace" | "matches"
528        | "replaceMatches" => evaluate_string_function(function, context, config).await,
529        "toBoolean" | "convertsToBoolean" | "toInteger" | "convertsToInteger" | "toDecimal"
530        | "convertsToDecimal" | "toString" | "convertsToString" | "toDate" | "convertsToDate"
531        | "toDateTime" | "convertsToDateTime" | "toTime" | "convertsToTime" => {
532            evaluate_conversion(function, &context)
533        }
534        "toQuantity" | "convertsToQuantity" => {
535            evaluate_to_quantity(function, context, config).await
536        }
537        "trace" => evaluate_trace(function, context, config).await,
538        "now" | "timeOfDay" | "today" => evaluate_current_time(function, &context),
539
540        _ => Err(FHIRPathError::NotImplemented(format!(
541            "Function '{}' is not implemented",
542            function.name.0
543        ))),
544    }
545}
546
547async fn evaluate_where<'a>(
548    function: &FunctionInvocation,
549    context: Context<'a>,
550    config: Option<Arc<Config<'a>>>,
551) -> Result<Context<'a>, FHIRPathError> {
552    validate_arguments(&function.arguments, &Cardinality::One)?;
553
554    let where_condition = &function.arguments[0];
555    let mut new_context = vec![];
556
557    for (index, value) in context.values.iter().enumerate() {
558        let result = evaluate_expression(
559            where_condition,
560            context.new_item_context(*value, index),
561            config.clone(),
562        )
563        .await?;
564
565        if result.values.len() > 1 {
566            return Err(FHIRPathError::InternalError(
567                "Where condition did not return a single value".to_string(),
568            ));
569        }
570
571        // Empty result is treated as false.
572        if !result.values.is_empty() && downcast_bool(result.values[0])? {
573            new_context.push(*value);
574        }
575    }
576
577    Ok(context.new_context_from(new_context))
578}
579
580fn evaluate_of_type<'a>(
581    function: &FunctionInvocation,
582    context: &Context<'a>,
583) -> Result<Context<'a>, FHIRPathError> {
584    validate_arguments(&function.arguments, &Cardinality::One)?;
585
586    let type_name = derive_typename(&function.arguments[0])?;
587
588    Ok(filter_by_type(&type_name, context))
589}
590
591fn evaluate_count<'a>(
592    function: &FunctionInvocation,
593    context: &Context<'a>,
594) -> Result<Context<'a>, FHIRPathError> {
595    validate_arguments(&function.arguments, &Cardinality::Zero)?;
596
597    let count: i64 = context
598        .values
599        .len()
600        .try_into()
601        .map_err(|_| FHIRPathError::OperationError(OperationError::SizeOverflow))?;
602
603    Ok(
604        context.new_context_from(vec![context.allocate_literal(FHIRInteger {
605            value: Some(count),
606            ..Default::default()
607        })]),
608    )
609}
610
611fn evaluate_case<'a>(
612    function: &FunctionInvocation,
613    context: &Context<'a>,
614) -> Result<Context<'a>, FHIRPathError> {
615    validate_arguments(&function.arguments, &Cardinality::Zero)?;
616
617    let op = function.name.0.as_str();
618
619    if context.values.is_empty() {
620        return Ok(context.new_context_from(vec![]));
621    }
622
623    if context.values.len() > 1 {
624        return Err(FunctionError::InvalidCardinality(op.to_string(), context.values.len()).into());
625    }
626
627    let input = downcast_string(context.values[0])?;
628
629    let transformed = match op {
630        "upper" => input.to_uppercase(),
631        "lower" => input.to_lowercase(),
632        _ => unreachable!(),
633    };
634
635    Ok(
636        context.new_context_from(vec![context.allocate_literal(FHIRString {
637            value: Some(transformed),
638            ..Default::default()
639        })]),
640    )
641}
642
643fn evaluate_empty<'a>(
644    function: &FunctionInvocation,
645    context: &Context<'a>,
646) -> Result<Context<'a>, FHIRPathError> {
647    validate_arguments(&function.arguments, &Cardinality::Zero)?;
648
649    Ok(
650        context.new_context_from(vec![context.allocate_literal(FHIRBoolean {
651            value: Some(context.values.is_empty()),
652            ..Default::default()
653        })]),
654    )
655}
656
657async fn evaluate_join<'a>(
658    function: &FunctionInvocation,
659    context: Context<'a>,
660    config: Option<Arc<Config<'a>>>,
661) -> Result<Context<'a>, FHIRPathError> {
662    validate_arguments(&function.arguments, &Cardinality::Custom(0, 1))?;
663
664    let separator = if let Some(separator_expression) = function.arguments.first() {
665        let separator_context =
666            evaluate_expression(separator_expression, context.clone(), config).await?;
667
668        if separator_context.values.len() != 1 {
669            return Err(FHIRPathError::OperationError(
670                OperationError::InvalidCardinality,
671            ));
672        }
673
674        downcast_string(separator_context.values[0])?
675    } else {
676        String::new()
677    };
678
679    let joined = context
680        .values
681        .iter()
682        .map(|v| downcast_string(*v))
683        .collect::<Result<Vec<_>, _>>()?
684        .join(&separator);
685
686    Ok(
687        context.new_context_from(vec![context.allocate_literal(FHIRString {
688            value: Some(joined),
689            ..Default::default()
690        })]),
691    )
692}
693
694async fn evaluate_exists<'a>(
695    function: &FunctionInvocation,
696    context: Context<'a>,
697    config: Option<Arc<Config<'a>>>,
698) -> Result<Context<'a>, FHIRPathError> {
699    validate_arguments(&function.arguments, &Cardinality::Many)?;
700
701    if function.arguments.len() > 1 {
702        return Err(FunctionError::InvalidCardinality(
703            "exists".to_string(),
704            function.arguments.len(),
705        )
706        .into());
707    }
708
709    if let Some(condition) = function.arguments.first() {
710        for value in &context.values {
711            let result = evaluate_expression(
712                condition,
713                context.new_context_from(vec![*value]),
714                config.clone(),
715            )
716            .await?;
717
718            if result.values.len() > 1 {
719                return Err(FHIRPathError::InternalError(
720                    "Exists condition did not return a single value".to_string(),
721                ));
722            }
723
724            if !result.values.is_empty() && downcast_bool(result.values[0])? {
725                return Ok(
726                    context.new_context_from(vec![context.allocate_literal(FHIRBoolean {
727                        value: Some(true),
728                        ..Default::default()
729                    })]),
730                );
731            }
732        }
733
734        return Ok(
735            context.new_context_from(vec![context.allocate_literal(FHIRBoolean {
736                value: Some(false),
737                ..Default::default()
738            })]),
739        );
740    }
741
742    Ok(
743        context.new_context_from(vec![context.allocate_literal(FHIRBoolean {
744            value: Some(!context.values.is_empty()),
745            ..Default::default()
746        })]),
747    )
748}
749
750fn children_of<'a>(values: &[&'a dyn MetaValue]) -> Vec<&'a dyn MetaValue> {
751    values
752        .iter()
753        .flat_map(|value| {
754            value
755                .fields()
756                .iter()
757                .filter_map(|f| value.get_field(f).map(|v| v.flatten()))
758                .flatten()
759                .collect::<Vec<_>>()
760        })
761        .collect()
762}
763
764fn evaluate_children<'a>(
765    function: &FunctionInvocation,
766    context: &Context<'a>,
767) -> Result<Context<'a>, FHIRPathError> {
768    validate_arguments(&function.arguments, &Cardinality::Zero)?;
769
770    Ok(context.new_context_from(children_of(&context.values)))
771}
772
773/// Most items the `repeat()` calls of one evaluation may produce. A projection can
774/// keep yielding new values (`1.repeat($this + 1)`), so evaluation fails past this.
775const REPEAT_ITEM_LIMIT: usize = 10_000;
776
777/// An item as `repeat()` tells it from the items it already produced: numbers, strings and
778/// booleans by value, as `=` compares them, anything else (dates and times included) only
779/// equal to itself.
780#[derive(PartialEq, Eq, Hash)]
781enum SeenKey {
782    Number(u64),
783    String(String),
784    Boolean(bool),
785    Item(usize, &'static str),
786}
787
788impl SeenKey {
789    fn of(value: &dyn MetaValue) -> Result<Self, FHIRPathError> {
790        let fhir_type = value.fhir_type();
791        Ok(if NUMBER_TYPES.contains(fhir_type) {
792            let number = downcast_number(value)?;
793            // -0.0 = 0.0
794            SeenKey::Number(if number == 0.0 { 0.0 } else { number }.to_bits())
795        } else if STRING_TYPES.contains(fhir_type) {
796            SeenKey::String(downcast_string(value)?)
797        } else if BOOLEAN_TYPES.contains(fhir_type) {
798            SeenKey::Boolean(downcast_bool(value)?)
799        } else {
800            SeenKey::Item(std::ptr::from_ref(value).cast::<()>().addr(), fhir_type)
801        })
802    }
803}
804
805/// <https://hl7.org/fhirpath/N1/#repeatprojection-expression-collection>
806async fn evaluate_repeat<'a>(
807    function: &FunctionInvocation,
808    context: Context<'a>,
809    config: Option<Arc<Config<'a>>>,
810) -> Result<Context<'a>, FHIRPathError> {
811    validate_arguments(&function.arguments, &Cardinality::One)?;
812
813    let projection = &function.arguments[0];
814    let mut end_result = vec![];
815    let mut seen = HashSet::new();
816    let mut cur = context;
817
818    // Repeats only as long as the projection yields items not already in the output.
819    while !cur.values.is_empty() {
820        let mut next = vec![];
821        for (index, value) in cur.values.iter().enumerate() {
822            let item = cur.new_item_context(*value, index);
823            let projected = evaluate_expression(projection, item, config.clone()).await?;
824            for projected_value in projected.values {
825                if seen.insert(SeenKey::of(projected_value)?) {
826                    next.push(projected_value);
827                }
828            }
829        }
830        cur.count_repeat_items(next.len())?;
831        end_result.extend_from_slice(&next);
832        cur = cur.new_context_from(next);
833    }
834
835    Ok(cur.new_context_from(end_result))
836}
837
838/// `repeat(children())` without removing equal items, so two equal values at
839/// different places in the resource are both returned. A resource is a finite
840/// tree, so this ends.
841fn evaluate_descendants<'a>(context: &Context<'a>) -> Context<'a> {
842    let mut end_result = vec![];
843    let mut children = children_of(&context.values);
844
845    while !children.is_empty() {
846        end_result.extend_from_slice(&children);
847        children = children_of(&children);
848    }
849
850    context.new_context_from(end_result)
851}
852
853fn evaluate_type<'a>(
854    function: &FunctionInvocation,
855    context: &Context<'a>,
856) -> Result<Context<'a>, FHIRPathError> {
857    validate_arguments(&function.arguments, &Cardinality::Zero)?;
858
859    let mut next_ctx = Vec::with_capacity(context.values.len());
860
861    for value in &context.values {
862        let type_name = value.fhir_type();
863
864        next_ctx.push(context.allocate_literal(Reflection {
865            name: type_name.to_string(),
866        }));
867    }
868
869    Ok(context.new_context_from(next_ctx))
870}
871
872fn evaluate_first<'a>(
873    function: &FunctionInvocation,
874    context: &Context<'a>,
875) -> Result<Context<'a>, FHIRPathError> {
876    validate_arguments(&function.arguments, &Cardinality::Zero)?;
877
878    match context.values.first() {
879        Some(value) => Ok(context.new_context_from(vec![*value])),
880        None => Ok(context.new_context_from(vec![])),
881    }
882}
883
884fn boolean_result<'a>(context: &Context<'a>, value: bool) -> Context<'a> {
885    context.new_context_from(vec![context.allocate_literal(FHIRBoolean {
886        value: Some(value),
887        ..Default::default()
888    })])
889}
890
891fn optional_boolean_result<'a>(context: &Context<'a>, value: Option<bool>) -> Context<'a> {
892    match value {
893        Some(value) => boolean_result(context, value),
894        None => context.new_context_from(vec![]),
895    }
896}
897
898/// A number that keeps its type, so Integer arithmetic gives an Integer.
899#[derive(Clone, Copy)]
900enum Number {
901    Integer(i64),
902    Decimal(f64),
903}
904
905impl Number {
906    fn of(value: &dyn MetaValue) -> Result<Number, FHIRPathError> {
907        let any = value.as_any();
908        let integer = if let Some(integer) = any.downcast_ref::<FHIRInteger>() {
909            integer.value
910        } else if let Some(integer) = any.downcast_ref::<FHIRPositiveInt>() {
911            integer.value.and_then(|v| i64::try_from(v).ok())
912        } else if let Some(integer) = any.downcast_ref::<FHIRUnsignedInt>() {
913            integer.value.and_then(|v| i64::try_from(v).ok())
914        } else {
915            any.downcast_ref::<i64>().copied()
916        };
917
918        match integer {
919            Some(integer) => Ok(Number::Integer(integer)),
920            None => Ok(Number::Decimal(downcast_number(value)?)),
921        }
922    }
923
924    #[allow(clippy::cast_precision_loss)]
925    fn as_f64(self) -> f64 {
926        match self {
927            Number::Integer(value) => value as f64,
928            Number::Decimal(value) => value,
929        }
930    }
931
932    /// `None` when the whole number does not fit an Integer.
933    #[allow(clippy::cast_possible_truncation)]
934    fn integer_from(value: f64) -> Option<Number> {
935        // 2^63, exact as an f64. The range also rejects NaN and infinity.
936        const BOUND: f64 = 9_223_372_036_854_775_808.0;
937        (-BOUND..BOUND)
938            .contains(&value)
939            .then_some(Number::Integer(value as i64))
940    }
941
942    /// `None` for NaN and infinity, which a Decimal cannot represent.
943    fn decimal_from(value: f64) -> Option<Number> {
944        value.is_finite().then_some(Number::Decimal(value))
945    }
946
947    /// Two Integers use `integer_op` (`None` on overflow), anything else
948    /// uses `decimal_op`. Empty when the result is not finite.
949    fn combine(
950        self,
951        other: Number,
952        integer_op: fn(i64, i64) -> Option<i64>,
953        decimal_op: fn(f64, f64) -> f64,
954    ) -> Option<Number> {
955        match (self, other) {
956            (Number::Integer(l), Number::Integer(r)) => integer_op(l, r).map(Number::Integer),
957            (l, r) => Number::decimal_from(decimal_op(l.as_f64(), r.as_f64())),
958        }
959    }
960}
961
962fn number_result<'a>(context: &Context<'a>, number: Option<Number>) -> Context<'a> {
963    let value = match number {
964        Some(Number::Integer(value)) => context.allocate_literal(FHIRInteger {
965            value: Some(value),
966            ..Default::default()
967        }),
968        Some(Number::Decimal(value)) => context.allocate_literal(FHIRDecimal {
969            value: Some(value),
970            ..Default::default()
971        }),
972        None => return context.new_context_from(vec![]),
973    };
974
975    context.new_context_from(vec![value])
976}
977
978/// The input's only item: `None` when empty, an error for more than one.
979fn singleton<'a>(
980    name: &str,
981    context: &Context<'a>,
982) -> Result<Option<&'a dyn MetaValue>, FHIRPathError> {
983    match context.values.as_slice() {
984        [] => Ok(None),
985        [value] => Ok(Some(*value)),
986        values => Err(FunctionError::InvalidCardinality(name.to_string(), values.len()).into()),
987    }
988}
989
990/// <https://hl7.org/fhirpath/N1/#singleton-evaluation-of-collections>: a
991/// single item that is not a Boolean counts as true.
992fn singleton_boolean(name: &str, context: &Context<'_>) -> Result<Option<bool>, FHIRPathError> {
993    match singleton(name, context)? {
994        None => Ok(None),
995        Some(value) if BOOLEAN_TYPES.contains(value.fhir_type()) => Ok(Some(downcast_bool(value)?)),
996        Some(_) => Ok(Some(true)),
997    }
998}
999
1000fn contains_value(
1001    collection: &[&dyn MetaValue],
1002    value: &dyn MetaValue,
1003) -> Result<bool, FHIRPathError> {
1004    for item in collection {
1005        if value_equal(*item, value)? {
1006            return Ok(true);
1007        }
1008    }
1009    Ok(false)
1010}
1011
1012/// The items of `values` whose membership in `other` is `member`.
1013fn filter_by_membership<'a>(
1014    values: impl IntoIterator<Item = &'a dyn MetaValue>,
1015    other: &[&dyn MetaValue],
1016    member: bool,
1017) -> Result<Vec<&'a dyn MetaValue>, FHIRPathError> {
1018    let mut filtered = Vec::new();
1019    for value in values {
1020        if contains_value(other, value)? == member {
1021            filtered.push(value);
1022        }
1023    }
1024    Ok(filtered)
1025}
1026
1027fn distinct_values<'a>(
1028    values: impl IntoIterator<Item = &'a dyn MetaValue>,
1029) -> Result<Vec<&'a dyn MetaValue>, FHIRPathError> {
1030    let mut distinct = Vec::new();
1031    for value in values {
1032        if !contains_value(&distinct, value)? {
1033            distinct.push(value);
1034        }
1035    }
1036    Ok(distinct)
1037}
1038
1039/// <https://hl7.org/fhirpath/N1/#not-boolean>
1040fn evaluate_not<'a>(
1041    function: &FunctionInvocation,
1042    context: &Context<'a>,
1043) -> Result<Context<'a>, FHIRPathError> {
1044    validate_arguments(&function.arguments, &Cardinality::Zero)?;
1045
1046    let value = singleton_boolean("not", context)?;
1047    Ok(optional_boolean_result(context, value.map(|value| !value)))
1048}
1049
1050/// <https://hl7.org/fhirpath/N1/#allcriteria-expression-boolean>
1051async fn evaluate_all<'a>(
1052    function: &FunctionInvocation,
1053    context: Context<'a>,
1054    config: Option<Arc<Config<'a>>>,
1055) -> Result<Context<'a>, FHIRPathError> {
1056    validate_arguments(&function.arguments, &Cardinality::One)?;
1057
1058    for (index, value) in context.values.iter().enumerate() {
1059        let item = context.new_item_context(*value, index);
1060        let result = evaluate_expression(&function.arguments[0], item, config.clone()).await?;
1061
1062        if singleton_boolean("all", &result)? != Some(true) {
1063            return Ok(boolean_result(&context, false));
1064        }
1065    }
1066
1067    Ok(boolean_result(&context, true))
1068}
1069
1070/// <https://hl7.org/fhirpath/N1/#selectprojection-expression-collection>
1071async fn evaluate_select<'a>(
1072    function: &FunctionInvocation,
1073    context: Context<'a>,
1074    config: Option<Arc<Config<'a>>>,
1075) -> Result<Context<'a>, FHIRPathError> {
1076    validate_arguments(&function.arguments, &Cardinality::One)?;
1077
1078    let mut selected = vec![];
1079    for (index, value) in context.values.iter().enumerate() {
1080        let item = context.new_item_context(*value, index);
1081        let result = evaluate_expression(&function.arguments[0], item, config.clone()).await?;
1082        selected.extend(result.values);
1083    }
1084
1085    Ok(context.new_context_from(selected))
1086}
1087
1088/// <https://hl7.org/fhirpath/N1/#alltrue-boolean> and the three that follow it.
1089fn evaluate_boolean_aggregate<'a>(
1090    function: &FunctionInvocation,
1091    context: &Context<'a>,
1092) -> Result<Context<'a>, FHIRPathError> {
1093    validate_arguments(&function.arguments, &Cardinality::Zero)?;
1094
1095    let values = context
1096        .values
1097        .iter()
1098        .map(|value| downcast_bool(*value))
1099        .collect::<Result<Vec<_>, _>>()?;
1100
1101    let result = match function.name.0.as_str() {
1102        "allTrue" => values.iter().all(|value| *value),
1103        "anyTrue" => values.iter().any(|value| *value),
1104        "allFalse" => values.iter().all(|value| !*value),
1105        "anyFalse" => values.iter().any(|value| !*value),
1106        _ => unreachable!(),
1107    };
1108
1109    Ok(boolean_result(context, result))
1110}
1111
1112/// <https://hl7.org/fhirpath/N1/#subsetofother-collection-boolean> and
1113/// <https://hl7.org/fhirpath/N1/#supersetofother-collection-boolean>
1114async fn evaluate_subset<'a>(
1115    function: &FunctionInvocation,
1116    context: Context<'a>,
1117    config: Option<Arc<Config<'a>>>,
1118) -> Result<Context<'a>, FHIRPathError> {
1119    validate_arguments(&function.arguments, &Cardinality::One)?;
1120
1121    let other = evaluate_expression(&function.arguments[0], context.clone(), config).await?;
1122    let (subset, superset) = if function.name.0 == "subsetOf" {
1123        (&context.values, &other.values)
1124    } else {
1125        (&other.values, &context.values)
1126    };
1127
1128    let missing = filter_by_membership(subset.iter().copied(), superset, false)?;
1129    Ok(boolean_result(&context, missing.is_empty()))
1130}
1131
1132/// <https://hl7.org/fhirpath/N1/#distinct-collection> and
1133/// <https://hl7.org/fhirpath/N1/#isdistinct-boolean>
1134fn evaluate_distinct<'a>(
1135    function: &FunctionInvocation,
1136    context: &Context<'a>,
1137) -> Result<Context<'a>, FHIRPathError> {
1138    validate_arguments(&function.arguments, &Cardinality::Zero)?;
1139
1140    let distinct = distinct_values(context.values.iter().copied())?;
1141
1142    if function.name.0 == "isDistinct" {
1143        let is_distinct = distinct.len() == context.values.len();
1144        Ok(boolean_result(context, is_distinct))
1145    } else {
1146        Ok(context.new_context_from(distinct))
1147    }
1148}
1149
1150/// <https://hl7.org/fhirpath/N1/#single-collection>,
1151/// <https://hl7.org/fhirpath/N1/#last-collection> and
1152/// <https://hl7.org/fhirpath/N1/#tail-collection>
1153fn evaluate_subsetting<'a>(
1154    function: &FunctionInvocation,
1155    context: &Context<'a>,
1156) -> Result<Context<'a>, FHIRPathError> {
1157    validate_arguments(&function.arguments, &Cardinality::Zero)?;
1158
1159    let values = match function.name.0.as_str() {
1160        "single" => singleton("single", context)?.into_iter().collect(),
1161        "last" => context.values.last().copied().into_iter().collect(),
1162        "tail" => context.values.iter().skip(1).copied().collect(),
1163        _ => unreachable!(),
1164    };
1165
1166    Ok(context.new_context_from(values))
1167}
1168
1169/// <https://hl7.org/fhirpath/N1/#skipnum-integer-collection> and
1170/// <https://hl7.org/fhirpath/N1/#takenum-integer-collection>
1171async fn evaluate_skip_take<'a>(
1172    function: &FunctionInvocation,
1173    context: Context<'a>,
1174    config: Option<Arc<Config<'a>>>,
1175) -> Result<Context<'a>, FHIRPathError> {
1176    validate_arguments(&function.arguments, &Cardinality::One)?;
1177
1178    let name = function.name.0.as_str();
1179    let num = evaluate_expression(&function.arguments[0], context.clone(), config).await?;
1180    let num = match singleton(name, &num)?.map(Number::of).transpose()? {
1181        Some(Number::Integer(num)) => num,
1182        None => return Ok(context.new_context_from(vec![])),
1183        Some(Number::Decimal(_)) => {
1184            let message = format!("{name} requires an Integer argument");
1185            return Err(FunctionError::InvalidFunctionCall(message).into());
1186        }
1187    };
1188
1189    // Zero or less skips nothing and takes nothing.
1190    let num = usize::try_from(num.max(0)).unwrap_or(usize::MAX);
1191    let values = context.values.iter().copied();
1192    let values = if name == "skip" {
1193        values.skip(num).collect()
1194    } else {
1195        values.take(num).collect()
1196    };
1197
1198    Ok(context.new_context_from(values))
1199}
1200
1201/// <https://hl7.org/fhirpath/N1/#unionother-collection>,
1202/// <https://hl7.org/fhirpath/N1/#combineother-collection-collection>,
1203/// <https://hl7.org/fhirpath/N1/#intersectother-collection-collection> and
1204/// <https://hl7.org/fhirpath/N1/#excludeother-collection-collection>
1205async fn evaluate_combining<'a>(
1206    function: &FunctionInvocation,
1207    context: Context<'a>,
1208    config: Option<Arc<Config<'a>>>,
1209) -> Result<Context<'a>, FHIRPathError> {
1210    validate_arguments(&function.arguments, &Cardinality::One)?;
1211
1212    let other = evaluate_expression(&function.arguments[0], context.clone(), config).await?;
1213    let input = context.values.iter().copied();
1214    let both = input.clone().chain(other.values.iter().copied());
1215
1216    let values = match function.name.0.as_str() {
1217        "union" => distinct_values(both)?,
1218        "combine" => both.collect(),
1219        "intersect" => filter_by_membership(distinct_values(input)?, &other.values, true)?,
1220        "exclude" => filter_by_membership(input, &other.values, false)?,
1221        _ => unreachable!(),
1222    };
1223
1224    Ok(context.new_context_from(values))
1225}
1226
1227/// <https://hl7.org/fhirpath/N1/#iifcriterion-expression-true-result-collection-otherwise-result-collection-collection>
1228async fn evaluate_iif<'a>(
1229    function: &FunctionInvocation,
1230    context: Context<'a>,
1231    config: Option<Arc<Config<'a>>>,
1232) -> Result<Context<'a>, FHIRPathError> {
1233    validate_arguments(&function.arguments, &Cardinality::Custom(2, 3))?;
1234
1235    let criterion =
1236        evaluate_expression(&function.arguments[0], context.clone(), config.clone()).await?;
1237
1238    // Only the chosen branch is evaluated.
1239    let branch = if singleton_boolean("iif", &criterion)? == Some(true) {
1240        function.arguments.get(1)
1241    } else {
1242        function.arguments.get(2)
1243    };
1244
1245    match branch {
1246        Some(branch) => evaluate_expression(branch, context, config).await,
1247        None => Ok(context.new_context_from(vec![])),
1248    }
1249}
1250
1251/// <https://hl7.org/fhirpath/N1/#math>: empty for an empty input or a result
1252/// that cannot be represented, such as `(-1).sqrt()`.
1253async fn evaluate_math<'a>(
1254    function: &FunctionInvocation,
1255    context: Context<'a>,
1256    config: Option<Arc<Config<'a>>>,
1257) -> Result<Context<'a>, FHIRPathError> {
1258    let name = function.name.0.as_str();
1259    let cardinality = match name {
1260        "log" | "power" => Cardinality::One,
1261        "round" => Cardinality::Custom(0, 1),
1262        _ => Cardinality::Zero,
1263    };
1264    validate_arguments(&function.arguments, &cardinality)?;
1265
1266    let Some(input) = singleton(name, &context)? else {
1267        return Ok(context.new_context_from(vec![]));
1268    };
1269
1270    // abs keeps a Quantity's unit.
1271    if name == "abs"
1272        && let Some(quantity) = input.as_any().downcast_ref::<Quantity>()
1273    {
1274        let mut quantity = quantity.clone();
1275        if let Some(value) = quantity.value.as_mut() {
1276            value.value = value.value.map(f64::abs);
1277        }
1278        return Ok(context.new_context_from(vec![context.allocate_literal(quantity)]));
1279    }
1280
1281    let argument = match function.arguments.first() {
1282        Some(argument) => {
1283            let argument = evaluate_expression(argument, context.clone(), config).await?;
1284            singleton(name, &argument)?.map(Number::of).transpose()?
1285        }
1286        None => None,
1287    };
1288
1289    let number = Number::of(input)?;
1290    let value = number.as_f64();
1291    let result = match (name, number, argument) {
1292        ("abs", Number::Integer(integer), _) => integer.checked_abs().map(Number::Integer),
1293        ("abs", Number::Decimal(decimal), _) => Some(Number::Decimal(decimal.abs())),
1294        ("ceiling", _, _) => Number::integer_from(value.ceil()),
1295        ("floor", _, _) => Number::integer_from(value.floor()),
1296        ("truncate", _, _) => Number::integer_from(value.trunc()),
1297        ("exp", _, _) => Number::decimal_from(value.exp()),
1298        ("ln", _, _) => Number::decimal_from(value.ln()),
1299        ("sqrt", _, _) => Number::decimal_from(value.sqrt()),
1300        ("log", _, Some(base)) => Number::decimal_from(value.log(base.as_f64())),
1301        ("power", Number::Integer(base), Some(Number::Integer(exponent))) if exponent >= 0 => {
1302            u32::try_from(exponent)
1303                .ok()
1304                .and_then(|exponent| base.checked_pow(exponent))
1305                .map(Number::Integer)
1306        }
1307        ("power", _, Some(exponent)) => Number::decimal_from(value.powf(exponent.as_f64())),
1308        ("round", _, None) => Number::decimal_from(value.round()),
1309        ("round", _, Some(Number::Integer(precision @ 0..))) => {
1310            // An f64 holds about 15 decimal places, so more changes nothing.
1311            let scale = 10f64.powi(i32::try_from(precision.min(15)).unwrap_or(15));
1312            Number::decimal_from((value * scale).round() / scale)
1313        }
1314        ("round", _, Some(_)) => {
1315            let message = "round precision must be an Integer >= 0".to_string();
1316            return Err(FunctionError::InvalidFunctionCall(message).into());
1317        }
1318        // log or power with an empty argument.
1319        _ => None,
1320    };
1321
1322    Ok(number_result(&context, result))
1323}
1324
1325/// An empty pattern would match between every character, so it leaves the input as is.
1326fn replace_matches(
1327    input: String,
1328    pattern: &str,
1329    substitution: &str,
1330) -> Result<String, FHIRPathError> {
1331    if pattern.is_empty() {
1332        return Ok(input);
1333    }
1334    Ok(compile_regex(pattern)?
1335        .replace_all(&input, substitution)
1336        .into_owned())
1337}
1338
1339fn string_result<'a>(context: &Context<'a>, value: String) -> Context<'a> {
1340    context.new_context_from(vec![context.allocate_literal(FHIRString {
1341        value: Some(value),
1342        ..Default::default()
1343    })])
1344}
1345
1346/// Compiled patterns, which come from expressions and are cached like their ASTs.
1347static REGEX: LazyLock<DashMap<String, Arc<Regex>>> = LazyLock::new(DashMap::new);
1348
1349fn compile_regex(pattern: &str) -> Result<Arc<Regex>, FHIRPathError> {
1350    if let Some(regex) = REGEX.get(pattern) {
1351        return Ok(regex.clone());
1352    }
1353
1354    // The spec's 'single line' mode, where '.' also matches a newline.
1355    let regex = RegexBuilder::new(pattern)
1356        .dot_matches_new_line(true)
1357        .build()
1358        .map_err(|e| FunctionError::InvalidFunctionCall(format!("invalid regex: {e}")))?;
1359    let regex = Arc::new(regex);
1360    REGEX.insert(pattern.to_string(), regex.clone());
1361    Ok(regex)
1362}
1363
1364fn char_count(value: &str) -> Result<i64, FHIRPathError> {
1365    i64::try_from(value.chars().count())
1366        .map_err(|_| FHIRPathError::OperationError(OperationError::SizeOverflow))
1367}
1368
1369fn trim(value: &str) -> String {
1370    value.trim_matches([' ', '\t', '\r', '\n']).to_owned()
1371}
1372
1373/// <https://hl7.org/fhirpath/N1/#indexofsubstring-string-integer> through
1374/// <https://hl7.org/fhirpath/N1/#tochars-collection>: empty when the input or
1375/// a required argument is empty.
1376async fn evaluate_string_function<'a>(
1377    function: &FunctionInvocation,
1378    context: Context<'a>,
1379    config: Option<Arc<Config<'a>>>,
1380) -> Result<Context<'a>, FHIRPathError> {
1381    let name = function.name.0.as_str();
1382    let cardinality = match name {
1383        "length" | "toChars" | "trim" => Cardinality::Zero,
1384        "indexOf" | "matches" => Cardinality::One,
1385        "substring" => Cardinality::Custom(1, 2),
1386        _ => Cardinality::Custom(2, 2),
1387    };
1388    validate_arguments(&function.arguments, &cardinality)?;
1389
1390    let empty = context.new_context_from(vec![]);
1391    let Some(input) = singleton(name, &context)? else {
1392        return Ok(empty);
1393    };
1394    let input = downcast_string(input)?;
1395
1396    let mut arguments = Vec::with_capacity(function.arguments.len());
1397    for argument in &function.arguments {
1398        let argument = evaluate_expression(argument, context.clone(), config.clone()).await?;
1399        arguments.push(singleton(name, &argument)?);
1400    }
1401    let string_argument = |index: usize| -> Result<Option<String>, FHIRPathError> {
1402        Ok(arguments[index].map(downcast_string).transpose()?)
1403    };
1404    let integer_argument = |index: usize| -> Result<Option<i64>, FHIRPathError> {
1405        match arguments
1406            .get(index)
1407            .copied()
1408            .flatten()
1409            .map(Number::of)
1410            .transpose()?
1411        {
1412            Some(Number::Integer(value)) => Ok(Some(value)),
1413            None => Ok(None),
1414            Some(Number::Decimal(_)) => {
1415                let message = format!("{name} requires Integer arguments");
1416                Err(FunctionError::InvalidFunctionCall(message).into())
1417            }
1418        }
1419    };
1420
1421    let result = match name {
1422        "length" => number_result(&context, Some(Number::Integer(char_count(&input)?))),
1423        "trim" => string_result(&context, trim(&input)),
1424        "toChars" => {
1425            let chars = input.chars().map(|c| {
1426                context.allocate_literal(FHIRString {
1427                    value: Some(c.to_string()),
1428                    ..Default::default()
1429                })
1430            });
1431            context.new_context_from(chars.collect())
1432        }
1433        "indexOf" => {
1434            let Some(substring) = string_argument(0)? else {
1435                return Ok(empty);
1436            };
1437            let index = match input.find(&substring) {
1438                Some(byte_index) => char_count(&input[..byte_index])?,
1439                None => -1,
1440            };
1441            number_result(&context, Some(Number::Integer(index)))
1442        }
1443        "substring" => {
1444            let Some(start) = integer_argument(0)? else {
1445                return Ok(empty);
1446            };
1447            let Ok(start) = usize::try_from(start) else {
1448                return Ok(empty);
1449            };
1450            if start >= input.chars().count() {
1451                return Ok(empty);
1452            }
1453            // An empty length is the same as none.
1454            let length = integer_argument(1)?
1455                .map_or(usize::MAX, |length| usize::try_from(length).unwrap_or(0));
1456            string_result(&context, input.chars().skip(start).take(length).collect())
1457        }
1458        "replace" => {
1459            let (Some(pattern), Some(substitution)) = (string_argument(0)?, string_argument(1)?)
1460            else {
1461                return Ok(empty);
1462            };
1463            string_result(&context, input.replace(&pattern, &substitution))
1464        }
1465        "matches" => {
1466            let Some(pattern) = string_argument(0)? else {
1467                return Ok(empty);
1468            };
1469            boolean_result(&context, compile_regex(&pattern)?.is_match(&input))
1470        }
1471        "replaceMatches" => {
1472            let (Some(pattern), Some(substitution)) = (string_argument(0)?, string_argument(1)?)
1473            else {
1474                return Ok(empty);
1475            };
1476            string_result(&context, replace_matches(input, &pattern, &substitution)?)
1477        }
1478        _ => unreachable!(),
1479    };
1480
1481    Ok(result)
1482}
1483
1484/// The kinds of item the conversion functions accept.
1485enum Convertible<'v> {
1486    Boolean(bool),
1487    Number(Number),
1488    String(String),
1489    DateTime(String),
1490    Quantity(&'v Quantity),
1491}
1492
1493impl<'v> Convertible<'v> {
1494    fn of(value: &'v dyn MetaValue) -> Result<Option<Self>, FHIRPathError> {
1495        let fhir_type = value.fhir_type();
1496        let convertible = if BOOLEAN_TYPES.contains(fhir_type) {
1497            Convertible::Boolean(downcast_bool(value)?)
1498        } else if NUMBER_TYPES.contains(fhir_type) {
1499            Convertible::Number(Number::of(value)?)
1500        } else if STRING_TYPES.contains(fhir_type) {
1501            Convertible::String(downcast_string(value)?)
1502        } else if let Some(datetime) = value.as_any().downcast_ref::<PartialDateTime>() {
1503            Convertible::DateTime(datetime.to_string())
1504        } else if DATE_TIME_TYPES.contains(fhir_type) {
1505            Convertible::DateTime(downcast_datetime(value)?)
1506        } else if let Some(quantity) = value.as_any().downcast_ref::<Quantity>() {
1507            Convertible::Quantity(quantity)
1508        } else {
1509            return Ok(None);
1510        };
1511        Ok(Some(convertible))
1512    }
1513}
1514
1515/// <https://hl7.org/fhirpath/N1/#boolean-conversion-functions>
1516fn to_boolean(value: Convertible) -> Option<bool> {
1517    match value {
1518        Convertible::Boolean(value) => Some(value),
1519        Convertible::Number(number) => match number.as_f64() {
1520            1.0 => Some(true),
1521            0.0 => Some(false),
1522            _ => None,
1523        },
1524        Convertible::String(value) => {
1525            let is = |words: &[&str]| words.iter().any(|word| value.eq_ignore_ascii_case(word));
1526            if is(&["true", "t", "yes", "y", "1", "1.0"]) {
1527                Some(true)
1528            } else if is(&["false", "f", "no", "n", "0", "0.0"]) {
1529                Some(false)
1530            } else {
1531                None
1532            }
1533        }
1534        Convertible::DateTime(_) | Convertible::Quantity(_) => None,
1535    }
1536}
1537
1538/// <https://hl7.org/fhirpath/N1/#integer-conversion-functions>
1539fn to_integer(value: Convertible) -> Option<i64> {
1540    match value {
1541        Convertible::Boolean(value) => Some(i64::from(value)),
1542        Convertible::Number(Number::Integer(value)) => Some(value),
1543        // Rust's parser accepts exactly (\+|-)?\d+, failing only on overflow.
1544        Convertible::String(value) => value.parse().ok(),
1545        _ => None,
1546    }
1547}
1548
1549/// Whether `value` has the spec's Decimal format, (\+|-)?\d+(\.\d+)?.
1550fn is_decimal(value: &str) -> bool {
1551    let unsigned = value.strip_prefix(['+', '-']).unwrap_or(value);
1552    let (whole, fraction) = unsigned.split_once('.').unwrap_or((unsigned, "0"));
1553    let digits = |part: &str| !part.is_empty() && part.bytes().all(|b| b.is_ascii_digit());
1554    digits(whole) && digits(fraction)
1555}
1556
1557/// <https://hl7.org/fhirpath/N1/#decimal-conversion-functions>
1558fn to_decimal(value: Convertible) -> Option<f64> {
1559    match value {
1560        Convertible::Boolean(value) => Some(f64::from(u8::from(value))),
1561        Convertible::Number(number) => Some(number.as_f64()),
1562        Convertible::String(value) if is_decimal(&value) => value.parse().ok(),
1563        _ => None,
1564    }
1565}
1566
1567/// <https://hl7.org/fhirpath/N1/#date-conversion-functions>: the date part of a
1568/// `Date`, `DateTime` or date-formatted `String`, keeping its precision.
1569fn to_date(value: Convertible) -> Option<Date> {
1570    let (Convertible::String(value) | Convertible::DateTime(value)) = value else {
1571        return None;
1572    };
1573    parse_any_datetime(&value)?;
1574    let date = value
1575        .split_once('T')
1576        .map_or(value.as_str(), |(date, _)| date);
1577    parse_date(date).ok()
1578}
1579
1580/// <https://hl7.org/fhirpath/N1/#datetime-conversion-functions>: a `Date`,
1581/// `DateTime` or datetime-formatted `String`, keeping its precision.
1582fn to_date_time(value: Convertible) -> Option<AnyDateTime> {
1583    let (Convertible::String(value) | Convertible::DateTime(value)) = value else {
1584        return None;
1585    };
1586    parse_any_datetime(&value)
1587}
1588
1589/// <https://hl7.org/fhirpath/N1/#time-conversion-functions>: a `Time` or
1590/// time-formatted `String`, keeping its precision.
1591fn to_time(value: Convertible) -> Option<Time> {
1592    let (Convertible::String(value) | Convertible::DateTime(value)) = value else {
1593        return None;
1594    };
1595    parse_partial_time(&value).ok()
1596}
1597
1598/// A Decimal's String form, which keeps a fraction so it reads as a Decimal.
1599fn decimal_string(value: f64) -> String {
1600    let string = value.to_string();
1601    if string.contains('.') {
1602        string
1603    } else {
1604        string + ".0"
1605    }
1606}
1607
1608/// <https://hl7.org/fhirpath/N1/#string-conversion-functions>
1609fn to_string(value: Convertible) -> Option<String> {
1610    match value {
1611        Convertible::Boolean(value) => Some(value.to_string()),
1612        Convertible::Number(Number::Integer(value)) => Some(value.to_string()),
1613        Convertible::Number(Number::Decimal(value)) => Some(decimal_string(value)),
1614        Convertible::String(value) | Convertible::DateTime(value) => Some(value),
1615        Convertible::Quantity(quantity) => {
1616            let value = quantity.value.as_ref()?.value?;
1617            let unit = quantity_unit(quantity);
1618            // Calendar keywords are bare, UCUM units are quoted.
1619            Some(if CALENDAR_UNITS.contains(&unit) {
1620                format!("{value} {unit}")
1621            } else {
1622                format!("{value} '{unit}'")
1623            })
1624        }
1625    }
1626}
1627
1628/// The UCUM code when there is one, as `FHIRPath` quantities use UCUM units.
1629fn quantity_unit(quantity: &Quantity) -> &str {
1630    let code = quantity
1631        .code
1632        .as_ref()
1633        .and_then(|code| code.value.as_deref());
1634    let unit = quantity
1635        .unit
1636        .as_ref()
1637        .and_then(|unit| unit.value.as_deref());
1638    code.or(unit).unwrap_or("1")
1639}
1640
1641const UCUM_SYSTEM: &str = "http://unitsofmeasure.org";
1642
1643/// The spec's calendar duration keywords, which are not UCUM units.
1644const CALENDAR_UNITS: [&str; 16] = [
1645    "year",
1646    "years",
1647    "month",
1648    "months",
1649    "week",
1650    "weeks",
1651    "day",
1652    "days",
1653    "hour",
1654    "hours",
1655    "minute",
1656    "minutes",
1657    "second",
1658    "seconds",
1659    "millisecond",
1660    "milliseconds",
1661];
1662
1663/// The spec's Quantity string format: a number then an optional quoted UCUM
1664/// unit or calendar duration keyword.
1665static QUANTITY_STRING: LazyLock<Regex> = LazyLock::new(|| {
1666    Regex::new(r"^([+-]?\d+(?:\.\d+)?)\s*(?:'([^']+)'|([a-zA-Z]+))?$")
1667        .expect("quantity regex is valid")
1668});
1669
1670fn new_quantity(value: f64, unit: &str) -> Quantity {
1671    let ucum = !CALENDAR_UNITS.contains(&unit);
1672    Quantity {
1673        value: Some(Box::new(FHIRDecimal {
1674            value: Some(value),
1675            ..Default::default()
1676        })),
1677        unit: Some(Box::new(unit.to_string().into())),
1678        system: ucum.then(|| Box::new(FHIRUri::from(UCUM_SYSTEM.to_string()))),
1679        code: Some(Box::new(FHIRCode::from(unit.to_string()))),
1680        ..Default::default()
1681    }
1682}
1683
1684/// <https://hl7.org/fhirpath/N1/#toquantityunit-string-quantity>. Without a
1685/// UCUM engine, a `unit` is only satisfied by the item's own unit.
1686fn to_quantity(value: Convertible, unit: Option<&str>) -> Option<Quantity> {
1687    let quantity = match value {
1688        Convertible::Quantity(quantity) => {
1689            quantity.value.as_ref()?.value?;
1690            quantity.clone()
1691        }
1692        Convertible::Boolean(value) => new_quantity(f64::from(u8::from(value)), "1"),
1693        Convertible::Number(number) => new_quantity(number.as_f64(), "1"),
1694        Convertible::String(value) => {
1695            let captures = QUANTITY_STRING.captures(&value)?;
1696            let number = captures[1].parse().ok()?;
1697            let unit = match (captures.get(2), captures.get(3)) {
1698                (Some(quoted), _) => quoted.as_str(),
1699                // A bare word must be a calendar keyword.
1700                (None, Some(bare)) if CALENDAR_UNITS.contains(&bare.as_str()) => bare.as_str(),
1701                (None, Some(_)) => return None,
1702                (None, None) => "1",
1703            };
1704            new_quantity(number, unit)
1705        }
1706        Convertible::DateTime(_) => return None,
1707    };
1708    unit.is_none_or(|unit| unit == quantity_unit(&quantity))
1709        .then_some(quantity)
1710}
1711
1712/// `toQuantity([unit])` and `convertsToQuantity([unit])`.
1713async fn evaluate_to_quantity<'a>(
1714    function: &FunctionInvocation,
1715    context: Context<'a>,
1716    config: Option<Arc<Config<'a>>>,
1717) -> Result<Context<'a>, FHIRPathError> {
1718    validate_arguments(&function.arguments, &Cardinality::Custom(0, 1))?;
1719
1720    let name = function.name.0.as_str();
1721    let converts = name == "convertsToQuantity";
1722    let empty = context.new_context_from(vec![]);
1723    let Some(value) = singleton(name, &context)? else {
1724        return Ok(empty);
1725    };
1726
1727    let mut unit = None;
1728    if let Some(argument) = function.arguments.first() {
1729        let argument = evaluate_expression(argument, context.clone(), config).await?;
1730        match singleton(name, &argument)? {
1731            Some(argument) => unit = Some(downcast_string(argument)?),
1732            None => return Ok(empty),
1733        }
1734    }
1735
1736    let quantity = Convertible::of(value)?.and_then(|value| to_quantity(value, unit.as_deref()));
1737    Ok(match quantity {
1738        _ if converts => boolean_result(&context, quantity.is_some()),
1739        Some(quantity) => context.new_context_from(vec![context.allocate_literal(quantity)]),
1740        None => empty,
1741    })
1742}
1743
1744/// `toX()` gives the converted item or empty, `convertsToX()` whether there is
1745/// one, per <https://hl7.org/fhirpath/N1/#conversion>.
1746fn evaluate_conversion<'a>(
1747    function: &FunctionInvocation,
1748    context: &Context<'a>,
1749) -> Result<Context<'a>, FHIRPathError> {
1750    validate_arguments(&function.arguments, &Cardinality::Zero)?;
1751
1752    let name = function.name.0.as_str();
1753    let Some(value) = singleton(name, context)? else {
1754        return Ok(context.new_context_from(vec![]));
1755    };
1756    let (converts, target) = match name.strip_prefix("convertsTo") {
1757        Some(target) => (true, target),
1758        None => (false, &name["to".len()..]),
1759    };
1760
1761    let converted = Convertible::of(value)?.and_then(|value| match target {
1762        "Boolean" => to_boolean(value).map(|value| boolean_result(context, value)),
1763        "Integer" => {
1764            to_integer(value).map(|value| number_result(context, Some(Number::Integer(value))))
1765        }
1766        "Decimal" => {
1767            to_decimal(value).map(|value| number_result(context, Some(Number::Decimal(value))))
1768        }
1769        "String" => to_string(value).map(|value| string_result(context, value)),
1770        "Date" => to_date(value).map(|value| {
1771            context.new_context_from(vec![context.allocate_literal(FHIRDate {
1772                value: Some(value),
1773                ..Default::default()
1774            })])
1775        }),
1776        "DateTime" => to_date_time(value).map(|value| datetime_result(context, value)),
1777        "Time" => to_time(value).map(|value| {
1778            context.new_context_from(vec![context.allocate_literal(FHIRTime {
1779                value: Some(value),
1780                ..Default::default()
1781            })])
1782        }),
1783        _ => unreachable!(),
1784    });
1785
1786    if converts {
1787        Ok(boolean_result(context, converted.is_some()))
1788    } else {
1789        Ok(converted.unwrap_or_else(|| context.new_context_from(vec![])))
1790    }
1791}
1792
1793/// `now`, `timeOfDay` and `today`, per
1794/// <https://hl7.org/fhirpath/N1/#current-date-and-time-functions>.
1795fn evaluate_current_time<'a>(
1796    function: &FunctionInvocation,
1797    context: &Context<'a>,
1798) -> Result<Context<'a>, FHIRPathError> {
1799    validate_arguments(&function.arguments, &Cardinality::Zero)?;
1800
1801    let now = context.now();
1802    let value = match function.name.0.as_str() {
1803        "now" => context.allocate_literal(FHIRDateTime {
1804            value: Some(DateTime::Iso8601(now)),
1805            ..Default::default()
1806        }),
1807        "timeOfDay" => context.allocate_literal(FHIRTime {
1808            value: Some(
1809                parse_time(&now.format("%H:%M:%S%.3f").to_string())
1810                    .map_err(|e| FHIRPathError::InternalError(format!("{e:?}")))?,
1811            ),
1812            ..Default::default()
1813        }),
1814        _ => context.allocate_literal(FHIRDate {
1815            value: Some(
1816                parse_date(&now.format("%Y-%m-%d").to_string())
1817                    .map_err(|e| FHIRPathError::InternalError(format!("{e:?}")))?,
1818            ),
1819            ..Default::default()
1820        }),
1821    };
1822    Ok(context.new_context_from(vec![value]))
1823}
1824
1825/// <https://hl7.org/fhirpath/N1/#tracename-string-projection-expression-collection>:
1826/// logs at debug level and returns the input. The arguments are only evaluated
1827/// when that level is on.
1828async fn evaluate_trace<'a>(
1829    function: &FunctionInvocation,
1830    context: Context<'a>,
1831    config: Option<Arc<Config<'a>>>,
1832) -> Result<Context<'a>, FHIRPathError> {
1833    validate_arguments(&function.arguments, &Cardinality::Custom(1, 2))?;
1834
1835    if tracing::enabled!(tracing::Level::DEBUG) {
1836        let name = evaluate_expression(&function.arguments[0], context.clone(), config.clone());
1837        let name = singleton("trace", &name.await?)?.map(downcast_string);
1838        let name = name.transpose()?.unwrap_or_default();
1839
1840        let traced = match function.arguments.get(1) {
1841            Some(projection) => evaluate_expression(projection, context.clone(), config).await?,
1842            None => context.clone(),
1843        };
1844        tracing::debug!(name, values = ?traced.values, "FHIRPath trace");
1845    }
1846
1847    Ok(context)
1848}
1849
1850/// `is` as an operator or function, per <https://hl7.org/fhirpath/N1/#is-type-specifier>:
1851/// empty for an empty input and an error for more than one item.
1852fn is_type<'a>(input: &Context<'a>, spec: &TypeSpecifier) -> Result<Context<'a>, FHIRPathError> {
1853    let value = singleton("is", input)?;
1854    Ok(optional_boolean_result(
1855        input,
1856        value.map(|value| check_type(value, spec)),
1857    ))
1858}
1859
1860/// <https://hl7.org/fhirpath/N1/#istype-type-specifier>
1861fn evaluate_is_function<'a>(
1862    function: &FunctionInvocation,
1863    context: &Context<'a>,
1864) -> Result<Context<'a>, FHIRPathError> {
1865    validate_arguments(&function.arguments, &Cardinality::One)?;
1866
1867    is_type(context, &derive_typename(&function.arguments[0])?)
1868}
1869
1870fn expression_to_literal(expression: &Expression) -> Result<&Literal, FHIRPathError> {
1871    match expression {
1872        Expression::Singular(vec) if vec.len() == 1 => match &vec[0] {
1873            Term::Literal(lit) => Ok(lit),
1874            _ => Err(FHIRPathError::InternalError(
1875                "Expected a literal expression".to_string(),
1876            )),
1877        },
1878        _ => Err(FHIRPathError::InternalError(
1879            "Expected a singular expression".to_string(),
1880        )),
1881    }
1882}
1883
1884/// The string functions that test their input against one string argument.
1885#[derive(Clone, Copy)]
1886enum StringMatch {
1887    StartsWith,
1888    EndsWith,
1889    Contains,
1890}
1891
1892impl StringMatch {
1893    /// The function's name, as written in an expression.
1894    fn name(self) -> &'static str {
1895        match self {
1896            StringMatch::StartsWith => "startsWith",
1897            StringMatch::EndsWith => "endsWith",
1898            StringMatch::Contains => "contains",
1899        }
1900    }
1901
1902    /// What the function calls its argument, for error messages.
1903    fn argument(self) -> &'static str {
1904        match self {
1905            StringMatch::StartsWith => "prefix",
1906            StringMatch::EndsWith => "suffix",
1907            StringMatch::Contains => "substring",
1908        }
1909    }
1910
1911    fn matches(self, value: &str, argument: &str) -> bool {
1912        match self {
1913            StringMatch::StartsWith => value.starts_with(argument),
1914            StringMatch::EndsWith => value.ends_with(argument),
1915            StringMatch::Contains => value.contains(argument),
1916        }
1917    }
1918}
1919
1920/// `startsWith`, `endsWith` and `contains`, per
1921/// <https://hl7.org/fhirpath/N1/#startswithprefix-string-boolean> and the two
1922/// that follow it: empty when the input is empty, an error when it has more
1923/// than one value.
1924fn evaluate_string_match<'a>(
1925    string_match: StringMatch,
1926    function: &FunctionInvocation,
1927    context: &Context<'a>,
1928) -> Result<Context<'a>, FHIRPathError> {
1929    let name = string_match.name();
1930    let argument = string_match.argument();
1931
1932    if context.values.is_empty() {
1933        return Ok(context.new_context_from(vec![]));
1934    }
1935
1936    if context.values.len() > 1 {
1937        return Err(FHIRPathError::InternalError(format!(
1938            "{name} function requires no more than one context value"
1939        )));
1940    }
1941
1942    if function.arguments.is_empty() {
1943        return Ok(context.new_context_from(vec![]));
1944    }
1945
1946    validate_arguments(&function.arguments, &Cardinality::Custom(1, 1))?;
1947
1948    let literal = if let Some(arg) = function.arguments.first() {
1949        expression_to_literal(arg)?
1950    } else {
1951        return Err(FHIRPathError::InternalError(format!(
1952            "{name} function requires a {argument} argument"
1953        )));
1954    };
1955
1956    let Literal::String(literal) = literal else {
1957        return Err(FHIRPathError::InternalError(format!(
1958            "{name} function requires a string {argument}"
1959        )));
1960    };
1961
1962    let value_string = context
1963        .values
1964        .first()
1965        .and_then(|k| downcast_string(*k).ok())
1966        .unwrap_or_default();
1967
1968    Ok(
1969        context.new_context_from(vec![context.allocate_literal(FHIRBoolean {
1970            value: Some(string_match.matches(&value_string, literal)),
1971            ..Default::default()
1972        })]),
1973    )
1974}
1975
1976fn evaluate_get_reference_key<'a>(
1977    function: &FunctionInvocation,
1978    context: &Context<'a>,
1979) -> Result<Context<'a>, FHIRPathError> {
1980    validate_arguments(&function.arguments, &Cardinality::Custom(0, 1))?;
1981
1982    let type_to_filter_by = if let Some(resource_type) = function.arguments.first() {
1983        Some(derive_typename(resource_type)?)
1984    } else {
1985        None
1986    };
1987
1988    let ids = context
1989        .iter()
1990        .filter_map(|value| {
1991            let reference = value.as_any().downcast_ref::<Reference>()?;
1992
1993            let mut pieces = reference
1994                .reference
1995                .as_ref()
1996                .and_then(|r| r.value.as_ref())
1997                .map(|r| r.split('/'))?;
1998
1999            let resource_type = pieces.next()?;
2000            let id = pieces.next()?;
2001
2002            if let Some(type_to_filter_by) = &type_to_filter_by
2003                && !check_type_name(resource_type, type_to_filter_by)
2004            {
2005                return None;
2006            }
2007
2008            Some(FHIRId {
2009                value: Some(id.to_string()),
2010                ..Default::default()
2011            })
2012        })
2013        .collect::<Vec<_>>();
2014
2015    let mut next_context = Vec::with_capacity(ids.len());
2016
2017    for id in ids {
2018        next_context.push(context.allocate_literal(id));
2019    }
2020
2021    Ok(context.new_context_from(next_context))
2022}
2023
2024fn evaluate_get_resource_key<'a>(
2025    function: &FunctionInvocation,
2026    context: &Context<'a>,
2027    config: Option<Arc<Config<'a>>>,
2028) -> Result<Context<'a>, FHIRPathError> {
2029    validate_arguments(&function.arguments, &Cardinality::Zero)?;
2030
2031    let Some(id) = config.and_then(|c| c.resource_id.clone()) else {
2032        return Err(FHIRPathError::InternalError(
2033            "getResourceKey function requires resource_id in config".to_string(),
2034        ));
2035    };
2036
2037    let resource_key = FHIRId {
2038        value: Some(id),
2039        ..Default::default()
2040    };
2041
2042    Ok(context.new_context_from(vec![context.allocate_literal(resource_key)]))
2043}
2044
2045fn equal_check<'b>(left: &Context<'b>, right: &Context<'b>) -> Result<Option<bool>, FHIRPathError> {
2046    equality(left.values[0], right.values[0])
2047}
2048
2049/// Equality as the collection functions use it: dates and times that cannot be compared are
2050/// unequal.
2051fn value_equal(left: &dyn MetaValue, right: &dyn MetaValue) -> Result<bool, FHIRPathError> {
2052    Ok(equality(left, right)?.unwrap_or(false))
2053}
2054
2055/// A Quantity's unit as written: the code, else the unit text, `None` when
2056/// it has neither.
2057fn quantity_unit_text(quantity: &Quantity) -> Option<&str> {
2058    let code = quantity
2059        .code
2060        .as_ref()
2061        .and_then(|code| code.value.as_deref());
2062    let unit = quantity
2063        .unit
2064        .as_ref()
2065        .and_then(|unit| unit.value.as_deref());
2066    code.filter(|code| !code.is_empty())
2067        .or_else(|| unit.filter(|unit| !unit.is_empty()))
2068}
2069
2070/// Both items as Quantities, `None` when either is another type.
2071fn as_quantities<'v>(
2072    left: &'v dyn MetaValue,
2073    right: &'v dyn MetaValue,
2074) -> Option<(&'v Quantity, &'v Quantity)> {
2075    Some((
2076        left.as_any().downcast_ref::<Quantity>()?,
2077        right.as_any().downcast_ref::<Quantity>()?,
2078    ))
2079}
2080
2081/// How two Quantities order by value; `None` when they have different units
2082/// or no value. Units compare case-sensitively and are not converted.
2083fn quantity_order(left: &Quantity, right: &Quantity) -> Option<Ordering> {
2084    if quantity_unit_text(left) != quantity_unit_text(right) {
2085        return None;
2086    }
2087    let value = |quantity: &Quantity| quantity.value.as_ref().and_then(|value| value.value);
2088    value(left)
2089        .zip(value(right))
2090        .and_then(|(left, right)| left.partial_cmp(&right))
2091}
2092
2093/// `=` on two items, `None` for dates and times that cannot be compared.
2094fn equality(left: &dyn MetaValue, right: &dyn MetaValue) -> Result<Option<bool>, FHIRPathError> {
2095    // An element equals itself. Other complex values are unequal until
2096    // member-wise equality is implemented.
2097    if std::ptr::addr_eq(left, right) && left.fhir_type() == right.fhir_type() {
2098        return Ok(Some(true));
2099    }
2100
2101    if NUMBER_TYPES.contains(left.fhir_type()) && NUMBER_TYPES.contains(right.fhir_type()) {
2102        let left_value = downcast_number(left)?;
2103        let right_value = downcast_number(right)?;
2104        #[allow(clippy::float_cmp)]
2105        Ok(Some(left_value == right_value))
2106    } else if STRING_TYPES.contains(left.fhir_type()) && STRING_TYPES.contains(right.fhir_type()) {
2107        let left_value = downcast_string(left)?;
2108        let right_value = downcast_string(right)?;
2109        Ok(Some(left_value == right_value))
2110    } else if BOOLEAN_TYPES.contains(left.fhir_type()) && BOOLEAN_TYPES.contains(right.fhir_type())
2111    {
2112        let left_value = downcast_bool(left)?;
2113        let right_value = downcast_bool(right)?;
2114        // Suppress clippy warning because strict equality comparison for float values
2115        // is intentional and required by the FHIRPath specification.
2116        #[allow(clippy::float_cmp)]
2117        Ok(Some(left_value == right_value))
2118    } else if let Some(order) = temporal_order(left, right) {
2119        Ok(match order {
2120            TemporalOrder::Ordered(ordering) => Some(ordering.is_eq()),
2121            TemporalOrder::Unknown => None,
2122        })
2123    } else if let Some(order) = time_order(left, right) {
2124        Ok(match order {
2125            TemporalOrder::Ordered(ordering) => Some(ordering.is_eq()),
2126            TemporalOrder::Unknown => None,
2127        })
2128    } else if let Some((left, right)) = as_quantities(left, right) {
2129        Ok(quantity_order(left, right).map(Ordering::is_eq))
2130    } else {
2131        // https://hl7.org/fhirpath/N1/#conversion for implicit conversion rules todo.
2132        //
2133        // If types do not match return false.
2134        // Should consider implicit conversion rules here but for now
2135        // FPs like 'Patient.deceased.exists() and Patient.deceased != false' (deceased is either boolean or dateTime)
2136        // Should return false rather than error.
2137        Ok(Some(false))
2138    }
2139}
2140
2141async fn evaluate_operation<'a>(
2142    operation: &Operation,
2143    context: Context<'a>,
2144    config: Option<Arc<Config<'a>>>,
2145) -> Result<Context<'a>, FHIRPathError> {
2146    match operation {
2147        Operation::Add(left, right) => evaluate_add(left, right, context, config).await,
2148        Operation::Subtraction(left, right) => {
2149            evaluate_subtraction(left, right, context, config).await
2150        }
2151        Operation::Multiplication(left, right) => {
2152            evaluate_multiplication(left, right, context, config).await
2153        }
2154        Operation::Division(left, right) => evaluate_division(left, right, context, config).await,
2155        Operation::Equal(left, right) => evaluate_equal(left, right, context, config).await,
2156        Operation::NotEqual(left, right) => evaluate_not_equal(left, right, context, config).await,
2157        Operation::And(left, right) => evaluate_and(left, right, context, config).await,
2158        Operation::Or(left, right) => evaluate_or(left, right, context, config).await,
2159        Operation::Union(left, right) => evaluate_union(left, right, context, config).await,
2160        Operation::Is(expr, ty) => evaluate_is(expr, ty, context, config).await,
2161        Operation::As(expr, ty) => evaluate_as(expr, ty, context, config).await,
2162        Operation::XOr(left, right) => evaluate_xor(left, right, context, config).await,
2163
2164        Operation::Modulo(left, right) => {
2165            evaluate_truncated_division(left, right, context, config, true).await
2166        }
2167        Operation::Polarity(polarity, expression) => {
2168            evaluate_polarity(polarity, expression, context, config).await
2169        }
2170        Operation::DivisionTruncated(left, right) => {
2171            evaluate_truncated_division(left, right, context, config, false).await
2172        }
2173        Operation::Concatenate(left, right) => {
2174            evaluate_concatenate(left, right, context, config).await
2175        }
2176        Operation::LessThan(left, right) => evaluate_less_than(left, right, context, config).await,
2177        Operation::GreaterThan(left, right) => {
2178            evaluate_greater_than(left, right, context, config).await
2179        }
2180        Operation::LessThanEqual(left, right) => {
2181            evaluate_less_than_equal(left, right, context, config).await
2182        }
2183        Operation::GreaterThanEqual(left, right) => {
2184            evaluate_greater_than_equal(left, right, context, config).await
2185        }
2186        Operation::Equivalent(left, right) => {
2187            evaluate_equivalence(left, right, context, config, false).await
2188        }
2189        Operation::NotEquivalent(left, right) => {
2190            evaluate_equivalence(left, right, context, config, true).await
2191        }
2192        Operation::In(element, collection) | Operation::Contains(collection, element) => {
2193            evaluate_membership(element, collection, context, config).await
2194        }
2195        Operation::Implies(left, right) => evaluate_implies(left, right, context, config).await,
2196    }
2197}
2198
2199/// A Date, `DateTime` or Time plus or minus a time-valued Quantity. `None`
2200/// when the operands are not such a pair; empty when the Quantity cannot
2201/// shift the value; an error for a unit that is not a fixed-length time unit.
2202fn temporal_arithmetic<'a>(
2203    left: &Context<'a>,
2204    right: &Context<'a>,
2205    subtract: bool,
2206) -> Result<Option<Context<'a>>, FHIRPathError> {
2207    let (Some(temporal), Some(quantity)) = (
2208        Temporal::of(left.values[0]),
2209        right.values[0].as_any().downcast_ref::<Quantity>(),
2210    ) else {
2211        return Ok(None);
2212    };
2213    let Some(amount) = quantity.value.as_ref().and_then(|value| value.value) else {
2214        return Ok(None);
2215    };
2216    let shifted = temporal
2217        .shift(amount, quantity_unit(quantity), subtract)
2218        .map_err(FHIRPathError::OperationError)?;
2219    Ok(Some(left.new_context_from(match shifted {
2220        Some(Shifted::Date(value)) => vec![left.allocate_literal(FHIRDate {
2221            value: Some(value),
2222            ..Default::default()
2223        })],
2224        Some(Shifted::DateTime(value)) => vec![left.allocate_literal(FHIRDateTime {
2225            value: Some(value),
2226            ..Default::default()
2227        })],
2228        Some(Shifted::PartialDateTime(value)) => vec![left.allocate_literal(value)],
2229        Some(Shifted::Time(value)) => vec![left.allocate_literal(FHIRTime {
2230            value: Some(value),
2231            ..Default::default()
2232        })],
2233        None => vec![],
2234    })))
2235}
2236
2237async fn evaluate_add<'a>(
2238    left: &Expression,
2239    right: &Expression,
2240    context: Context<'a>,
2241    config: Option<Arc<Config<'a>>>,
2242) -> Result<Context<'a>, FHIRPathError> {
2243    operation_2(left, right, context, config, |left, right| {
2244        Box::pin(async move {
2245            if let Some(shifted) = temporal_arithmetic(&left, &right, false)? {
2246                Ok(shifted)
2247            } else if NUMBER_TYPES.contains(left.values[0].fhir_type())
2248                && NUMBER_TYPES.contains(right.values[0].fhir_type())
2249            {
2250                let sum = Number::of(left.values[0])?.combine(
2251                    Number::of(right.values[0])?,
2252                    i64::checked_add,
2253                    |l, r| l + r,
2254                );
2255
2256                Ok(number_result(&left, sum))
2257            } else if STRING_TYPES.contains(left.values[0].fhir_type())
2258                && STRING_TYPES.contains(right.values[0].fhir_type())
2259            {
2260                let left_string = downcast_string(left.values[0])?;
2261                let right_string = downcast_string(right.values[0])?;
2262
2263                Ok(
2264                    left.new_context_from(vec![left.allocate_literal(FHIRString {
2265                        value: Some(left_string + &right_string),
2266                        ..Default::default()
2267                    })]),
2268                )
2269            } else {
2270                Err(FHIRPathError::OperationError(OperationError::TypeMismatch(
2271                    left.values[0].fhir_type(),
2272                    right.values[0].fhir_type(),
2273                )))
2274            }
2275        })
2276    })
2277    .await
2278}
2279
2280async fn evaluate_numeric_binary<'a>(
2281    left: &Expression,
2282    right: &Expression,
2283    context: Context<'a>,
2284    config: Option<Arc<Config<'a>>>,
2285    integer_op: fn(i64, i64) -> Option<i64>,
2286    decimal_op: fn(f64, f64) -> f64,
2287) -> Result<Context<'a>, FHIRPathError> {
2288    operation_2(left, right, context, config, move |left, right| {
2289        Box::pin(async move {
2290            let result = Number::of(left.values[0])?.combine(
2291                Number::of(right.values[0])?,
2292                integer_op,
2293                decimal_op,
2294            );
2295
2296            Ok(number_result(&left, result))
2297        })
2298    })
2299    .await
2300}
2301
2302/// Ordered comparison of two numbers, strings (code point order) or
2303/// same-unit Quantities.
2304/// Empty when unordered (NaN); errors on any other operand types.
2305async fn evaluate_ordered_comparison<'a>(
2306    left: &Expression,
2307    right: &Expression,
2308    context: Context<'a>,
2309    config: Option<Arc<Config<'a>>>,
2310    accept: fn(Ordering) -> bool,
2311) -> Result<Context<'a>, FHIRPathError> {
2312    operation_2(left, right, context, config, move |left, right| {
2313        Box::pin(async move {
2314            let (l, r) = (left.values[0], right.values[0]);
2315            let ordering = if let Some(order) = temporal_order(l, r).or_else(|| time_order(l, r)) {
2316                match order {
2317                    TemporalOrder::Ordered(ordering) => Some(ordering),
2318                    TemporalOrder::Unknown => None,
2319                }
2320            } else if let (Ok(l), Ok(r)) = (downcast_number(l), downcast_number(r)) {
2321                l.partial_cmp(&r)
2322            } else if let (Ok(l), Ok(r)) = (downcast_string(l), downcast_string(r)) {
2323                Some(l.cmp(&r))
2324            } else if let Some((l, r)) = as_quantities(l, r) {
2325                quantity_order(l, r)
2326            } else {
2327                return Err(FHIRPathError::OperationError(OperationError::InvalidType(
2328                    l.fhir_type(),
2329                    r.fhir_type(),
2330                )));
2331            };
2332
2333            Ok(left.new_context_from(
2334                ordering
2335                    .map(|o| {
2336                        left.allocate_literal(FHIRBoolean {
2337                            value: Some(accept(o)),
2338                            ..Default::default()
2339                        })
2340                    })
2341                    .into_iter()
2342                    .collect(),
2343            ))
2344        })
2345    })
2346    .await
2347}
2348
2349async fn evaluate_subtraction<'a>(
2350    left: &Expression,
2351    right: &Expression,
2352    context: Context<'a>,
2353    config: Option<Arc<Config<'a>>>,
2354) -> Result<Context<'a>, FHIRPathError> {
2355    operation_2(left, right, context, config, |left, right| {
2356        Box::pin(async move {
2357            if let Some(shifted) = temporal_arithmetic(&left, &right, true)? {
2358                return Ok(shifted);
2359            }
2360            let difference = Number::of(left.values[0])?.combine(
2361                Number::of(right.values[0])?,
2362                i64::checked_sub,
2363                |l, r| l - r,
2364            );
2365
2366            Ok(number_result(&left, difference))
2367        })
2368    })
2369    .await
2370}
2371
2372async fn evaluate_multiplication<'a>(
2373    left: &Expression,
2374    right: &Expression,
2375    context: Context<'a>,
2376    config: Option<Arc<Config<'a>>>,
2377) -> Result<Context<'a>, FHIRPathError> {
2378    evaluate_numeric_binary(left, right, context, config, i64::checked_mul, |l, r| l * r).await
2379}
2380
2381async fn evaluate_division<'a>(
2382    left: &Expression,
2383    right: &Expression,
2384    context: Context<'a>,
2385    config: Option<Arc<Config<'a>>>,
2386) -> Result<Context<'a>, FHIRPathError> {
2387    // `/` is always a Decimal; dividing by 0 is empty.
2388    operation_2(left, right, context, config, |left, right| {
2389        Box::pin(async move {
2390            let quotient =
2391                Number::of(left.values[0])?.as_f64() / Number::of(right.values[0])?.as_f64();
2392
2393            Ok(number_result(&left, Number::decimal_from(quotient)))
2394        })
2395    })
2396    .await
2397}
2398
2399async fn evaluate_less_than<'a>(
2400    left: &Expression,
2401    right: &Expression,
2402    context: Context<'a>,
2403    config: Option<Arc<Config<'a>>>,
2404) -> Result<Context<'a>, FHIRPathError> {
2405    evaluate_ordered_comparison(left, right, context, config, Ordering::is_lt).await
2406}
2407
2408async fn evaluate_less_than_equal<'a>(
2409    left: &Expression,
2410    right: &Expression,
2411    context: Context<'a>,
2412    config: Option<Arc<Config<'a>>>,
2413) -> Result<Context<'a>, FHIRPathError> {
2414    evaluate_ordered_comparison(left, right, context, config, Ordering::is_le).await
2415}
2416
2417async fn evaluate_greater_than<'a>(
2418    left: &Expression,
2419    right: &Expression,
2420    context: Context<'a>,
2421    config: Option<Arc<Config<'a>>>,
2422) -> Result<Context<'a>, FHIRPathError> {
2423    evaluate_ordered_comparison(left, right, context, config, Ordering::is_gt).await
2424}
2425
2426async fn evaluate_greater_than_equal<'a>(
2427    left: &Expression,
2428    right: &Expression,
2429    context: Context<'a>,
2430    config: Option<Arc<Config<'a>>>,
2431) -> Result<Context<'a>, FHIRPathError> {
2432    evaluate_ordered_comparison(left, right, context, config, Ordering::is_ge).await
2433}
2434
2435/// Three-valued logic: <https://hl7.org/fhirpath/N1/#boolean-logic>.
2436/// Both operands are always evaluated (no short-circuit semantics).
2437async fn evaluate_boolean_binary<'a>(
2438    name: &str,
2439    left: &Expression,
2440    right: &Expression,
2441    context: Context<'a>,
2442    config: Option<Arc<Config<'a>>>,
2443    op: fn(Option<bool>, Option<bool>) -> Option<bool>,
2444) -> Result<Context<'a>, FHIRPathError> {
2445    let left = evaluate_expression(left, context.clone(), config.clone()).await?;
2446    let left = singleton_boolean(name, &left)?;
2447    let right = evaluate_expression(right, context.clone(), config).await?;
2448    let right = singleton_boolean(name, &right)?;
2449
2450    Ok(optional_boolean_result(&context, op(left, right)))
2451}
2452
2453/// <https://hl7.org/fhirpath/N1/#and>
2454async fn evaluate_and<'a>(
2455    left: &Expression,
2456    right: &Expression,
2457    context: Context<'a>,
2458    config: Option<Arc<Config<'a>>>,
2459) -> Result<Context<'a>, FHIRPathError> {
2460    evaluate_boolean_binary("and", left, right, context, config, |l, r| match (l, r) {
2461        (Some(false), _) | (_, Some(false)) => Some(false),
2462        (Some(true), Some(true)) => Some(true),
2463        _ => None,
2464    })
2465    .await
2466}
2467
2468/// <https://hl7.org/fhirpath/N1/#or>
2469async fn evaluate_or<'a>(
2470    left: &Expression,
2471    right: &Expression,
2472    context: Context<'a>,
2473    config: Option<Arc<Config<'a>>>,
2474) -> Result<Context<'a>, FHIRPathError> {
2475    evaluate_boolean_binary("or", left, right, context, config, |l, r| match (l, r) {
2476        (Some(true), _) | (_, Some(true)) => Some(true),
2477        (Some(false), Some(false)) => Some(false),
2478        _ => None,
2479    })
2480    .await
2481}
2482
2483/// <https://hl7.org/fhirpath/N1/#xor>
2484async fn evaluate_xor<'a>(
2485    left: &Expression,
2486    right: &Expression,
2487    context: Context<'a>,
2488    config: Option<Arc<Config<'a>>>,
2489) -> Result<Context<'a>, FHIRPathError> {
2490    evaluate_boolean_binary("xor", left, right, context, config, |l, r| Some(l? ^ r?)).await
2491}
2492
2493/// <https://hl7.org/fhirpath/N1/#equals>. Collections are equal when they have the same length
2494/// and each pair, in order, is equal. `None` when a pair cannot be compared.
2495fn collection_equal<'b>(
2496    left: &Context<'b>,
2497    right: &Context<'b>,
2498) -> Result<Option<bool>, FHIRPathError> {
2499    if left.values.len() != right.values.len() {
2500        return Ok(Some(false));
2501    }
2502    for (l, r) in left.values.iter().zip(&right.values) {
2503        match equality(*l, *r)? {
2504            Some(true) => {}
2505            other => return Ok(other),
2506        }
2507    }
2508    Ok(Some(true))
2509}
2510
2511async fn evaluate_equality<'a>(
2512    left: &Expression,
2513    right: &Expression,
2514    context: Context<'a>,
2515    config: Option<Arc<Config<'a>>>,
2516    negate: bool,
2517) -> Result<Context<'a>, FHIRPathError> {
2518    operation_n(left, right, context, config, |left, right| {
2519        // An empty side gives an empty result.
2520        if left.values.is_empty() || right.values.is_empty() {
2521            return Ok(left.new_context_from(vec![]));
2522        }
2523
2524        let result = if left.values.len() == 1 && right.values.len() == 1 {
2525            equal_check(&left, &right)?
2526        } else {
2527            collection_equal(&left, &right)?
2528        };
2529
2530        // Dates and times of different precisions cannot be compared: the result is empty.
2531        Ok(match result {
2532            Some(result) => boolean_result(&left, result != negate),
2533            None => left.new_context_from(vec![]),
2534        })
2535    })
2536    .await
2537}
2538
2539async fn evaluate_equal<'a>(
2540    left: &Expression,
2541    right: &Expression,
2542    context: Context<'a>,
2543    config: Option<Arc<Config<'a>>>,
2544) -> Result<Context<'a>, FHIRPathError> {
2545    evaluate_equality(left, right, context, config, false).await
2546}
2547
2548async fn evaluate_not_equal<'a>(
2549    left: &Expression,
2550    right: &Expression,
2551    context: Context<'a>,
2552    config: Option<Arc<Config<'a>>>,
2553) -> Result<Context<'a>, FHIRPathError> {
2554    evaluate_equality(left, right, context, config, true).await
2555}
2556
2557/// Strings are equivalent ignoring case and whitespace differences; other
2558/// types follow `=`.
2559fn value_equivalent(left: &dyn MetaValue, right: &dyn MetaValue) -> Result<bool, FHIRPathError> {
2560    if STRING_TYPES.contains(left.fhir_type()) && STRING_TYPES.contains(right.fhir_type()) {
2561        let normalize = |value: &str| {
2562            value
2563                .split_whitespace()
2564                .map(str::to_lowercase)
2565                .collect::<Vec<_>>()
2566        };
2567        Ok(normalize(&downcast_string(left)?) == normalize(&downcast_string(right)?))
2568    } else if let Some(equivalent) = time_equivalent(left, right) {
2569        Ok(equivalent)
2570    } else {
2571        value_equal(left, right)
2572    }
2573}
2574
2575/// <https://hl7.org/fhirpath/N1/#equivalent> and
2576/// <https://hl7.org/fhirpath/N1/#not-equivalent>. Order is ignored.
2577async fn evaluate_equivalence<'a>(
2578    left: &Expression,
2579    right: &Expression,
2580    context: Context<'a>,
2581    config: Option<Arc<Config<'a>>>,
2582    negate: bool,
2583) -> Result<Context<'a>, FHIRPathError> {
2584    operation_n(left, right, context, config, |left, right| {
2585        let mut equivalent = left.values.len() == right.values.len();
2586        if equivalent {
2587            let mut matched = vec![false; right.values.len()];
2588            'left: for item in &left.values {
2589                for (index, candidate) in right.values.iter().enumerate() {
2590                    if !matched[index] && value_equivalent(*item, *candidate)? {
2591                        matched[index] = true;
2592                        continue 'left;
2593                    }
2594                }
2595                equivalent = false;
2596                break;
2597            }
2598        }
2599        Ok(boolean_result(&left, equivalent != negate))
2600    })
2601    .await
2602}
2603
2604async fn evaluate_union<'a>(
2605    left: &Expression,
2606    right: &Expression,
2607    context: Context<'a>,
2608    config: Option<Arc<Config<'a>>>,
2609) -> Result<Context<'a>, FHIRPathError> {
2610    // <https://hl7.org/fhirpath/N1/#union-collections> removes duplicates.
2611    operation_n(left, right, context, config, |left, right| {
2612        let union = distinct_values(left.values.iter().chain(right.values.iter()).copied())?;
2613
2614        Ok(left.new_context_from(union))
2615    })
2616    .await
2617}
2618
2619/// <https://hl7.org/fhirpath/N1/#div> and <https://hl7.org/fhirpath/N1/#mod>:
2620/// empty when dividing by 0.
2621async fn evaluate_truncated_division<'a>(
2622    left: &Expression,
2623    right: &Expression,
2624    context: Context<'a>,
2625    config: Option<Arc<Config<'a>>>,
2626    remainder: bool,
2627) -> Result<Context<'a>, FHIRPathError> {
2628    operation_2(left, right, context, config, move |left, right| {
2629        Box::pin(async move {
2630            let result = match (Number::of(left.values[0])?, Number::of(right.values[0])?) {
2631                (Number::Integer(dividend), Number::Integer(divisor)) => {
2632                    let result = if remainder {
2633                        dividend.checked_rem(divisor)
2634                    } else {
2635                        dividend.checked_div(divisor)
2636                    };
2637                    result.map(Number::Integer)
2638                }
2639                // Dividing by 0.0 gives NaN or infinity, which decimal_from turns into empty.
2640                (dividend, divisor) => {
2641                    let (dividend, divisor) = (dividend.as_f64(), divisor.as_f64());
2642                    Number::decimal_from(if remainder {
2643                        dividend % divisor
2644                    } else {
2645                        (dividend / divisor).trunc()
2646                    })
2647                }
2648            };
2649
2650            Ok(number_result(&left, result))
2651        })
2652    })
2653    .await
2654}
2655
2656/// Unary `+` and `-`, per <https://hl7.org/fhirpath/N1/#operator-precedence>.
2657async fn evaluate_polarity<'a>(
2658    polarity: &Polarity,
2659    expression: &Expression,
2660    context: Context<'a>,
2661    config: Option<Arc<Config<'a>>>,
2662) -> Result<Context<'a>, FHIRPathError> {
2663    let operand = evaluate_expression(expression, context, config).await?;
2664    let Some(value) = singleton("polarity", &operand)? else {
2665        return Ok(operand);
2666    };
2667
2668    if let Some(quantity) = value.as_any().downcast_ref::<Quantity>() {
2669        return Ok(match polarity {
2670            Polarity::Positive => operand,
2671            Polarity::Negative => {
2672                let mut quantity = quantity.clone();
2673                if let Some(value) = quantity.value.as_mut() {
2674                    value.value = value.value.map(|value| -value);
2675                }
2676                operand.new_context_from(vec![operand.allocate_literal(quantity)])
2677            }
2678        });
2679    }
2680
2681    let result = match (polarity, Number::of(value)?) {
2682        (Polarity::Positive, number) => Some(number),
2683        (Polarity::Negative, Number::Integer(value)) => value.checked_neg().map(Number::Integer),
2684        (Polarity::Negative, Number::Decimal(value)) => Some(Number::Decimal(-value)),
2685    };
2686
2687    Ok(number_result(&operand, result))
2688}
2689
2690/// <https://hl7.org/fhirpath/N1/#string-concatenation>: an empty operand
2691/// counts as `''`.
2692async fn evaluate_concatenate<'a>(
2693    left: &Expression,
2694    right: &Expression,
2695    context: Context<'a>,
2696    config: Option<Arc<Config<'a>>>,
2697) -> Result<Context<'a>, FHIRPathError> {
2698    operation_n(left, right, context, config, |left, right| {
2699        let mut concatenated = String::new();
2700        for operand in [&left, &right] {
2701            if let Some(value) = singleton("&", operand)? {
2702                concatenated.push_str(&downcast_string(value)?);
2703            }
2704        }
2705
2706        Ok(string_result(&left, concatenated))
2707    })
2708    .await
2709}
2710
2711/// `in` and `contains`, per <https://hl7.org/fhirpath/N1/#membership>: empty
2712/// for an empty element, false for an empty collection.
2713async fn evaluate_membership<'a>(
2714    element: &Expression,
2715    collection: &Expression,
2716    context: Context<'a>,
2717    config: Option<Arc<Config<'a>>>,
2718) -> Result<Context<'a>, FHIRPathError> {
2719    operation_n(
2720        element,
2721        collection,
2722        context,
2723        config,
2724        |element, collection| {
2725            let Some(value) = singleton("membership", &element)? else {
2726                return Ok(element);
2727            };
2728
2729            let contained = contains_value(&collection.values, value)?;
2730            Ok(boolean_result(&element, contained))
2731        },
2732    )
2733    .await
2734}
2735
2736/// <https://hl7.org/fhirpath/N1/#implies>
2737async fn evaluate_implies<'a>(
2738    left: &Expression,
2739    right: &Expression,
2740    context: Context<'a>,
2741    config: Option<Arc<Config<'a>>>,
2742) -> Result<Context<'a>, FHIRPathError> {
2743    let left = evaluate_expression(left, context.clone(), config.clone()).await?;
2744    let left = singleton_boolean("implies", &left)?;
2745
2746    // A false left operand makes the right one irrelevant.
2747    if left == Some(false) {
2748        return Ok(boolean_result(&context, true));
2749    }
2750
2751    let right = evaluate_expression(right, context.clone(), config).await?;
2752    let right = singleton_boolean("implies", &right)?;
2753
2754    let result = match (left, right) {
2755        (Some(_), right) => right,
2756        (None, Some(true)) => Some(true),
2757        (None, _) => None,
2758    };
2759
2760    Ok(optional_boolean_result(&context, result))
2761}
2762
2763async fn evaluate_type_operation<'a>(
2764    expression: &Expression,
2765    type_name: &QualifiedIdentifier,
2766    context: Context<'a>,
2767    config: Option<Arc<Config<'a>>>,
2768    return_context: bool,
2769) -> Result<Context<'a>, FHIRPathError> {
2770    let left = evaluate_expression(expression, context, config).await?;
2771
2772    let Some(type_name) = TypeSpecifier::new(&type_name.0, |id| Some(id.0.as_str())) else {
2773        return Ok(left.new_context_from(vec![]));
2774    };
2775
2776    if return_context {
2777        // <https://hl7.org/fhirpath/N1/#as-type-specifier>
2778        let value = singleton("as", &left)?.filter(|value| check_type(*value, &type_name));
2779        Ok(left.new_context_from(value.into_iter().collect()))
2780    } else {
2781        is_type(&left, &type_name)
2782    }
2783}
2784
2785async fn evaluate_is<'a>(
2786    expression: &Expression,
2787    type_name: &QualifiedIdentifier,
2788    context: Context<'a>,
2789    config: Option<Arc<Config<'a>>>,
2790) -> Result<Context<'a>, FHIRPathError> {
2791    evaluate_type_operation(expression, type_name, context, config, false).await
2792}
2793
2794async fn evaluate_as<'a>(
2795    expression: &Expression,
2796    type_name: &QualifiedIdentifier,
2797    context: Context<'a>,
2798    config: Option<Arc<Config<'a>>>,
2799) -> Result<Context<'a>, FHIRPathError> {
2800    evaluate_type_operation(expression, type_name, context, config, true).await
2801}
2802
2803fn evaluate_expression<'a>(
2804    ast: &Expression,
2805    context: Context<'a>,
2806    config: Option<Arc<Config<'a>>>,
2807) -> Pin<Box<impl Future<Output = Result<Context<'a>, FHIRPathError>>>> {
2808    Box::pin(async move {
2809        match ast {
2810            Expression::Operation(operation) => {
2811                evaluate_operation(operation, context, config).await
2812            }
2813            Expression::Singular(singular_ast) => {
2814                evaluate_singular(singular_ast, context, config).await
2815            }
2816        }
2817    })
2818}
2819
2820#[derive(Debug)]
2821pub enum ResolvedValue {
2822    Box(Box<dyn MetaValue>),
2823    Arc(Arc<dyn MetaValue>),
2824}
2825
2826impl ResolvedValue {
2827    #[must_use]
2828    pub fn as_meta_value(&self) -> &dyn MetaValue {
2829        match self {
2830            ResolvedValue::Box(b) => &**b,
2831            ResolvedValue::Arc(a) => &**a,
2832        }
2833    }
2834}
2835
2836pub struct Context<'a> {
2837    allocator: Arc<Mutex<allocators::bumpalo::Allocator>>,
2838    values: Vec<&'a dyn MetaValue>,
2839    // Position of the current item in an iteration function, backing `$index`.
2840    index: Option<usize>,
2841}
2842
2843pub type ResolvedValueFuture = Pin<Box<dyn Future<Output = Option<ResolvedValue>> + Send>>;
2844pub type ResolveValueCallback = Box<dyn Fn(String) -> ResolvedValueFuture + Send + Sync>;
2845
2846pub enum ExternalConstantResolver<'a> {
2847    Function(ResolveValueCallback),
2848    Variable(Arc<HashMap<String, &'a dyn MetaValue>>),
2849}
2850
2851#[derive(Default)]
2852pub struct Config<'a> {
2853    // Used to resolve resource_id in getResourceKey for sql-on-fhir.
2854    resource_id: Option<String>,
2855    variable_resolver: Option<ExternalConstantResolver<'a>>,
2856    // Backs memberOf, subsumes, subsumedBy, conformsTo and resolve.
2857    services: Option<Arc<dyn FHIRServices>>,
2858}
2859
2860impl<'a> Config<'a> {
2861    #[must_use]
2862    pub fn builder() -> Self {
2863        Config::default()
2864    }
2865
2866    #[must_use]
2867    pub fn with_variable_resolver(mut self, resolver: ExternalConstantResolver<'a>) -> Self {
2868        self.variable_resolver = Some(resolver);
2869        self
2870    }
2871
2872    #[must_use]
2873    pub fn with_services(mut self, services: Arc<dyn FHIRServices>) -> Self {
2874        self.services = Some(services);
2875        self
2876    }
2877
2878    #[must_use]
2879    pub fn with_resource_id(mut self, resource_id: String) -> Self {
2880        self.resource_id = Some(resource_id);
2881        self
2882    }
2883}
2884
2885/// Variables FHIR defines for every evaluation, resolved before the caller's resolver.
2886fn fhir_environment_variable(name: &str) -> Option<String> {
2887    match name {
2888        "sct" => Some("http://snomed.info/sct".to_string()),
2889        "loinc" => Some("http://loinc.org".to_string()),
2890        "ucum" => Some("http://unitsofmeasure.org".to_string()),
2891        _ => name
2892            .strip_prefix("vs-")
2893            .map(|n| format!("http://hl7.org/fhir/ValueSet/{n}"))
2894            .or_else(|| {
2895                name.strip_prefix("ext-")
2896                    .map(|n| format!("http://hl7.org/fhir/StructureDefinition/{n}"))
2897            }),
2898    }
2899}
2900
2901async fn resolve_external_constant<'a>(
2902    name: &str,
2903    resolver: Option<&ExternalConstantResolver<'a>>,
2904    context: Context<'a>,
2905) -> Result<Context<'a>, FHIRPathError> {
2906    if let Some(value) = fhir_environment_variable(name) {
2907        return Ok(
2908            context.new_context_from(vec![context.allocate_literal(FHIRString {
2909                value: Some(value),
2910                ..Default::default()
2911            })]),
2912        );
2913    }
2914
2915    let external_constant = match resolver {
2916        Some(ExternalConstantResolver::Function(func)) => {
2917            let result = func(name.to_string()).await;
2918
2919            result.map(|result| context.allocate(result))
2920        }
2921        // A name missing from a fixed set of variables is an error, not empty.
2922        Some(ExternalConstantResolver::Variable(map)) => Some(
2923            map.get(name)
2924                .copied()
2925                .ok_or_else(|| FHIRPathError::UndefinedConstant(name.to_string()))?,
2926        ),
2927        None => None,
2928    };
2929
2930    if let Some(result) = external_constant {
2931        return Ok(context.new_context_from(vec![result]));
2932    }
2933    Ok(context.new_context_from(vec![]))
2934}
2935
2936impl<'a> IntoIterator for &'a Context<'_> {
2937    type Item = &'a dyn haste_reflect::MetaValue;
2938
2939    type IntoIter = std::boxed::Box<
2940        dyn std::iter::Iterator<Item = &'a (dyn haste_reflect::MetaValue + 'static)> + 'a,
2941    >;
2942
2943    fn into_iter(self) -> Self::IntoIter {
2944        self.iter()
2945    }
2946}
2947
2948impl<'a> Context<'a> {
2949    fn new(
2950        values: Vec<&'a dyn MetaValue>,
2951        allocator: Arc<Mutex<allocators::bumpalo::Allocator>>,
2952    ) -> Self {
2953        Self {
2954            allocator,
2955            values,
2956            index: None,
2957        }
2958    }
2959    fn new_context_from(&self, values: Vec<&'a dyn MetaValue>) -> Self {
2960        Self {
2961            allocator: self.allocator.clone(),
2962            values,
2963            index: self.index,
2964        }
2965    }
2966    /// A singleton context for one item of an iteration function, binding `$index`.
2967    fn new_item_context(&self, value: &'a dyn MetaValue, index: usize) -> Self {
2968        Self {
2969            index: Some(index),
2970            ..self.new_context_from(vec![value])
2971        }
2972    }
2973    fn allocate(&self, value: ResolvedValue) -> &'a dyn MetaValue {
2974        self.allocator
2975            .lock()
2976            .unwrap_or_else(std::sync::PoisonError::into_inner)
2977            .allocate_resolved(value)
2978    }
2979
2980    fn now(&self) -> chrono::DateTime<chrono::Utc> {
2981        self.allocator
2982            .lock()
2983            .unwrap_or_else(std::sync::PoisonError::into_inner)
2984            .now()
2985    }
2986
2987    /// Counts items `repeat()` produced, failing once the evaluation's total passes
2988    /// [`REPEAT_ITEM_LIMIT`].
2989    fn count_repeat_items(&self, count: usize) -> Result<(), FHIRPathError> {
2990        let total = self
2991            .allocator
2992            .lock()
2993            .unwrap_or_else(std::sync::PoisonError::into_inner)
2994            .add_repeat_items(count);
2995        if total > REPEAT_ITEM_LIMIT {
2996            return Err(FunctionError::InvalidFunctionCall(format!(
2997                "repeat() produced more than {REPEAT_ITEM_LIMIT} items"
2998            ))
2999            .into());
3000        }
3001        Ok(())
3002    }
3003
3004    fn allocate_literal<T: MetaValue>(&self, value: T) -> &'a dyn MetaValue {
3005        self.allocator
3006            .lock()
3007            .unwrap_or_else(std::sync::PoisonError::into_inner)
3008            .allocate_literal(value)
3009    }
3010
3011    /// The values, which live as long as `'a` rather than this borrow.
3012    #[must_use]
3013    pub fn values(&self) -> &[&'a dyn MetaValue] {
3014        &self.values
3015    }
3016
3017    #[must_use]
3018    pub fn iter(&'a self) -> Box<dyn Iterator<Item = &'a dyn MetaValue> + 'a> {
3019        Box::new(self.values.iter().copied())
3020    }
3021
3022    #[must_use]
3023    pub fn is_empty(&self) -> bool {
3024        self.values.is_empty()
3025    }
3026}
3027
3028impl Clone for Context<'_> {
3029    fn clone(&self) -> Self {
3030        Self {
3031            allocator: self.allocator.clone(),
3032            values: self.values.clone(),
3033            index: self.index,
3034        }
3035    }
3036}
3037
3038pub struct FPEngine {}
3039
3040static AST: LazyLock<DashMap<String, Arc<Expression>>> = LazyLock::new(DashMap::new);
3041
3042fn get_ast(path: &str) -> Result<Arc<Expression>, FHIRPathError> {
3043    if let Some(expression_ast) = AST.get(path) {
3044        return Ok(expression_ast.clone());
3045    }
3046
3047    let expression_ast = Arc::new(parser::parse(path)?);
3048    AST.insert(path.to_string(), expression_ast.clone());
3049
3050    Ok(expression_ast)
3051}
3052
3053impl Default for FPEngine {
3054    fn default() -> Self {
3055        Self::new()
3056    }
3057}
3058
3059impl FPEngine {
3060    #[must_use]
3061    pub fn new() -> Self {
3062        Self {}
3063    }
3064
3065    /// Evaluate a `FHIRPath` expression against a context.
3066    /// The context is a vector of references to `MetaValue` objects.
3067    /// The path is a `FHIRPath` expression.
3068    /// The result is a vector of references to `MetaValue` objects.
3069    ///
3070    /// # Errors
3071    ///
3072    /// Returns a [`FHIRPathError`] if the expression parsing fails (bad AST)
3073    /// or if the evaluation engine encounters an error during execution.
3074    pub async fn evaluate<'a, 'b>(
3075        &self,
3076        path: &str,
3077        values: Vec<&'a dyn MetaValue>,
3078    ) -> Result<Context<'b>, FHIRPathError>
3079    where
3080        'a: 'b,
3081    {
3082        let ast = get_ast(path)?;
3083
3084        // Store created.
3085        let allocator: Arc<Mutex<allocators::bumpalo::Allocator>> =
3086            Arc::new(Mutex::new(allocators::bumpalo::Allocator::new()));
3087
3088        let context = Context::new(values, allocator.clone());
3089
3090        let result = evaluate_expression(&ast, context, None).await?;
3091        Ok(result)
3092    }
3093
3094    /// Evaluate a `FHIRPath` expression against a context.
3095    /// The context is a vector of references to `MetaValue` objects.
3096    /// The path is a `FHIRPath` expression.
3097    /// The result is a vector of references to `MetaValue` objects.
3098    ///
3099    /// # Errors
3100    ///
3101    /// Returns a [`FHIRPathError`] if the expression parsing fails (bad AST)
3102    /// or if the evaluation engine encounters an error during execution.
3103    pub async fn evaluate_with_config<'a, 'b>(
3104        &self,
3105        path: &str,
3106        values: Vec<&'a dyn MetaValue>,
3107        config: Arc<Config<'b>>,
3108    ) -> Result<Context<'b>, FHIRPathError>
3109    where
3110        'a: 'b,
3111    {
3112        let ast = get_ast(path)?;
3113
3114        // Store created.
3115        let allocator = Arc::new(Mutex::new(allocators::bumpalo::Allocator::new()));
3116
3117        let context = Context::new(values, allocator.clone());
3118
3119        let result = evaluate_expression(&ast, context, Some(config)).await?;
3120
3121        Ok(result)
3122    }
3123}
3124
3125#[cfg(test)]
3126mod tests {
3127    use super::*;
3128    use haste_fhir_model::r4::{
3129        datetime::DateTime,
3130        generated::{
3131            resources::{
3132                Bundle, Group, GroupMember, Patient, PatientDeceasedTypeChoice, PatientLink,
3133                Resource, SearchParameter,
3134            },
3135            types::{
3136                Extension, ExtensionValueTypeChoice, FHIRDateTime, FHIRString, FHIRUri, HumanName,
3137                Identifier, Reference,
3138            },
3139        },
3140    };
3141
3142    use haste_reflect_derive::Reflect;
3143
3144    #[derive(Reflect, Debug)]
3145    #[fhir_type = "BackboneElement"]
3146    struct C {
3147        c: String,
3148    }
3149
3150    #[derive(Reflect, Debug)]
3151    #[fhir_type = "BackboneElement"]
3152    struct B {
3153        b: Vec<C>,
3154    }
3155
3156    #[derive(Reflect, Debug)]
3157    #[fhir_type = "BackboneElement"]
3158    struct A {
3159        a: Vec<B>,
3160    }
3161
3162    fn load_search_parameters() -> Vec<SearchParameter> {
3163        let json = include_str!(
3164            "../../../../artifacts/r4/hl7-core/definitions/hl7/search-parameters.min.json"
3165        );
3166        let bundle = serde_json::from_str::<Bundle>(json).unwrap();
3167
3168        bundle
3169            .entry
3170            .unwrap_or_default()
3171            .into_iter()
3172            .filter_map(|e| e.resource)
3173            .filter_map(|e| match *e {
3174                Resource::SearchParameter(sp) => Some(sp),
3175                _ => None,
3176            })
3177            .collect()
3178    }
3179
3180    #[tokio::test]
3181    async fn filter_typechoice_test() {
3182        let patient = Patient {
3183            id: Some("patient-id".to_string()),
3184            deceased: Some(PatientDeceasedTypeChoice::Boolean(Box::new(FHIRBoolean {
3185                value: Some(true),
3186                ..Default::default()
3187            }))),
3188            ..Default::default()
3189        };
3190
3191        let engine = FPEngine::new();
3192        let result = engine
3193            .evaluate("(Patient.deceased.ofType(dateTime))", vec![&patient])
3194            .await
3195            .unwrap();
3196
3197        assert_eq!(result.values.len(), 0);
3198
3199        let result = engine
3200            .evaluate("(Patient.deceased.ofType(boolean))", vec![&patient])
3201            .await
3202            .unwrap();
3203
3204        let value = result.values[0];
3205        let boolean_value: &FHIRBoolean = value
3206            .as_any()
3207            .downcast_ref::<FHIRBoolean>()
3208            .expect("Failed to downcast to FHIRBoolean");
3209
3210        assert_eq!(boolean_value.value, Some(true));
3211
3212        let patient = Patient {
3213            id: Some("patient-id".to_string()),
3214            deceased: Some(PatientDeceasedTypeChoice::DateTime(Box::new(
3215                FHIRDateTime {
3216                    value: Some(DateTime::Year(1980)),
3217                    ..Default::default()
3218                },
3219            ))),
3220            ..Default::default()
3221        };
3222
3223        let result = engine
3224            .evaluate("(Patient.deceased.ofType(boolean))", vec![&patient])
3225            .await
3226            .unwrap();
3227
3228        assert_eq!(result.values.len(), 0);
3229
3230        let result = engine
3231            .evaluate("(Patient.deceased.ofType(dateTime))", vec![&patient])
3232            .await
3233            .unwrap();
3234
3235        assert_eq!(result.values.len(), 1);
3236
3237        let value = result.values[0];
3238        let datetime_value: &FHIRDateTime = value
3239            .as_any()
3240            .downcast_ref::<FHIRDateTime>()
3241            .expect("Failed to downcast to FHIRDateTime");
3242
3243        assert_eq!(datetime_value.value, Some(DateTime::Year(1980)));
3244    }
3245
3246    #[tokio::test]
3247    async fn test_current_date_and_time() {
3248        let engine = FPEngine::new();
3249        let result = engine
3250            .evaluate(
3251                "now().toString().substring(0, 10) = today().toString() \
3252                 and now().toString().substring(11, 8) = timeOfDay().toString().substring(0, 8) \
3253                 and today().toString() = today().toString()",
3254                vec![],
3255            )
3256            .await
3257            .unwrap();
3258        assert_eq!(result.values.len(), 1);
3259        assert!(downcast_bool(result.values[0]).unwrap());
3260
3261        for name in ["now", "today", "timeOfDay"] {
3262            assert!(
3263                engine
3264                    .evaluate(&format!("{name}(1)"), vec![])
3265                    .await
3266                    .is_err()
3267            );
3268        }
3269    }
3270
3271    #[tokio::test]
3272    async fn test_variable_resolution() {
3273        let engine = FPEngine::new();
3274        let patient = Patient {
3275            id: Some("my-patient".to_string()),
3276            ..Default::default()
3277        };
3278        let config = Arc::new(
3279            Config::builder().with_variable_resolver(ExternalConstantResolver::Variable(Arc::new(
3280                vec![("patient".to_string(), &patient as &dyn MetaValue)]
3281                    .into_iter()
3282                    .collect(),
3283            ))),
3284        );
3285
3286        let result = engine
3287            .evaluate_with_config("%patient", vec![], config.clone())
3288            .await
3289            .unwrap();
3290
3291        assert_eq!(result.values.len(), 1);
3292        let p = result.values[0].as_any().downcast_ref::<Patient>().unwrap();
3293
3294        assert_eq!(p.id, patient.id);
3295
3296        let result_failed = engine.evaluate_with_config("%nobody", vec![], config).await;
3297
3298        assert!(matches!(
3299            result_failed,
3300            Err(FHIRPathError::UndefinedConstant(name)) if name == "nobody"
3301        ));
3302    }
3303
3304    #[tokio::test]
3305    async fn test_where_clause() {
3306        let engine = FPEngine::new();
3307        let mut patient = Patient::default();
3308        let mut identifier = Identifier::default();
3309        let extension = Extension {
3310            id: None,
3311            url: "test-extension".to_string(),
3312            extension: None,
3313            value: Some(ExtensionValueTypeChoice::String(Box::new(FHIRString {
3314                id: None,
3315                extension: None,
3316                value: Some("example value".to_string()),
3317            }))),
3318        };
3319        identifier.value = Some(Box::new(FHIRString {
3320            id: None,
3321            extension: Some(vec![extension]),
3322            value: Some("12345".to_string()),
3323        }));
3324        patient.identifier_ = Some(vec![identifier]);
3325
3326        let context = engine
3327            .evaluate(
3328                "$this.identifier.value.where($this.extension.value.exists())",
3329                vec![&patient],
3330            )
3331            .await;
3332
3333        assert_eq!(context.unwrap().values.len(), 1);
3334
3335        let context = engine
3336            .evaluate(
3337                "$this.identifier.value.where($this.extension.extension.exists())",
3338                vec![&patient],
3339            )
3340            .await;
3341        assert_eq!(context.unwrap().values.len(), 0);
3342    }
3343
3344    #[tokio::test]
3345    async fn test_all_parameters() {
3346        let search_parameters = load_search_parameters();
3347        for param in &search_parameters {
3348            if let Some(expression) = &param.expression {
3349                let engine = FPEngine::new();
3350                let context = engine
3351                    .evaluate(expression.value.as_ref().unwrap().as_str(), vec![])
3352                    .await;
3353
3354                if let Err(err) = context {
3355                    panic!(
3356                        "Failed to evaluate search parameter '{}': {}",
3357                        expression.value.as_ref().unwrap(),
3358                        err
3359                    );
3360                }
3361            }
3362        }
3363    }
3364
3365    fn test_patient() -> Patient {
3366        let mut patient = Patient::default();
3367        let name = HumanName {
3368            given: Some(vec![FHIRString {
3369                id: None,
3370                extension: None,
3371                value: Some("Bob".to_string()),
3372            }]),
3373            ..Default::default()
3374        };
3375        let mrn_identifier = Identifier {
3376            value: Some(Box::new(FHIRString {
3377                id: None,
3378                extension: None,
3379                value: Some("mrn-12345".to_string()),
3380            })),
3381            system: Some(Box::new(FHIRUri {
3382                id: None,
3383                extension: None,
3384                value: Some("mrn".to_string()),
3385            })),
3386            ..Default::default()
3387        };
3388        let ssn_identifier = Identifier {
3389            value: Some(Box::new(FHIRString {
3390                id: None,
3391                extension: None,
3392                value: Some("ssn-12345".to_string()),
3393            })),
3394            system: Some(Box::new(FHIRUri {
3395                id: None,
3396                extension: None,
3397                value: Some("ssn".to_string()),
3398            })),
3399            ..Default::default()
3400        };
3401
3402        patient.identifier_ = Some(vec![mrn_identifier, ssn_identifier]);
3403        patient.name = Some(vec![name]);
3404        patient
3405    }
3406
3407    #[tokio::test]
3408    async fn indexing_tests() {
3409        let engine = FPEngine::new();
3410        let patient = test_patient();
3411
3412        let given_name = engine
3413            .evaluate("$this.name.given[0]", vec![&patient])
3414            .await
3415            .unwrap();
3416
3417        assert_eq!(given_name.values.len(), 1);
3418        let value = given_name.values[0];
3419        let name: &FHIRString = value
3420            .as_any()
3421            .downcast_ref::<FHIRString>()
3422            .expect("Failed to downcast to FHIRString");
3423
3424        assert_eq!(name.value.as_deref(), Some("Bob"));
3425
3426        let ssn_identifier = engine
3427            .evaluate("$this.identifier[1]", vec![&patient])
3428            .await
3429            .unwrap();
3430
3431        assert_eq!(ssn_identifier.values.len(), 1);
3432        let value = ssn_identifier.values[0];
3433        let identifier: &Identifier = value
3434            .as_any()
3435            .downcast_ref::<Identifier>()
3436            .expect("Failed to downcast to Identifier");
3437
3438        assert_eq!(
3439            identifier.value.as_ref().unwrap().value.as_deref(),
3440            Some("ssn-12345")
3441        );
3442
3443        let all_identifiers = engine
3444            .evaluate("$this.identifier", vec![&patient])
3445            .await
3446            .unwrap();
3447        assert_eq!(all_identifiers.values.len(), 2);
3448    }
3449
3450    #[tokio::test]
3451    async fn where_testing() {
3452        let engine = FPEngine::new();
3453        let patient = test_patient();
3454
3455        let name_where_clause = engine
3456            .evaluate(
3457                "$this.name.given.where($this.value = 'Bob')",
3458                vec![&patient],
3459            )
3460            .await
3461            .unwrap();
3462
3463        assert_eq!(name_where_clause.values.len(), 1);
3464        let value = name_where_clause.values[0];
3465        let name: &FHIRString = value
3466            .as_any()
3467            .downcast_ref::<FHIRString>()
3468            .expect("Failed to downcast to FHIRString");
3469
3470        assert_eq!(name.value.as_deref(), Some("Bob"));
3471
3472        let ssn_identifier_clause = engine
3473            .evaluate(
3474                "$this.identifier.where($this.system.value = 'ssn')",
3475                vec![&patient],
3476            )
3477            .await
3478            .unwrap();
3479        assert_eq!(ssn_identifier_clause.values.len(), 1);
3480
3481        let ssn_identifier = ssn_identifier_clause.values[0]
3482            .as_any()
3483            .downcast_ref::<Identifier>()
3484            .expect("Failed to downcast to Identifier");
3485
3486        assert_eq!(
3487            ssn_identifier.value.as_ref().unwrap().value.as_deref(),
3488            Some("ssn-12345")
3489        );
3490    }
3491
3492    #[tokio::test]
3493    async fn test_equality() {
3494        let engine = FPEngine::new();
3495
3496        // String tests
3497        let string_equal = engine.evaluate("'test' = 'test'", vec![]).await.unwrap();
3498        for r in &string_equal {
3499            let b = r
3500                .as_any()
3501                .downcast_ref::<FHIRBoolean>()
3502                .unwrap()
3503                .value
3504                .unwrap();
3505            assert!(b);
3506        }
3507        let string_unequal = engine.evaluate("'invalid' = 'test'", vec![]).await.unwrap();
3508        for r in &string_unequal {
3509            let b = r
3510                .as_any()
3511                .downcast_ref::<FHIRBoolean>()
3512                .unwrap()
3513                .value
3514                .unwrap();
3515            assert!(!b);
3516        }
3517
3518        // Number tests
3519        let number_equal = engine.evaluate("12 = 12", vec![]).await.unwrap();
3520        for r in &number_equal {
3521            let b = r
3522                .as_any()
3523                .downcast_ref::<FHIRBoolean>()
3524                .unwrap()
3525                .value
3526                .unwrap();
3527            assert!(b);
3528        }
3529        let number_unequal = engine.evaluate("13 = 12", vec![]).await.unwrap();
3530        for r in &number_unequal {
3531            let b = r
3532                .as_any()
3533                .downcast_ref::<FHIRBoolean>()
3534                .unwrap()
3535                .value
3536                .unwrap();
3537            assert!(!b);
3538        }
3539
3540        // Boolean tests
3541        let bool_equal = engine.evaluate("false = false", vec![]).await.unwrap();
3542        for r in &bool_equal {
3543            let b = r
3544                .as_any()
3545                .downcast_ref::<FHIRBoolean>()
3546                .unwrap()
3547                .value
3548                .unwrap();
3549            assert!(b);
3550        }
3551        let bool_unequal = engine.evaluate("false = true", vec![]).await.unwrap();
3552        for r in &bool_unequal {
3553            let b = r
3554                .as_any()
3555                .downcast_ref::<FHIRBoolean>()
3556                .unwrap()
3557                .value
3558                .unwrap();
3559            assert!(!b);
3560        }
3561
3562        // Nested Equality tests
3563        let bool_equal = engine.evaluate("12 = 13 = false", vec![]).await.unwrap();
3564        for r in &bool_equal {
3565            let b = r
3566                .as_any()
3567                .downcast_ref::<FHIRBoolean>()
3568                .unwrap()
3569                .value
3570                .unwrap();
3571            assert!(b);
3572        }
3573        let bool_unequal = engine.evaluate("12 = 13 = true", vec![]).await.unwrap();
3574        for r in &bool_unequal {
3575            let b = r
3576                .as_any()
3577                .downcast_ref::<FHIRBoolean>()
3578                .unwrap()
3579                .value
3580                .unwrap();
3581            assert!(!b);
3582        }
3583        let bool_unequal = engine.evaluate("12 = (13 - 1)", vec![]).await.unwrap();
3584        for r in &bool_unequal {
3585            let b = r
3586                .as_any()
3587                .downcast_ref::<FHIRBoolean>()
3588                .unwrap()
3589                .value
3590                .unwrap();
3591            assert!(b);
3592        }
3593    }
3594
3595    #[tokio::test]
3596    async fn test_string_concat() {
3597        let engine = FPEngine::new();
3598        let patient = test_patient();
3599
3600        let simple_result = engine.evaluate("'Hello' + ' World'", vec![]).await.unwrap();
3601        for r in &simple_result {
3602            let s = r.as_any().downcast_ref::<FHIRString>().unwrap().clone();
3603            assert_eq!(s.value, Some("Hello World".to_string()));
3604        }
3605
3606        let simple_result = engine
3607            .evaluate("$this.name.given + ' Miller'", vec![&patient])
3608            .await
3609            .unwrap();
3610        for r in &simple_result {
3611            let s = r.as_any().downcast_ref::<FHIRString>().unwrap().clone();
3612            assert_eq!(s.value, Some("Bob Miller".to_string()));
3613        }
3614    }
3615
3616    #[tokio::test]
3617    async fn test_positive_and_unsigned_int() {
3618        let engine = FPEngine::new();
3619        let study = serde_json::from_str::<Resource>(
3620            r#"{"resourceType":"ImagingStudy","status":"available","subject":{"reference":"Patient/1"},"numberOfSeries":9,"numberOfInstances":4}"#,
3621        )
3622        .unwrap();
3623
3624        for (expression, expected) in [
3625            ("ImagingStudy.numberOfSeries = 9", true),
3626            ("ImagingStudy.numberOfSeries > 3", true),
3627            ("ImagingStudy.numberOfInstances < 3", false),
3628        ] {
3629            let result = engine.evaluate(expression, vec![&study]).await.unwrap();
3630            let b = result.values[0]
3631                .as_any()
3632                .downcast_ref::<FHIRBoolean>()
3633                .unwrap();
3634            assert_eq!(b.value, Some(expected), "{expression}");
3635        }
3636
3637        let sum = engine
3638            .evaluate("ImagingStudy.numberOfSeries + 1", vec![&study])
3639            .await
3640            .unwrap();
3641        let sum = sum.values[0]
3642            .as_any()
3643            .downcast_ref::<FHIRInteger>()
3644            .unwrap();
3645        assert_eq!(sum.value, Some(10));
3646    }
3647
3648    #[tokio::test]
3649    async fn test_simple() {
3650        let root = A {
3651            a: vec![B {
3652                b: vec![C {
3653                    c: "whatever".to_string(),
3654                }],
3655            }],
3656        };
3657
3658        let engine = FPEngine::new();
3659        let result = engine.evaluate("a.b.c", vec![&root]).await.unwrap();
3660
3661        let strings: Vec<&String> = result
3662            .iter()
3663            .map(|r| r.as_any().downcast_ref::<String>().unwrap())
3664            .collect();
3665
3666        assert_eq!(strings, vec!["whatever"]);
3667    }
3668
3669    #[tokio::test]
3670    async fn allocation() {
3671        let engine = FPEngine::new();
3672        let result = engine.evaluate("'asdf'", vec![]).await.unwrap();
3673
3674        for r in &result {
3675            let s = r.as_any().downcast_ref::<FHIRString>().unwrap().clone();
3676
3677            assert_eq!(s.value, Some("asdf".to_string()));
3678        }
3679    }
3680
3681    #[tokio::test]
3682    async fn order_operation() {
3683        let engine = FPEngine::new();
3684        let result = engine.evaluate("45 + 2  * 3", vec![]).await.unwrap();
3685
3686        for r in &result {
3687            let s = r.as_any().downcast_ref::<FHIRInteger>().unwrap().clone();
3688
3689            assert_eq!(s.value, Some(51));
3690        }
3691    }
3692
3693    #[tokio::test]
3694    async fn xor_operation() {
3695        let engine = FPEngine::new();
3696        let result = engine.evaluate("true xor true", vec![]).await.unwrap();
3697
3698        for r in &result {
3699            let b = r
3700                .as_any()
3701                .downcast_ref::<FHIRBoolean>()
3702                .unwrap()
3703                .value
3704                .unwrap();
3705
3706            assert!(!b);
3707        }
3708    }
3709
3710    #[tokio::test]
3711    async fn domain_resource_filter() {
3712        let engine = FPEngine::new();
3713
3714        let patient =
3715            serde_json::from_str::<Resource>(r#"{"id": "patient-id", "resourceType": "Patient"}"#)
3716                .unwrap();
3717        let result = engine
3718            .evaluate("Resource.id", vec![&patient])
3719            .await
3720            .unwrap();
3721        let ids: Vec<&String> = result
3722            .iter()
3723            .map(|r| r.as_any().downcast_ref::<String>().unwrap())
3724            .collect();
3725
3726        assert_eq!(ids.len(), 1);
3727        assert_eq!(ids[0], "patient-id");
3728
3729        let result2 = engine
3730            .evaluate("DomainResource.id", vec![&patient])
3731            .await
3732            .unwrap();
3733        let ids2: Vec<&String> = result2
3734            .iter()
3735            .map(|r| r.as_any().downcast_ref::<String>().unwrap())
3736            .collect();
3737        assert_eq!(ids2.len(), 1);
3738        assert_eq!(ids2[0], "patient-id");
3739    }
3740
3741    #[tokio::test]
3742    async fn type_test() {
3743        let engine = FPEngine::new();
3744        let patient = Patient::default();
3745
3746        let result = engine
3747            .evaluate("$this.type().name", vec![&patient])
3748            .await
3749            .unwrap();
3750        let ids: Vec<&String> = result
3751            .iter()
3752            .map(|r| r.as_any().downcast_ref::<String>().unwrap())
3753            .collect();
3754
3755        assert_eq!(ids.len(), 1);
3756        assert_eq!(ids[0], "Patient");
3757    }
3758
3759    #[tokio::test]
3760    async fn resolve_test() {
3761        let engine = FPEngine::new();
3762        let observation = serde_json::from_str::<Resource>(r#"
3763             {
3764                "resourceType": "Observation",
3765                "id": "f001",
3766                "text": {
3767                    "status": "generated",
3768                    "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Generated Narrative with Details</b></p><p><b>id</b>: f001</p><p><b>identifier</b>: 6323 (OFFICIAL)</p><p><b>status</b>: final</p><p><b>code</b>: Glucose [Moles/volume] in Blood <span>(Details : {LOINC code '15074-8' = 'Glucose [Moles/volume] in Blood', given as 'Glucose [Moles/volume] in Blood'})</span></p><p><b>subject</b>: <a>P. van de Heuvel</a></p><p><b>effective</b>: 02/04/2013 9:30:10 AM --&gt; (ongoing)</p><p><b>issued</b>: 03/04/2013 3:30:10 PM</p><p><b>performer</b>: <a>A. Langeveld</a></p><p><b>value</b>: 6.3 mmol/l<span> (Details: UCUM code mmol/L = 'mmol/L')</span></p><p><b>interpretation</b>: High <span>(Details : {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation code 'H' = 'High', given as 'High'})</span></p><h3>ReferenceRanges</h3><table><tr><td>-</td><td><b>Low</b></td><td><b>High</b></td></tr><tr><td>*</td><td>3.1 mmol/l<span> (Details: UCUM code mmol/L = 'mmol/L')</span></td><td>6.2 mmol/l<span> (Details: UCUM code mmol/L = 'mmol/L')</span></td></tr></table></div>"
3769                },
3770                "identifier": [
3771                    {
3772                    "use": "official",
3773                    "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
3774                    "value": "6323"
3775                    }
3776                ],
3777                "status": "final",
3778                "code": {
3779                    "coding": [
3780                    {
3781                        "system": "http://loinc.org",
3782                        "code": "15074-8",
3783                        "display": "Glucose [Moles/volume] in Blood"
3784                    }
3785                    ]
3786                },
3787                "subject": {
3788                    "reference": "Patient/f001",
3789                    "display": "P. van de Heuvel"
3790                },
3791                "effectivePeriod": {
3792                    "start": "2013-04-02T09:30:10+01:00"
3793                },
3794                "issued": "2013-04-03T15:30:10+01:00",
3795                "performer": [
3796                    {
3797                    "reference": "Practitioner/f005",
3798                    "display": "A. Langeveld"
3799                    }
3800                ],
3801                "valueQuantity": {
3802                    "value": 6.3,
3803                    "unit": "mmol/l",
3804                    "system": "http://unitsofmeasure.org",
3805                    "code": "mmol/L"
3806                },
3807                "interpretation": [
3808                    {
3809                    "coding": [
3810                        {
3811                        "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
3812                        "code": "H",
3813                        "display": "High"
3814                        }
3815                    ]
3816                    }
3817                ],
3818                "referenceRange": [
3819                    {
3820                    "low": {
3821                        "value": 3.1,
3822                        "unit": "mmol/l",
3823                        "system": "http://unitsofmeasure.org",
3824                        "code": "mmol/L"
3825                    },
3826                    "high": {
3827                        "value": 6.2,
3828                        "unit": "mmol/l",
3829                        "system": "http://unitsofmeasure.org",
3830                        "code": "mmol/L"
3831                    }
3832                    }
3833                ]
3834                }
3835            "#).unwrap();
3836
3837        let result = engine
3838            .evaluate(
3839                "Observation.subject.where(resolve() is Patient)",
3840                vec![&observation],
3841            )
3842            .await
3843            .unwrap();
3844
3845        let references: Vec<&Reference> = result
3846            .iter()
3847            .map(|r| r.as_any().downcast_ref::<Reference>().unwrap())
3848            .collect();
3849
3850        assert_eq!(references.len(), 1);
3851        assert_eq!(
3852            references[0].reference.as_ref().unwrap().value,
3853            Some("Patient/f001".to_string())
3854        );
3855    }
3856
3857    #[tokio::test]
3858    async fn children_test() {
3859        let engine = FPEngine::new();
3860        let patient = Patient {
3861            name: Some(vec![HumanName {
3862                given: Some(vec![FHIRString {
3863                    value: Some("Alice".to_string()),
3864                    ..Default::default()
3865                }]),
3866                ..Default::default()
3867            }]),
3868            deceased: Some(PatientDeceasedTypeChoice::Boolean(Box::new(FHIRBoolean {
3869                value: Some(true),
3870                ..Default::default()
3871            }))),
3872            ..Default::default()
3873        };
3874
3875        let result = engine
3876            .evaluate("$this.children()", vec![&patient])
3877            .await
3878            .unwrap();
3879
3880        assert_eq!(result.values.len(), 2);
3881        assert_eq!(
3882            result
3883                .values
3884                .iter()
3885                .map(|v| v.fhir_type())
3886                .collect::<Vec<_>>(),
3887            vec!["HumanName", "boolean"]
3888        );
3889    }
3890
3891    #[tokio::test]
3892    async fn repeat_test() {
3893        let engine = FPEngine::new();
3894        let patient = Patient {
3895            name: Some(vec![HumanName {
3896                given: Some(vec![FHIRString {
3897                    value: Some("Alice".to_string()),
3898                    ..Default::default()
3899                }]),
3900                ..Default::default()
3901            }]),
3902            deceased: Some(PatientDeceasedTypeChoice::Boolean(Box::new(FHIRBoolean {
3903                value: Some(true),
3904                ..Default::default()
3905            }))),
3906            ..Default::default()
3907        };
3908
3909        let result = engine
3910            .evaluate("$this.name.given", vec![&patient])
3911            .await
3912            .unwrap();
3913
3914        assert_eq!(result.values.len(), 1);
3915
3916        assert_eq!(result.values[0].fhir_type(), "string");
3917
3918        let result = engine
3919            .evaluate("$this.repeat(children())", vec![&patient])
3920            .await
3921            .unwrap();
3922
3923        // The System values under the primitives equal them, so they are not new items.
3924        assert_eq!(
3925            result
3926                .values
3927                .iter()
3928                .map(|v| v.fhir_type())
3929                .collect::<Vec<_>>(),
3930            vec!["HumanName", "boolean", "string"]
3931        );
3932    }
3933    #[tokio::test]
3934    async fn descendants_test() {
3935        let engine = FPEngine::new();
3936        let patient = Patient {
3937            name: Some(vec![HumanName {
3938                given: Some(vec![FHIRString {
3939                    value: Some("Alice".to_string()),
3940                    ..Default::default()
3941                }]),
3942                ..Default::default()
3943            }]),
3944            deceased: Some(PatientDeceasedTypeChoice::Boolean(Box::new(FHIRBoolean {
3945                value: Some(true),
3946                ..Default::default()
3947            }))),
3948            ..Default::default()
3949        };
3950        let result = engine
3951            .evaluate("descendants()", vec![&patient])
3952            .await
3953            .unwrap();
3954
3955        assert_eq!(
3956            result
3957                .values
3958                .iter()
3959                .map(|v| v.fhir_type())
3960                .collect::<Vec<_>>(),
3961            vec![
3962                "HumanName",
3963                "boolean",
3964                "string",
3965                "http://hl7.org/fhirpath/System.Boolean",
3966                "http://hl7.org/fhirpath/System.String"
3967            ]
3968        );
3969    }
3970
3971    #[tokio::test]
3972    async fn descendants_test_filter() {
3973        let engine = FPEngine::new();
3974        let patient = Patient {
3975            link: Some(vec![PatientLink {
3976                other: Box::new(Reference {
3977                    reference: Some(Box::new(FHIRString {
3978                        value: Some("Patient/123".to_string()),
3979                        ..Default::default()
3980                    })),
3981                    ..Default::default()
3982                }),
3983                ..Default::default()
3984            }]),
3985            name: Some(vec![HumanName {
3986                given: Some(vec![FHIRString {
3987                    value: Some("Alice".to_string()),
3988                    ..Default::default()
3989                }]),
3990                ..Default::default()
3991            }]),
3992            deceased: Some(PatientDeceasedTypeChoice::Boolean(Box::new(FHIRBoolean {
3993                value: Some(true),
3994                ..Default::default()
3995            }))),
3996            ..Default::default()
3997        };
3998        let result = engine
3999            .evaluate("descendants()", vec![&patient])
4000            .await
4001            .unwrap();
4002
4003        assert_eq!(
4004            result
4005                .values
4006                .iter()
4007                .map(|v| v.fhir_type())
4008                .collect::<Vec<_>>(),
4009            vec![
4010                "HumanName",
4011                "boolean",
4012                "BackboneElement",
4013                "string",
4014                "http://hl7.org/fhirpath/System.Boolean",
4015                "Reference",
4016                "http://hl7.org/fhirpath/System.String",
4017                "string",
4018                "http://hl7.org/fhirpath/System.String"
4019            ]
4020        );
4021
4022        let result = engine
4023            .evaluate("descendants().ofType(Reference)", vec![&patient])
4024            .await
4025            .unwrap();
4026
4027        assert_eq!(
4028            result
4029                .values
4030                .iter()
4031                .map(|v| v.fhir_type())
4032                .collect::<Vec<_>>(),
4033            vec!["Reference",]
4034        );
4035
4036        let value = result.values[0]
4037            .as_any()
4038            .downcast_ref::<Reference>()
4039            .unwrap();
4040
4041        assert_eq!(
4042            value.reference.as_ref().unwrap().value.as_ref().unwrap(),
4043            "Patient/123"
4044        );
4045    }
4046
4047    #[tokio::test]
4048    async fn try_unsafe_set_from_ref() {
4049        let engine = FPEngine::new();
4050        let patient = Patient {
4051            link: Some(vec![PatientLink {
4052                other: Box::new(Reference {
4053                    reference: Some(Box::new(FHIRString {
4054                        value: Some("Patient/123".to_string()),
4055                        ..Default::default()
4056                    })),
4057                    ..Default::default()
4058                }),
4059                ..Default::default()
4060            }]),
4061            name: Some(vec![HumanName {
4062                given: Some(vec![FHIRString {
4063                    value: Some("Alice".to_string()),
4064                    ..Default::default()
4065                }]),
4066                ..Default::default()
4067            }]),
4068            deceased: Some(PatientDeceasedTypeChoice::Boolean(Box::new(FHIRBoolean {
4069                value: Some(true),
4070                ..Default::default()
4071            }))),
4072            ..Default::default()
4073        };
4074
4075        let result = engine
4076            .evaluate("descendants().ofType(Reference)", vec![&patient])
4077            .await
4078            .unwrap();
4079
4080        assert_eq!(
4081            result
4082                .values
4083                .iter()
4084                .map(|v| v.fhir_type())
4085                .collect::<Vec<_>>(),
4086            vec!["Reference",]
4087        );
4088
4089        let value = result.values[0]
4090            .as_any()
4091            .downcast_ref::<Reference>()
4092            .unwrap();
4093
4094        assert_eq!(
4095            value.reference.as_ref().unwrap().value.as_ref().unwrap(),
4096            "Patient/123"
4097        );
4098
4099        // An example for use in transaction processing where we have a reference to an object
4100        // but need to modify it in place.
4101        unsafe {
4102            let r = std::ptr::from_ref::<Reference>(value);
4103            let mut_ptr = r.cast_mut();
4104
4105            (*mut_ptr).reference = Some(Box::new(FHIRString {
4106                value: Some("Patient/456".to_string()),
4107                ..Default::default()
4108            }));
4109        }
4110
4111        assert_eq!(
4112            value.reference.as_ref().unwrap().value.as_ref().unwrap(),
4113            "Patient/456"
4114        );
4115
4116        assert_eq!(
4117            patient.link.as_ref().unwrap()[0]
4118                .other
4119                .reference
4120                .as_ref()
4121                .unwrap()
4122                .value
4123                .as_ref()
4124                .unwrap(),
4125            "Patient/456"
4126        );
4127    }
4128
4129    #[tokio::test]
4130    async fn test_external_constant_function() {
4131        let engine = FPEngine::new();
4132
4133        let config = Arc::new(Config::builder().with_variable_resolver(
4134            ExternalConstantResolver::Function(Box::new(|v| {
4135                Box::pin(async move {
4136                    match v.as_ref() {
4137                        "test_variable" => Some(ResolvedValue::Box(Box::new(Patient {
4138                            name: Some(vec![HumanName {
4139                                given: Some(vec![FHIRString {
4140                                    value: Some("Paul".to_string()),
4141                                    ..Default::default()
4142                                }]),
4143                                ..Default::default()
4144                            }]),
4145                            ..Default::default()
4146                        })
4147                            as Box<dyn MetaValue>)),
4148                        _ => None,
4149                    }
4150                })
4151            })),
4152        ));
4153
4154        let result = engine
4155            .evaluate_with_config("%test_variable.name.given", vec![], config)
4156            .await
4157            .unwrap();
4158
4159        let value = result.values[0]
4160            .as_any()
4161            .downcast_ref::<FHIRString>()
4162            .unwrap();
4163
4164        assert_eq!(value.value.as_ref(), Some(&"Paul".to_string()));
4165    }
4166
4167    #[tokio::test]
4168    async fn test_external_constant_function_reference() {
4169        let engine = FPEngine::new();
4170
4171        let patient = Arc::new(Patient {
4172            name: Some(vec![HumanName {
4173                given: Some(vec![FHIRString {
4174                    value: Some("Paul".to_string()),
4175                    ..Default::default()
4176                }]),
4177                ..Default::default()
4178            }]),
4179            ..Default::default()
4180        });
4181
4182        let resolver = {
4183            let patient = patient.clone();
4184            ExternalConstantResolver::Function(Box::new(move |v| {
4185                let patient = patient.clone();
4186                Box::pin(async move {
4187                    // let patient = patient.clone();
4188                    match v.as_ref() {
4189                        "test_variable" => Some(ResolvedValue::Arc(patient.clone())),
4190                        _ => None,
4191                    }
4192                })
4193            }))
4194        };
4195
4196        let config = Arc::new(Config::builder().with_variable_resolver(resolver));
4197
4198        let result = engine
4199            .evaluate_with_config("%test_variable.name.given", vec![], config)
4200            .await
4201            .unwrap();
4202
4203        let value = result.values[0]
4204            .as_any()
4205            .downcast_ref::<FHIRString>()
4206            .unwrap();
4207
4208        assert_eq!(value.value.as_ref(), Some(&"Paul".to_string()));
4209    }
4210
4211    #[tokio::test]
4212    async fn test_upper_function() {
4213        let engine = FPEngine::new();
4214
4215        let result = engine.evaluate("'hello'.upper()", vec![]).await.unwrap();
4216        assert_eq!(result.values.len(), 1);
4217        let value = result.values[0]
4218            .as_any()
4219            .downcast_ref::<FHIRString>()
4220            .unwrap();
4221        assert_eq!(value.value.as_deref(), Some("HELLO"));
4222
4223        let result = engine.evaluate("'AbCd'.upper()", vec![]).await.unwrap();
4224        let value = result.values[0]
4225            .as_any()
4226            .downcast_ref::<FHIRString>()
4227            .unwrap();
4228        assert_eq!(value.value.as_deref(), Some("ABCD"));
4229
4230        let result = engine.evaluate("'XYZ'.upper()", vec![]).await.unwrap();
4231        let value = result.values[0]
4232            .as_any()
4233            .downcast_ref::<FHIRString>()
4234            .unwrap();
4235        assert_eq!(value.value.as_deref(), Some("XYZ"));
4236    }
4237
4238    #[tokio::test]
4239    async fn test_lower_function() {
4240        let engine = FPEngine::new();
4241
4242        let result = engine.evaluate("'HELLO'.lower()", vec![]).await.unwrap();
4243        assert_eq!(result.values.len(), 1);
4244        let value = result.values[0]
4245            .as_any()
4246            .downcast_ref::<FHIRString>()
4247            .unwrap();
4248        assert_eq!(value.value.as_deref(), Some("hello"));
4249
4250        let result = engine.evaluate("'AbCd'.lower()", vec![]).await.unwrap();
4251        let value = result.values[0]
4252            .as_any()
4253            .downcast_ref::<FHIRString>()
4254            .unwrap();
4255        assert_eq!(value.value.as_deref(), Some("abcd"));
4256
4257        let result = engine.evaluate("'xyz'.lower()", vec![]).await.unwrap();
4258        let value = result.values[0]
4259            .as_any()
4260            .downcast_ref::<FHIRString>()
4261            .unwrap();
4262        assert_eq!(value.value.as_deref(), Some("xyz"));
4263    }
4264
4265    #[tokio::test]
4266    async fn get_resource_key() {
4267        let engine = FPEngine::new();
4268        let fp_config = Config::builder().with_resource_id("asdf".to_string());
4269
4270        let result = engine
4271            .evaluate_with_config("getResourceKey()", vec![], Arc::new(fp_config))
4272            .await
4273            .unwrap();
4274
4275        let k = result.iter().collect::<Vec<_>>();
4276
4277        assert_eq!(k.len(), 1);
4278
4279        let s = k[0].as_any().downcast_ref::<FHIRId>().unwrap();
4280        assert_eq!(s.value.as_deref(), Some("asdf"));
4281    }
4282
4283    #[tokio::test]
4284    async fn get_reference_key() {
4285        let engine = FPEngine::new();
4286
4287        let group = Group {
4288            member: Some(vec![GroupMember {
4289                entity: Box::new(Reference {
4290                    reference: Some(Box::new("Patient/123".to_string().into())),
4291                    ..Default::default()
4292                }),
4293                ..Default::default()
4294            }]),
4295            ..Default::default()
4296        };
4297
4298        let result = engine
4299            .evaluate("$this.member.entity.getReferenceKey(Patient)", vec![&group])
4300            .await
4301            .expect("Failed to evaluate getReferenceKey");
4302
4303        let ids = result.iter().collect::<Vec<_>>();
4304
4305        assert_eq!(ids.len(), 1);
4306
4307        let s = ids[0].as_any().downcast_ref::<FHIRId>().unwrap();
4308        assert_eq!(s.value.as_deref(), Some("123"));
4309
4310        let result = engine
4311            .evaluate("$this.member.entity.getReferenceKey(Group)", vec![&group])
4312            .await
4313            .expect("Failed to evaluate getReferenceKey");
4314
4315        let ids = result.iter().collect::<Vec<_>>();
4316        assert_eq!(ids.len(), 0);
4317
4318        let result = engine
4319            .evaluate("$this.member.entity.getReferenceKey()", vec![&group])
4320            .await
4321            .expect("Failed to evaluate getReferenceKey");
4322
4323        let ids = result.iter().collect::<Vec<_>>();
4324        assert_eq!(ids.len(), 1);
4325        let s = ids[0].as_any().downcast_ref::<FHIRId>().unwrap();
4326        assert_eq!(s.value.as_deref(), Some("123"));
4327    }
4328
4329    #[tokio::test]
4330    async fn exists_with_clause() {
4331        let patient = Patient {
4332            name: Some(vec![
4333                HumanName {
4334                    given: Some(vec![FHIRString {
4335                        value: Some("Alice".to_string()),
4336                        ..Default::default()
4337                    }]),
4338                    ..Default::default()
4339                },
4340                HumanName {
4341                    given: Some(vec![FHIRString {
4342                        value: Some("Matilda".to_string()),
4343                        ..Default::default()
4344                    }]),
4345                    ..Default::default()
4346                },
4347            ]),
4348            ..Default::default()
4349        };
4350
4351        let engine = FPEngine::new();
4352        let result = engine
4353            .evaluate("$this.name.exists(given.exists())", vec![&patient])
4354            .await
4355            .unwrap();
4356
4357        let result = result.iter().collect::<Vec<_>>();
4358
4359        assert_eq!(result.len(), 1);
4360        let s = result[0].as_any().downcast_ref::<FHIRBoolean>().unwrap();
4361        assert_eq!(s.value, Some(true));
4362
4363        let result = engine
4364            .evaluate("$this.name.exists(given.empty())", vec![&patient])
4365            .await
4366            .unwrap();
4367
4368        let result = result.iter().collect::<Vec<_>>();
4369        assert_eq!(result.len(), 1);
4370        let s = result[0].as_any().downcast_ref::<FHIRBoolean>().unwrap();
4371        assert_eq!(s.value, Some(false));
4372
4373        let result = engine
4374            .evaluate("$this.name.exists(given = 'Matilda')", vec![&patient])
4375            .await
4376            .unwrap();
4377
4378        let result = result.iter().collect::<Vec<_>>();
4379        assert_eq!(result.len(), 1);
4380        let s = result[0].as_any().downcast_ref::<FHIRBoolean>().unwrap();
4381        assert_eq!(s.value, Some(true));
4382
4383        let result = engine
4384            .evaluate("$this.name.exists(given = 'Jane')", vec![&patient])
4385            .await
4386            .unwrap();
4387
4388        let result = result.iter().collect::<Vec<_>>();
4389        assert_eq!(result.len(), 1);
4390        let s = result[0].as_any().downcast_ref::<FHIRBoolean>().unwrap();
4391        assert_eq!(s.value, Some(false));
4392    }
4393    #[tokio::test]
4394    async fn test_first() {
4395        let engine = FPEngine::new();
4396
4397        let group = Group {
4398            member: Some(vec![
4399                GroupMember {
4400                    entity: Box::new(Reference {
4401                        reference: Some(Box::new("Patient/1".to_string().into())),
4402                        ..Default::default()
4403                    }),
4404                    ..Default::default()
4405                },
4406                GroupMember {
4407                    entity: Box::new(Reference {
4408                        reference: Some(Box::new("Patient/2".to_string().into())),
4409                        ..Default::default()
4410                    }),
4411                    ..Default::default()
4412                },
4413            ]),
4414            ..Default::default()
4415        };
4416
4417        let result = engine
4418            .evaluate("$this.member.entity.first()", vec![&group])
4419            .await
4420            .expect("Failed to evaluate first()");
4421
4422        let references = result.iter().collect::<Vec<_>>();
4423        assert_eq!(references.len(), 1);
4424
4425        let s = references[0].as_any().downcast_ref::<Reference>().unwrap();
4426        assert_eq!(
4427            s.reference.as_ref().unwrap().value,
4428            Some("Patient/1".to_string())
4429        );
4430    }
4431
4432    #[tokio::test]
4433    async fn test_join() {
4434        let engine = FPEngine::new();
4435
4436        let mut patient = test_patient();
4437        patient.name.as_mut().unwrap()[0]
4438            .given
4439            .as_mut()
4440            .unwrap()
4441            .push(FHIRString {
4442                value: Some("David".to_string()),
4443                ..Default::default()
4444            });
4445
4446        let result = engine
4447            .evaluate("$this.name.given.join(',')", vec![&patient])
4448            .await
4449            .expect("Failed to evaluate join()");
4450
4451        let joined_ = result.iter().collect::<Vec<_>>();
4452        assert_eq!(joined_.len(), 1);
4453        let s = joined_[0].as_any().downcast_ref::<FHIRString>().unwrap();
4454        assert_eq!(s.value, Some("Bob,David".to_string()));
4455
4456        let result = engine
4457            .evaluate("$this.name.given.join()", vec![&patient])
4458            .await
4459            .expect("Failed to evaluate join()");
4460
4461        let joined_ = result.iter().collect::<Vec<_>>();
4462        assert_eq!(joined_.len(), 1);
4463        let s = joined_[0].as_any().downcast_ref::<FHIRString>().unwrap();
4464        assert_eq!(s.value, Some("BobDavid".to_string()));
4465
4466        let result = engine.evaluate("$this.name.join()", vec![&patient]).await;
4467
4468        assert!(result.is_err());
4469    }
4470
4471    #[tokio::test]
4472    async fn numerical_comparisons() {
4473        let engine = FPEngine::new();
4474
4475        let result = engine
4476            .evaluate("5 > 5", vec![])
4477            .await
4478            .expect("Failed to evaluate join()");
4479
4480        assert_eq!(result.values.len(), 1);
4481        let b = result.values[0]
4482            .as_any()
4483            .downcast_ref::<FHIRBoolean>()
4484            .unwrap();
4485        assert_eq!(b.value, Some(false));
4486
4487        let result = engine
4488            .evaluate("5 > 4", vec![])
4489            .await
4490            .expect("Failed to evaluate join()");
4491
4492        assert_eq!(result.values.len(), 1);
4493        let b = result.values[0]
4494            .as_any()
4495            .downcast_ref::<FHIRBoolean>()
4496            .unwrap();
4497        assert_eq!(b.value, Some(true));
4498
4499        let result = engine
4500            .evaluate("5 >= 5", vec![])
4501            .await
4502            .expect("Failed to evaluate join()");
4503
4504        assert_eq!(result.values.len(), 1);
4505        let b = result.values[0]
4506            .as_any()
4507            .downcast_ref::<FHIRBoolean>()
4508            .unwrap();
4509        assert_eq!(b.value, Some(true));
4510
4511        let result = engine
4512            .evaluate("4 >= 5", vec![])
4513            .await
4514            .expect("Failed to evaluate join()");
4515
4516        assert_eq!(result.values.len(), 1);
4517        let b = result.values[0]
4518            .as_any()
4519            .downcast_ref::<FHIRBoolean>()
4520            .unwrap();
4521        assert_eq!(b.value, Some(false));
4522
4523        let result = engine
4524            .evaluate("6 <= 5", vec![])
4525            .await
4526            .expect("Failed to evaluate join()");
4527
4528        assert_eq!(result.values.len(), 1);
4529        let b = result.values[0]
4530            .as_any()
4531            .downcast_ref::<FHIRBoolean>()
4532            .unwrap();
4533        assert_eq!(b.value, Some(false));
4534
4535        let result = engine
4536            .evaluate("6 <= 6", vec![])
4537            .await
4538            .expect("Failed to evaluate join()");
4539
4540        assert_eq!(result.values.len(), 1);
4541        let b = result.values[0]
4542            .as_any()
4543            .downcast_ref::<FHIRBoolean>()
4544            .unwrap();
4545        assert_eq!(b.value, Some(true));
4546
4547        let result = engine
4548            .evaluate("6 < 6", vec![])
4549            .await
4550            .expect("Failed to evaluate join()");
4551
4552        assert_eq!(result.values.len(), 1);
4553        let b = result.values[0]
4554            .as_any()
4555            .downcast_ref::<FHIRBoolean>()
4556            .unwrap();
4557        assert_eq!(b.value, Some(false));
4558
4559        let result = engine
4560            .evaluate("6 < 7", vec![])
4561            .await
4562            .expect("Failed to evaluate join()");
4563
4564        assert_eq!(result.values.len(), 1);
4565        let b = result.values[0]
4566            .as_any()
4567            .downcast_ref::<FHIRBoolean>()
4568            .unwrap();
4569        assert_eq!(b.value, Some(true));
4570    }
4571
4572    #[tokio::test]
4573    async fn string_match() {
4574        let engine = FPEngine::new();
4575
4576        let result = engine
4577            .evaluate("'test'.startsWith('te')", vec![])
4578            .await
4579            .expect("Failed to evaluate join()");
4580
4581        assert_eq!(result.values.len(), 1);
4582        let b = result.values[0]
4583            .as_any()
4584            .downcast_ref::<FHIRBoolean>()
4585            .unwrap();
4586        assert_eq!(b.value, Some(true));
4587
4588        let context: &FHIRString = &"Hello".to_string().into();
4589
4590        let result = engine
4591            .evaluate("$this.startsWith('He')", vec![context])
4592            .await
4593            .expect("Failed to evaluate join()");
4594
4595        assert_eq!(result.values.len(), 1);
4596        let b = result.values[0]
4597            .as_any()
4598            .downcast_ref::<FHIRBoolean>()
4599            .unwrap();
4600        assert_eq!(b.value, Some(true));
4601
4602        let result = engine
4603            .evaluate("$this.startsWith('EH')", vec![context])
4604            .await
4605            .expect("Failed to evaluate join()");
4606
4607        assert_eq!(result.values.len(), 1);
4608        let b = result.values[0]
4609            .as_any()
4610            .downcast_ref::<FHIRBoolean>()
4611            .unwrap();
4612        assert_eq!(b.value, Some(false));
4613
4614        let result = engine
4615            .evaluate("$this.endsWith('lo')", vec![context])
4616            .await
4617            .expect("Failed to evaluate join()");
4618
4619        assert_eq!(result.values.len(), 1);
4620        let b = result.values[0]
4621            .as_any()
4622            .downcast_ref::<FHIRBoolean>()
4623            .unwrap();
4624        assert_eq!(b.value, Some(true));
4625
4626        let result = engine
4627            .evaluate("$this.endsWith('ll')", vec![context])
4628            .await
4629            .expect("Failed to evaluate join()");
4630
4631        assert_eq!(result.values.len(), 1);
4632        let b = result.values[0]
4633            .as_any()
4634            .downcast_ref::<FHIRBoolean>()
4635            .unwrap();
4636        assert_eq!(b.value, Some(false));
4637
4638        let result = engine
4639            .evaluate("$this.contains('ll')", vec![context])
4640            .await
4641            .expect("Failed to evaluate join()");
4642
4643        assert_eq!(result.values.len(), 1);
4644        let b = result.values[0]
4645            .as_any()
4646            .downcast_ref::<FHIRBoolean>()
4647            .unwrap();
4648        assert_eq!(b.value, Some(true));
4649
4650        let result = engine
4651            .evaluate("$this.contains('ele')", vec![context])
4652            .await
4653            .expect("Failed to evaluate join()");
4654
4655        assert_eq!(result.values.len(), 1);
4656        let b = result.values[0]
4657            .as_any()
4658            .downcast_ref::<FHIRBoolean>()
4659            .unwrap();
4660        assert_eq!(b.value, Some(false));
4661    }
4662}