use crate::evaluator::EvaluationContext;
use helios_fhirpath_support::{EvaluationError, EvaluationResult};
pub fn contains_function(
invocation_base: &EvaluationResult,
arg: &EvaluationResult,
_context: &EvaluationContext,
) -> Result<EvaluationResult, EvaluationError> {
if let EvaluationResult::Collection { items, .. } = invocation_base {
if matches!(arg, EvaluationResult::String(_, _, _)) {
let all_strings = items
.iter()
.all(|item| matches!(item, EvaluationResult::String(_, _, _)));
if !all_strings && !items.is_empty() {
return Err(EvaluationError::SemanticError(
"contains() with string argument requires string collection or single string"
.to_string(),
));
}
}
}
if arg == &EvaluationResult::Empty {
return Ok(EvaluationResult::Empty);
}
if invocation_base == &EvaluationResult::Empty {
if matches!(arg, EvaluationResult::String(_, _, _)) {
return Ok(EvaluationResult::Empty);
}
return Ok(EvaluationResult::boolean(false));
}
if arg.count() > 1 {
return Err(EvaluationError::SingletonEvaluationError(
"contains argument must be a singleton".to_string(),
));
}
if let EvaluationResult::String(s, _, _) = invocation_base {
if let EvaluationResult::String(substr, _, _) = arg {
return Ok(EvaluationResult::boolean(s.contains(substr)));
} else {
return Err(EvaluationError::TypeError(format!(
"contains function on String requires String argument, found {}",
arg.type_name()
)));
}
}
if let EvaluationResult::Collection { items, .. } = invocation_base {
let contains = items.iter().any(|item| simple_equality_check(item, arg));
return Ok(EvaluationResult::boolean(contains));
}
let contains = simple_equality_check(invocation_base, arg);
Ok(EvaluationResult::boolean(contains))
}
fn simple_equality_check(a: &EvaluationResult, b: &EvaluationResult) -> bool {
match (a, b) {
(EvaluationResult::Boolean(a_val, _, _), EvaluationResult::Boolean(b_val, _, _)) => {
a_val == b_val
}
(EvaluationResult::Integer(a_val, _, _), EvaluationResult::Integer(b_val, _, _)) => {
a_val == b_val
}
(EvaluationResult::Decimal(a_val, _, _), EvaluationResult::Decimal(b_val, _, _)) => {
a_val == b_val
}
(EvaluationResult::String(a_val, _, _), EvaluationResult::String(b_val, _, _)) => {
a_val == b_val
}
(
EvaluationResult::Quantity(a_val, a_unit, _, _),
EvaluationResult::Quantity(b_val, b_unit, _, _),
) => a_val == b_val && a_unit == b_unit,
(
EvaluationResult::Object { map: a_map, .. },
EvaluationResult::Object { map: b_map, .. },
) => {
if a_map.len() != b_map.len() {
return false;
}
for (key, a_value) in a_map {
if let Some(b_value) = b_map.get(key) {
if !simple_equality_check(a_value, b_value) {
return false;
}
} else {
return false;
}
}
true
}
(
EvaluationResult::Collection { items: a_items, .. },
EvaluationResult::Collection { items: b_items, .. },
) => {
if a_items.len() != b_items.len() {
return false;
}
a_items
.iter()
.zip(b_items.iter())
.all(|(a, b)| simple_equality_check(a, b))
}
(EvaluationResult::Empty, EvaluationResult::Empty) => true,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_collection(items: Vec<EvaluationResult>) -> EvaluationResult {
EvaluationResult::Collection {
items,
has_undefined_order: false,
type_info: None,
}
}
fn create_test_context() -> EvaluationContext {
EvaluationContext::new_empty_with_default_version()
}
#[test]
fn test_contains_string_substring() {
let base = EvaluationResult::string("Hello, world!".to_string());
let arg = EvaluationResult::string("world".to_string());
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_contains_string_missing_substring() {
let base = EvaluationResult::string("Hello, world!".to_string());
let arg = EvaluationResult::string("universe".to_string());
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::boolean(false));
}
#[test]
fn test_contains_string_type_error() {
let base = EvaluationResult::string("Hello, world!".to_string());
let arg = EvaluationResult::integer(42);
let context = create_test_context();
let result = contains_function(&base, &arg, &context);
assert!(result.is_err());
}
#[test]
fn test_contains_collection_with_matching_item() {
let base = create_test_collection(vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
]);
let arg = EvaluationResult::integer(2);
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_contains_collection_without_matching_item() {
let base = create_test_collection(vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
EvaluationResult::integer(3),
]);
let arg = EvaluationResult::integer(4);
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::boolean(false));
}
#[test]
fn test_contains_empty_source() {
let base = EvaluationResult::Empty;
let arg = EvaluationResult::integer(1);
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::boolean(false));
}
#[test]
fn test_contains_empty_source_string_arg() {
let base = EvaluationResult::Empty;
let arg = EvaluationResult::string("test".to_string());
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
}
#[test]
fn test_contains_empty_argument() {
let base = create_test_collection(vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
]);
let arg = EvaluationResult::Empty;
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::Empty);
}
#[test]
fn test_contains_single_item() {
let base = EvaluationResult::integer(42);
let arg = EvaluationResult::integer(42);
let context = create_test_context();
let result = contains_function(&base, &arg, &context).unwrap();
assert_eq!(result, EvaluationResult::boolean(true));
}
#[test]
fn test_contains_multi_item_argument_error() {
let base = EvaluationResult::string("Hello".to_string());
let arg = create_test_collection(vec![
EvaluationResult::string("Hello".to_string()),
EvaluationResult::string("World".to_string()),
]);
let context = create_test_context();
let result = contains_function(&base, &arg, &context);
assert!(result.is_err());
}
#[test]
fn test_simple_equality_integers() {
assert!(simple_equality_check(
&EvaluationResult::integer(42),
&EvaluationResult::integer(42)
));
assert!(!simple_equality_check(
&EvaluationResult::integer(42),
&EvaluationResult::integer(43)
));
}
#[test]
fn test_simple_equality_strings() {
assert!(simple_equality_check(
&EvaluationResult::string("test".to_string()),
&EvaluationResult::string("test".to_string())
));
assert!(!simple_equality_check(
&EvaluationResult::string("test".to_string()),
&EvaluationResult::string("different".to_string())
));
}
#[test]
fn test_simple_equality_different_types() {
assert!(!simple_equality_check(
&EvaluationResult::integer(42),
&EvaluationResult::string("42".to_string())
));
}
#[test]
fn test_contains_non_string_collection_semantic_error() {
let base = create_test_collection(vec![
EvaluationResult::integer(1),
EvaluationResult::integer(2),
]);
let arg = EvaluationResult::string("test".to_string());
let context = create_test_context();
let result = contains_function(&base, &arg, &context);
assert!(result.is_err());
if let Err(EvaluationError::SemanticError(msg)) = result {
assert!(msg.contains("string collection"));
} else {
panic!("Expected SemanticError");
}
}
}