use crate::evaluator::EvaluationContext;
use helios_fhirpath_support::{EvaluationError, EvaluationResult};
pub fn first_function(
invocation_base: &EvaluationResult,
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
if let EvaluationResult::Collection {
has_undefined_order,
..
} = invocation_base
{
if *has_undefined_order && context.check_ordered_functions {
return Err(EvaluationError::SemanticError(
"first() operation on collection with undefined order is not allowed when checkOrderedFunctions is true."
.to_string(),
));
}
}
Ok(match invocation_base {
EvaluationResult::Collection { items, .. } => {
items.first().cloned().unwrap_or(EvaluationResult::Empty)
}
_ => invocation_base.clone(), })
}
pub fn last_function(
invocation_base: &EvaluationResult,
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
if let EvaluationResult::Collection {
has_undefined_order,
..
} = invocation_base
{
if *has_undefined_order && context.check_ordered_functions {
return Err(EvaluationError::SemanticError(
"last() operation on collection with undefined order is not allowed when checkOrderedFunctions is true."
.to_string(),
));
}
}
Ok(match invocation_base {
EvaluationResult::Collection { items, .. } => {
items.last().cloned().unwrap_or(EvaluationResult::Empty)
}
_ => invocation_base.clone(), })
}
pub fn count_function(invocation_base: &EvaluationResult) -> EvaluationResult {
match invocation_base {
EvaluationResult::Collection { items, .. } => EvaluationResult::integer(items.len() as i64),
EvaluationResult::Empty => EvaluationResult::integer(0),
_ => EvaluationResult::integer(1), }
}
pub fn empty_function(invocation_base: &EvaluationResult) -> EvaluationResult {
match invocation_base {
EvaluationResult::Empty => EvaluationResult::boolean(true),
EvaluationResult::Collection { items, .. } => EvaluationResult::boolean(items.is_empty()),
_ => EvaluationResult::boolean(false), }
}
pub fn exists_function(invocation_base: &EvaluationResult) -> EvaluationResult {
match invocation_base {
EvaluationResult::Empty => EvaluationResult::boolean(false),
EvaluationResult::Collection { items, .. } => EvaluationResult::boolean(!items.is_empty()),
_ => EvaluationResult::boolean(true), }
}
pub fn all_function(invocation_base: &EvaluationResult) -> EvaluationResult {
match invocation_base {
EvaluationResult::Empty => EvaluationResult::boolean(true), EvaluationResult::Collection { items, .. } => {
EvaluationResult::boolean(items.iter().all(|item| item.to_boolean()))
}
single_item => EvaluationResult::boolean(single_item.to_boolean()), }
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_collection(
items: Vec<EvaluationResult>,
has_undefined_order: bool,
) -> EvaluationResult {
EvaluationResult::Collection {
items,
has_undefined_order,
type_info: None,
}
}
#[test]
fn test_first_empty_collection() {
let empty = EvaluationResult::Empty;
let context = EvaluationContext::new_empty_with_default_version();
let result = first_function(&empty, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
}
#[test]
fn test_first_non_empty_collection() {
let collection = create_test_collection(
vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
false,
);
let context = EvaluationContext::new_empty_with_default_version();
let result = first_function(&collection, &context).unwrap();
assert_eq!(result, EvaluationResult::integer(1));
}
#[test]
fn test_first_single_item() {
let single = EvaluationResult::string("test".to_string());
let context = EvaluationContext::new_empty_with_default_version();
let result = first_function(&single, &context).unwrap();
assert_eq!(result, EvaluationResult::string("test".to_string()));
}
#[test]
fn test_first_undefined_order() {
let collection = create_test_collection(
vec![EvaluationResult::integer(1), EvaluationResult::integer(2)],
true, );
let mut context = EvaluationContext::new_empty_with_default_version();
context.check_ordered_functions = false;
let result = first_function(&collection, &context);
assert!(result.is_ok());
assert_eq!(result.unwrap(), EvaluationResult::integer(1));
context.check_ordered_functions = true;
let result = first_function(&collection, &context);
assert!(result.is_err());
}
#[test]
fn test_last_empty_collection() {
let empty = EvaluationResult::Empty;
let context = EvaluationContext::new_empty_with_default_version();
let result = last_function(&empty, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
}
#[test]
fn test_last_non_empty_collection() {
let collection = create_test_collection(
vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
false,
);
let context = EvaluationContext::new_empty_with_default_version();
let result = last_function(&collection, &context).unwrap();
assert_eq!(result, EvaluationResult::integer(3));
}
#[test]
fn test_last_single_item() {
let single = EvaluationResult::string("test".to_string());
let context = EvaluationContext::new_empty_with_default_version();
let result = last_function(&single, &context).unwrap();
assert_eq!(result, EvaluationResult::string("test".to_string()));
}
#[test]
fn test_last_undefined_order() {
let collection = create_test_collection(
vec![EvaluationResult::integer(1), EvaluationResult::integer(2)],
true, );
let mut context = EvaluationContext::new_empty_with_default_version();
context.check_ordered_functions = false;
let result = last_function(&collection, &context);
assert!(result.is_ok());
assert_eq!(result.unwrap(), EvaluationResult::integer(2));
context.check_ordered_functions = true;
let result = last_function(&collection, &context);
assert!(result.is_err());
}
#[test]
fn test_count_empty_collection() {
let empty = EvaluationResult::Empty;
let result = count_function(&empty);
assert_eq!(result, EvaluationResult::integer(0));
}
#[test]
fn test_count_non_empty_collection() {
let collection = create_test_collection(
vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
],
false,
);
let result = count_function(&collection);
assert_eq!(result, EvaluationResult::integer(3));
}
#[test]
fn test_count_single_item() {
let single = EvaluationResult::string("test".to_string());
let result = count_function(&single);
assert_eq!(result, EvaluationResult::integer(1));
}
#[test]
fn test_empty_on_empty_collection() {
let empty = EvaluationResult::Empty;
let result = empty_function(&empty);
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_empty_on_non_empty_collection() {
let collection = create_test_collection(vec![EvaluationResult::integer(1)], false);
let result = empty_function(&collection);
assert_eq!(result, EvaluationResult::boolean(false));
}
#[test]
fn test_empty_on_single_item() {
let single = EvaluationResult::string("test".to_string());
let result = empty_function(&single);
assert_eq!(result, EvaluationResult::boolean(false));
}
#[test]
fn test_exists_on_empty_collection() {
let empty = EvaluationResult::Empty;
let result = exists_function(&empty);
assert_eq!(result, EvaluationResult::boolean(false));
}
#[test]
fn test_exists_on_non_empty_collection() {
let collection = create_test_collection(vec![EvaluationResult::integer(1)], false);
let result = exists_function(&collection);
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_exists_on_single_item() {
let single = EvaluationResult::string("test".to_string());
let result = exists_function(&single);
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_all_on_empty_collection() {
let empty = EvaluationResult::Empty;
let result = all_function(&empty);
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_all_on_all_true_collection() {
let collection = create_test_collection(
vec![
EvaluationResult::boolean(true),
EvaluationResult::boolean(true),
],
false,
);
let result = all_function(&collection);
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_all_on_mixed_collection() {
let collection = create_test_collection(
vec![
EvaluationResult::boolean(true),
EvaluationResult::boolean(false),
],
false,
);
let result = all_function(&collection);
assert_eq!(result, EvaluationResult::boolean(false));
}
#[test]
fn test_all_on_single_true() {
let single = EvaluationResult::boolean(true);
let result = all_function(&single);
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_all_on_single_false() {
let single = EvaluationResult::boolean(false);
let result = all_function(&single);
assert_eq!(result, EvaluationResult::boolean(false));
}
}
pub fn sort_function(
invocation_base: &EvaluationResult,
args: &[crate::parser::Expression],
context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
let items = match invocation_base {
EvaluationResult::Empty => return Ok(EvaluationResult::Empty),
EvaluationResult::Collection { items, .. } => items.clone(),
single => vec![single.clone()],
};
if items.is_empty() {
return Ok(EvaluationResult::Empty);
}
if args.is_empty() {
let mut sorted_items = items;
sorted_items.sort_by(compare_evaluation_results);
return Ok(if sorted_items.len() == 1 {
sorted_items.into_iter().next().unwrap()
} else {
EvaluationResult::Collection {
items: sorted_items,
has_undefined_order: false,
type_info: None,
}
});
}
let mut sort_keys: Vec<(bool, crate::parser::Expression)> = Vec::new();
for arg in args {
let (is_descending, sort_expr) =
if let crate::parser::Expression::Polarity('-', inner) = arg {
(true, inner.as_ref().clone())
} else {
(false, arg.clone())
};
sort_keys.push((is_descending, sort_expr));
}
let mut items_with_keys: Vec<(Vec<(bool, EvaluationResult)>, EvaluationResult)> = Vec::new();
for item in &items {
let mut keys = Vec::new();
for (is_descending, sort_expr) in &sort_keys {
let mut sort_context = context.clone();
sort_context.this = Some(item.clone());
let sort_key = crate::evaluator::evaluate(sort_expr, &sort_context, Some(item))?;
keys.push((*is_descending, sort_key));
}
items_with_keys.push((keys, item.clone()));
}
items_with_keys.sort_by(|a, b| {
for (key_a, key_b) in a.0.iter().zip(b.0.iter()) {
let is_descending = key_a.0;
let ord = match (&key_a.1, &key_b.1) {
(EvaluationResult::Empty, EvaluationResult::Empty) => std::cmp::Ordering::Equal,
(EvaluationResult::Empty, _) => std::cmp::Ordering::Less, (_, EvaluationResult::Empty) => std::cmp::Ordering::Greater, _ => {
let ord = compare_evaluation_results(&key_a.1, &key_b.1);
if is_descending { ord.reverse() } else { ord }
}
};
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
std::cmp::Ordering::Equal
});
let sorted_items: Vec<EvaluationResult> =
items_with_keys.into_iter().map(|(_, item)| item).collect();
Ok(if sorted_items.len() == 1 {
sorted_items.into_iter().next().unwrap()
} else {
EvaluationResult::Collection {
items: sorted_items,
has_undefined_order: false,
type_info: None,
}
})
}
fn compare_evaluation_results(a: &EvaluationResult, b: &EvaluationResult) -> std::cmp::Ordering {
use rust_decimal::Decimal;
use std::cmp::Ordering;
match (a, b) {
(EvaluationResult::Empty, EvaluationResult::Empty) => Ordering::Equal,
(EvaluationResult::Empty, _) => Ordering::Less,
(_, EvaluationResult::Empty) => Ordering::Greater,
(EvaluationResult::Boolean(a, _, _), EvaluationResult::Boolean(b, _, _)) => a.cmp(b),
(EvaluationResult::Integer(a, _, _), EvaluationResult::Integer(b, _, _)) => a.cmp(b),
(EvaluationResult::Integer64(a, _, _), EvaluationResult::Integer64(b, _, _)) => a.cmp(b),
(EvaluationResult::Decimal(a, _, _), EvaluationResult::Decimal(b, _, _)) => {
if a < b {
Ordering::Less
} else if a > b {
Ordering::Greater
} else {
Ordering::Equal
}
}
(EvaluationResult::Integer(a, _, _), EvaluationResult::Decimal(b, _, _)) => {
let a_dec = Decimal::from(*a);
if a_dec < *b {
Ordering::Less
} else if a_dec > *b {
Ordering::Greater
} else {
Ordering::Equal
}
}
(EvaluationResult::Decimal(a, _, _), EvaluationResult::Integer(b, _, _)) => {
let b_dec = Decimal::from(*b);
if a < &b_dec {
Ordering::Less
} else if a > &b_dec {
Ordering::Greater
} else {
Ordering::Equal
}
}
(EvaluationResult::Integer(a, _, _), EvaluationResult::Integer64(b, _, _)) => a.cmp(b),
(EvaluationResult::Integer64(a, _, _), EvaluationResult::Integer(b, _, _)) => a.cmp(b),
(EvaluationResult::String(a, _, _), EvaluationResult::String(b, _, _)) => a.cmp(b),
(EvaluationResult::Date(a, _, _), EvaluationResult::Date(b, _, _)) => a.cmp(b),
(EvaluationResult::DateTime(a, _, _), EvaluationResult::DateTime(b, _, _)) => a.cmp(b),
(EvaluationResult::Time(a, _, _), EvaluationResult::Time(b, _, _)) => a.cmp(b),
(
EvaluationResult::Quantity(val_a, unit_a, _, _),
EvaluationResult::Quantity(val_b, unit_b, _, _),
) => {
if unit_a == unit_b {
if val_a < val_b {
Ordering::Less
} else if val_a > val_b {
Ordering::Greater
} else {
Ordering::Equal
}
} else {
match unit_a.cmp(unit_b) {
Ordering::Equal => {
if val_a < val_b {
Ordering::Less
} else if val_a > val_b {
Ordering::Greater
} else {
Ordering::Equal
}
}
other => other,
}
}
}
_ => {
let type_order = |v: &EvaluationResult| match v {
EvaluationResult::Empty => 0,
EvaluationResult::Boolean(_, _, _) => 1,
EvaluationResult::Integer(_, _, _) => 2,
EvaluationResult::Integer64(_, _, _) => 3,
EvaluationResult::Decimal(_, _, _) => 4,
EvaluationResult::String(_, _, _) => 5,
EvaluationResult::Date(_, _, _) => 6,
EvaluationResult::DateTime(_, _, _) => 7,
EvaluationResult::Time(_, _, _) => 8,
EvaluationResult::Quantity(_, _, _, _) => 9,
EvaluationResult::Collection { .. } => 10,
EvaluationResult::Object { .. } => 11,
};
type_order(a).cmp(&type_order(b))
}
}
}