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 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
136fn 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
147fn 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 if float_index < 0.0 || float_index.fract() != 0.0 {
177 return Err(FHIRPathError::OperationError(OperationError::InvalidIndex));
178 }
179
180 #[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
237async 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 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#[derive(Clone, Copy)]
364enum TypeModel {
365 Any,
367 Fhir,
368 System,
369 Unknown,
371}
372
373struct TypeSpecifier<'n> {
374 model: TypeModel,
375 name: &'n str,
376}
377
378impl<'n> TypeSpecifier<'n> {
379 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
410fn 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
428fn 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 || 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 "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 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
773const REPEAT_ITEM_LIMIT: usize = 10_000;
776
777#[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 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
805async 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 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
838fn 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#[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 #[allow(clippy::cast_possible_truncation)]
934 fn integer_from(value: f64) -> Option<Number> {
935 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 fn decimal_from(value: f64) -> Option<Number> {
944 value.is_finite().then_some(Number::Decimal(value))
945 }
946
947 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
978fn 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
990fn 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
1012fn 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
1039fn 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
1050async 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
1070async 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
1088fn 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
1112async 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
1132fn 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
1150fn 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
1169async 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 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
1201async 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
1227async 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 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
1251async 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 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 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 _ => None,
1320 };
1321
1322 Ok(number_result(&context, result))
1323}
1324
1325fn 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
1346static 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 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
1373async 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 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
1484enum 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
1515fn 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
1538fn 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 Convertible::String(value) => value.parse().ok(),
1545 _ => None,
1546 }
1547}
1548
1549fn 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
1557fn 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
1567fn 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
1580fn 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
1589fn 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
1598fn 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
1608fn 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 Some(if CALENDAR_UNITS.contains(&unit) {
1620 format!("{value} {unit}")
1621 } else {
1622 format!("{value} '{unit}'")
1623 })
1624 }
1625 }
1626}
1627
1628fn 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
1643const 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
1663static 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
1684fn 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 (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
1712async 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
1744fn 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
1793fn 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
1825async 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
1850fn 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
1860fn 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#[derive(Clone, Copy)]
1886enum StringMatch {
1887 StartsWith,
1888 EndsWith,
1889 Contains,
1890}
1891
1892impl StringMatch {
1893 fn name(self) -> &'static str {
1895 match self {
1896 StringMatch::StartsWith => "startsWith",
1897 StringMatch::EndsWith => "endsWith",
1898 StringMatch::Contains => "contains",
1899 }
1900 }
1901
1902 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
1920fn 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
2049fn value_equal(left: &dyn MetaValue, right: &dyn MetaValue) -> Result<bool, FHIRPathError> {
2052 Ok(equality(left, right)?.unwrap_or(false))
2053}
2054
2055fn 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
2070fn 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
2081fn 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
2093fn equality(left: &dyn MetaValue, right: &dyn MetaValue) -> Result<Option<bool>, FHIRPathError> {
2095 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 #[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 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
2199fn 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
2302async 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 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
2435async 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
2453async 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
2468async 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
2483async 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
2493fn 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 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 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
2557fn 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
2575async 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 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
2619async 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 (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
2656async 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
2690async 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
2711async 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
2736async 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 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 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 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 resource_id: Option<String>,
2855 variable_resolver: Option<ExternalConstantResolver<'a>>,
2856 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
2885fn 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 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 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 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 #[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 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 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 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 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) = ¶m.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 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 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 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 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 --> (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 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 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 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}