helios_fhirpath_support/lib.rs
1//! # FHIRPath Support Types
2//!
3//! This crate provides the foundational types and traits that serve as a bridge between
4//! the FHIRPath evaluator and the broader FHIR ecosystem. It defines the common data
5//! structures and conversion interfaces that enable seamless integration across all
6//! components of the FHIRPath implementation.
7//!
8//! ## Overview
9//!
10//! The fhirpath_support crate acts as the universal communication layer that allows:
11//! - FHIRPath evaluator to work with unified result types
12//! - FHIR data structures to convert into FHIRPath-compatible formats
13//! - Code generation macros to produce FHIRPath-aware implementations
14//! - Type conversion system to handle data transformation
15//!
16//! ## Core Types
17//!
18//! - [`EvaluationResult`] - Universal result type for FHIRPath expression evaluation
19//! - [`EvaluationError`] - Comprehensive error handling for evaluation failures
20//! - [`IntoEvaluationResult`] - Trait for converting types to evaluation results
21//!
22//! ## Usage Example
23//!
24//! ```rust
25//! use helios_fhirpath_support::{EvaluationResult, IntoEvaluationResult};
26//!
27//! // Convert a string to an evaluation result
28//! let text = "Hello, FHIR!".to_string();
29//! let result = text.to_evaluation_result();
30//! assert_eq!(result, EvaluationResult::String("Hello, FHIR!".to_string(), None));
31//!
32//! // Work with collections
33//! let numbers = vec![1, 2, 3];
34//! let collection = numbers.to_evaluation_result();
35//! assert_eq!(collection.count(), 3);
36//! ```
37
38use rust_decimal::Decimal;
39use rust_decimal::prelude::*;
40use std::cmp::Ordering;
41use std::collections::HashMap;
42use std::hash::{Hash, Hasher};
43
44mod type_info;
45pub use type_info::{TypeInfo, TypeInfoResult};
46
47/// Trait for FHIR choice element types.
48///
49/// This trait is implemented by generated enum types that represent FHIR choice elements
50/// (fields with [x] in the FHIR specification). It provides metadata about the choice
51/// element that enables proper polymorphic access in FHIRPath expressions.
52///
53/// # Example
54///
55/// For a FHIR field like `Observation.value[x]`, the generated enum would implement:
56/// ```rust,ignore
57/// impl ChoiceElement for ObservationValue {
58/// fn base_name() -> &'static str {
59/// "value"
60/// }
61///
62/// fn possible_field_names() -> Vec<&'static str> {
63/// vec!["valueQuantity", "valueCodeableConcept", "valueString", ...]
64/// }
65/// }
66/// ```
67pub trait ChoiceElement {
68 /// Returns the base name of the choice element without the [x] suffix.
69 ///
70 /// For example, for `value[x]`, this returns "value".
71 fn base_name() -> &'static str;
72
73 /// Returns all possible field names that this choice element can manifest as.
74 ///
75 /// For example, for `value[x]`, this might return:
76 /// ["valueQuantity", "valueCodeableConcept", "valueString", ...]
77 fn possible_field_names() -> Vec<&'static str>;
78}
79
80/// Trait for FHIR resource metadata.
81///
82/// This trait is implemented by generated FHIR resource structs to provide
83/// metadata about the resource's structure, particularly which fields are
84/// choice elements. This enables accurate polymorphic field access in FHIRPath.
85///
86/// # Example
87///
88/// ```rust,ignore
89/// impl FhirResourceMetadata for Observation {
90/// fn choice_elements() -> &'static [&'static str] {
91/// &["value", "effective", "component.value"]
92/// }
93/// }
94/// ```
95pub trait FhirResourceMetadata {
96 /// Returns the names of all choice element fields in this resource.
97 ///
98 /// The returned slice contains the base names (without [x]) of fields
99 /// that are choice elements in the FHIR specification.
100 fn choice_elements() -> &'static [&'static str];
101}
102
103/// Universal conversion trait for transforming values into FHIRPath evaluation results.
104///
105/// This trait provides the bridge between FHIR data types and the FHIRPath evaluation
106/// system. It allows any type to be converted into an `EvaluationResult` that can be
107/// processed by FHIRPath expressions.
108///
109/// # Implementation Guidelines
110///
111/// When implementing this trait:
112/// - Return `EvaluationResult::Empty` for `None` or missing values
113/// - Use appropriate variant types (Boolean, String, Integer, etc.)
114/// - For complex types, use `EvaluationResult::Object` with field mappings
115/// - For arrays/collections, use `EvaluationResult::Collection`
116///
117/// # Examples
118///
119/// ```rust
120/// use helios_fhirpath_support::{EvaluationResult, IntoEvaluationResult};
121///
122/// struct CustomType {
123/// value: String,
124/// active: bool,
125/// }
126///
127/// impl IntoEvaluationResult for CustomType {
128/// fn to_evaluation_result(&self) -> EvaluationResult {
129/// let mut map = std::collections::HashMap::new();
130/// map.insert("value".to_string(), self.value.to_evaluation_result());
131/// map.insert("active".to_string(), self.active.to_evaluation_result());
132/// EvaluationResult::Object { map, type_info: None }
133/// }
134/// }
135/// ```
136pub trait IntoEvaluationResult {
137 /// Converts this value into a FHIRPath evaluation result.
138 ///
139 /// This method should transform the implementing type into the most appropriate
140 /// `EvaluationResult` variant that represents the value's semantics in FHIRPath.
141 fn to_evaluation_result(&self) -> EvaluationResult;
142}
143
144/// Universal result type for FHIRPath expression evaluation.
145///
146/// This enum represents any value that can result from evaluating a FHIRPath expression
147/// against FHIR data. It provides a unified type system that bridges FHIR's data model
148/// with FHIRPath's evaluation semantics.
149///
150/// # Variants
151///
152/// - **`Empty`**: Represents no value or null (equivalent to FHIRPath's empty collection)
153/// - **`Boolean`**: True/false values from boolean expressions
154/// - **`String`**: Text values from FHIR strings, codes, URIs, etc.
155/// - **`Decimal`**: High-precision decimal numbers for accurate numeric computation
156/// - **`Integer`**: Whole numbers for counting and indexing operations
157/// - **`Integer64`**: Explicit 64-bit integers for special cases
158/// - **`Date`**: Date values in ISO format (YYYY-MM-DD)
159/// - **`DateTime`**: DateTime values in ISO format with optional timezone
160/// - **`Time`**: Time values in ISO format (HH:MM:SS)
161/// - **`Quantity`**: Value with unit (e.g., "5.4 mg", "10 years")
162/// - **`Collection`**: Ordered collections of evaluation results
163/// - **`Object`**: Key-value structures representing complex FHIR types
164///
165/// # Type Safety
166///
167/// The enum is designed to prevent type errors at runtime by encoding FHIRPath's
168/// type system at the Rust type level. Operations that require specific types
169/// can pattern match on the appropriate variants.
170///
171/// # Examples
172///
173/// ```rust
174/// use helios_fhirpath_support::EvaluationResult;
175/// use rust_decimal::Decimal;
176///
177/// // Creating different result types
178/// let empty = EvaluationResult::Empty;
179/// let text = EvaluationResult::String("Patient".to_string(), None);
180/// let number = EvaluationResult::Integer(42, None);
181/// let number64 = EvaluationResult::Integer64(9223372036854775807, None); // max i64
182/// let decimal = EvaluationResult::Decimal(Decimal::new(1234, 2), None); // 12.34
183///
184/// // Working with collections
185/// let items = vec![text, number];
186/// let collection = EvaluationResult::Collection {
187/// items,
188/// has_undefined_order: false,
189/// type_info: None
190/// };
191///
192/// assert_eq!(collection.count(), 2);
193/// assert!(collection.is_collection());
194/// ```
195#[derive(Debug, Clone)]
196pub enum EvaluationResult {
197 /// No value or empty collection.
198 ///
199 /// Represents the absence of a value, equivalent to FHIRPath's empty collection `{}`.
200 /// This is the result when accessing non-existent properties or when filters
201 /// match no elements.
202 Empty,
203 /// Boolean true/false value.
204 ///
205 /// Results from boolean expressions, existence checks, and logical operations.
206 /// Also used for FHIR boolean fields.
207 Boolean(bool, Option<TypeInfoResult>),
208 /// Text string value.
209 ///
210 /// Used for FHIR string, code, uri, canonical, id, and other text-based types.
211 /// Also results from string manipulation functions and conversions.
212 String(String, Option<TypeInfoResult>),
213 /// High-precision decimal number.
214 ///
215 /// Uses `rust_decimal::Decimal` for precise arithmetic without floating-point
216 /// errors. Required for FHIR's decimal type and mathematical operations.
217 Decimal(Decimal, Option<TypeInfoResult>),
218 /// Whole number value.
219 ///
220 /// Used for FHIR integer, positiveInt, unsignedInt types and counting operations.
221 /// Also results from indexing and length functions.
222 Integer(i64, Option<TypeInfoResult>),
223 /// 64-bit integer value.
224 ///
225 /// Explicit 64-bit integer type for cases where the distinction from regular
226 /// integers is important.
227 Integer64(i64, Option<TypeInfoResult>),
228 /// Date value in ISO format.
229 ///
230 /// Stores date as string in YYYY-MM-DD format. Handles FHIR date fields
231 /// and results from date extraction functions.
232 Date(String, Option<TypeInfoResult>),
233 /// DateTime value in ISO format.
234 ///
235 /// Stores datetime as string in ISO 8601 format with optional timezone.
236 /// Handles FHIR dateTime and instant fields.
237 DateTime(String, Option<TypeInfoResult>),
238 /// Time value in ISO format.
239 ///
240 /// Stores time as string in HH:MM:SS format. Handles FHIR time fields
241 /// and results from time extraction functions.
242 Time(String, Option<TypeInfoResult>),
243 /// Quantity with value and unit.
244 ///
245 /// Represents measurements with units (e.g., "5.4 mg", "10 years").
246 /// First element is the numeric value, second is the unit string.
247 /// Used for FHIR Quantity, Age, Duration, Distance, Count, and Money types.
248 Quantity(Decimal, String, Option<TypeInfoResult>),
249 /// Ordered collection of evaluation results.
250 ///
251 /// Represents arrays, lists, and multi-valued FHIR elements. Collections
252 /// maintain order for FHIRPath operations like indexing and iteration.
253 ///
254 /// # Fields
255 ///
256 /// - `items`: The ordered list of contained evaluation results
257 /// - `has_undefined_order`: Flag indicating if the original source order
258 /// was undefined (affects certain FHIRPath operations)
259 Collection {
260 /// The ordered items in this collection
261 items: Vec<EvaluationResult>,
262 /// Whether the original source order was undefined
263 has_undefined_order: bool,
264 /// Optional type information
265 type_info: Option<TypeInfoResult>,
266 },
267 /// Key-value object representing complex FHIR types.
268 ///
269 /// Used for FHIR resources, data types, and backbone elements. Keys are
270 /// field names and values are the corresponding evaluation results.
271 /// Enables property access via FHIRPath dot notation.
272 ///
273 /// The optional type_namespace and type_name fields preserve type information
274 /// for the FHIRPath type() function.
275 Object {
276 /// The object's properties
277 map: HashMap<String, EvaluationResult>,
278 /// Optional type information
279 type_info: Option<TypeInfoResult>,
280 },
281}
282
283/// Comprehensive error type for FHIRPath evaluation failures.
284///
285/// This enum covers all categories of errors that can occur during FHIRPath expression
286/// evaluation, from type mismatches to semantic violations. Each variant provides
287/// specific context about the failure to aid in debugging and error reporting.
288///
289/// # Error Categories
290///
291/// - **Type Errors**: Mismatched types in operations or function calls
292/// - **Argument Errors**: Invalid arguments passed to functions
293/// - **Runtime Errors**: Errors during expression evaluation (division by zero, etc.)
294/// - **Semantic Errors**: Violations of FHIRPath semantic rules
295/// - **System Errors**: Internal errors and edge cases
296///
297/// # Error Handling
298///
299/// All variants implement `std::error::Error` and `Display` for standard Rust
300/// error handling patterns. The error messages are designed to be user-friendly
301/// and provide actionable information for debugging.
302///
303/// # Examples
304///
305/// ```rust
306/// use helios_fhirpath_support::EvaluationError;
307///
308/// // Type error example
309/// let error = EvaluationError::TypeError(
310/// "Cannot add String and Integer".to_string()
311/// );
312///
313/// // Display the error
314/// println!("{}", error); // "Type Error: Cannot add String and Integer"
315/// ```
316#[derive(Debug, Clone, PartialEq, Eq)]
317pub enum EvaluationError {
318 /// Type mismatch or incompatible type operation.
319 ///
320 /// Occurs when operations are attempted on incompatible types or when
321 /// functions receive arguments of unexpected types.
322 ///
323 /// Example: "Expected Boolean, found Integer"
324 TypeError(String),
325 /// Invalid argument provided to a function.
326 ///
327 /// Occurs when function arguments don't meet the required constraints
328 /// or format expectations.
329 ///
330 /// Example: "Invalid argument for function 'where'"
331 InvalidArgument(String),
332 /// Reference to an undefined variable.
333 ///
334 /// Occurs when expressions reference variables that haven't been defined
335 /// in the current evaluation context.
336 ///
337 /// Example: "Variable '%undefinedVar' not found"
338 UndefinedVariable(String),
339 /// Invalid operation for the given operand types.
340 ///
341 /// Occurs when operators are used with incompatible operand types or
342 /// when operations are not supported for the given types.
343 ///
344 /// Example: "Cannot add String and Integer"
345 InvalidOperation(String),
346 /// Incorrect number of arguments provided to a function.
347 ///
348 /// Occurs when functions are called with too many or too few arguments
349 /// compared to their specification.
350 ///
351 /// Example: "Function 'substring' expects 1 or 2 arguments, got 3"
352 InvalidArity(String),
353 /// Invalid array or collection index.
354 ///
355 /// Occurs when collection indexing operations use invalid indices
356 /// (negative numbers, non-integers, out of bounds).
357 ///
358 /// Example: "Index must be a non-negative integer"
359 InvalidIndex(String),
360 /// Attempted division by zero.
361 ///
362 /// Occurs during mathematical operations when the divisor is zero.
363 /// This is a specific case of arithmetic error with clear semantics.
364 DivisionByZero,
365 /// Arithmetic operation resulted in overflow.
366 ///
367 /// Occurs when mathematical operations produce results that exceed
368 /// the representable range of the target numeric type.
369 ArithmeticOverflow,
370 /// Invalid regular expression pattern.
371 ///
372 /// Occurs when regex-based functions receive malformed regex patterns
373 /// that cannot be compiled.
374 ///
375 /// Example: "Invalid regex pattern: unclosed parenthesis"
376 InvalidRegex(String),
377 /// Invalid type specifier in type operations.
378 ///
379 /// Occurs when type checking operations (is, as, ofType) receive
380 /// invalid or unrecognized type specifiers.
381 ///
382 /// Example: "Unknown type 'InvalidType'"
383 InvalidTypeSpecifier(String),
384 /// Collection cardinality error for singleton operations.
385 ///
386 /// Occurs when operations expecting a single value receive collections
387 /// with zero or multiple items.
388 ///
389 /// Example: "Expected singleton, found collection with 3 items"
390 SingletonEvaluationError(String),
391 /// Semantic rule violation.
392 ///
393 /// Occurs when expressions violate FHIRPath semantic rules, such as
394 /// accessing non-existent properties in strict mode or violating
395 /// contextual constraints.
396 ///
397 /// Example: "Property 'invalidField' does not exist on type 'Patient'"
398 SemanticError(String),
399 /// Unsupported function called.
400 ///
401 /// Occurs when a FHIRPath function is recognized but not yet implemented
402 /// in this evaluation engine.
403 ///
404 /// Example: "Function 'conformsTo' is not implemented"
405 UnsupportedFunction(String),
406 /// Generic error for cases not covered by specific variants.
407 ///
408 /// Used for internal errors, edge cases, or temporary error conditions
409 /// that don't fit into the specific error categories.
410 ///
411 /// Example: "Internal evaluation error"
412 Other(String),
413}
414
415// === Standard Error Trait Implementations ===
416
417/// Implements the standard `Error` trait for `EvaluationError`.
418///
419/// This allows `EvaluationError` to be used with Rust's standard error handling
420/// mechanisms, including `?` operator, `Result` combinators, and error chains.
421impl std::error::Error for EvaluationError {}
422
423/// Implements the `Display` trait for user-friendly error messages.
424///
425/// Provides formatted, human-readable error messages that include error category
426/// prefixes for easy identification of error types.
427impl std::fmt::Display for EvaluationError {
428 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429 match self {
430 EvaluationError::TypeError(msg) => write!(f, "Type Error: {}", msg),
431 EvaluationError::InvalidArgument(msg) => write!(f, "Invalid Argument: {}", msg),
432 EvaluationError::UndefinedVariable(name) => write!(f, "Undefined Variable: {}", name),
433 EvaluationError::InvalidOperation(msg) => write!(f, "Invalid Operation: {}", msg),
434 EvaluationError::InvalidArity(msg) => write!(f, "Invalid Arity: {}", msg),
435 EvaluationError::InvalidIndex(msg) => write!(f, "Invalid Index: {}", msg),
436 EvaluationError::DivisionByZero => write!(f, "Division by zero"),
437 EvaluationError::ArithmeticOverflow => write!(f, "Arithmetic overflow"),
438 EvaluationError::InvalidRegex(msg) => write!(f, "Invalid Regex: {}", msg),
439 EvaluationError::InvalidTypeSpecifier(msg) => {
440 write!(f, "Invalid Type Specifier: {}", msg)
441 }
442 EvaluationError::SingletonEvaluationError(msg) => {
443 write!(f, "Singleton Evaluation Error: {}", msg)
444 }
445 EvaluationError::SemanticError(msg) => write!(f, "Semantic Error: {}", msg),
446 EvaluationError::UnsupportedFunction(msg) => write!(f, "Unsupported Function: {}", msg),
447 EvaluationError::Other(msg) => write!(f, "Evaluation Error: {}", msg),
448 }
449 }
450}
451
452// === EvaluationResult Trait Implementations ===
453
454/// Implements equality comparison for `EvaluationResult`.
455///
456/// This implementation follows FHIRPath equality semantics:
457/// - Decimal values are normalized before comparison for precision consistency
458/// - Collections compare both items and order flags
459/// - Objects use HashMap equality (order-independent)
460/// - Cross-variant comparisons always return `false`
461///
462/// # Examples
463///
464/// ```rust
465/// use helios_fhirpath_support::EvaluationResult;
466/// use rust_decimal::Decimal;
467///
468/// let a = EvaluationResult::String("test".to_string(), None);
469/// let b = EvaluationResult::String("test".to_string(), None);
470/// assert_eq!(a, b);
471///
472/// let c = EvaluationResult::Decimal(Decimal::new(100, 2), None); // 1.00
473/// let d = EvaluationResult::Decimal(Decimal::new(1, 0), None); // 1
474/// assert_eq!(c, d); // Normalized decimals are equal
475/// ```
476impl PartialEq for EvaluationResult {
477 fn eq(&self, other: &Self) -> bool {
478 match (self, other) {
479 (EvaluationResult::Empty, EvaluationResult::Empty) => true,
480 (EvaluationResult::Boolean(a, _), EvaluationResult::Boolean(b, _)) => a == b,
481 (EvaluationResult::String(a, _), EvaluationResult::String(b, _)) => a == b,
482 (EvaluationResult::Decimal(a, _), EvaluationResult::Decimal(b, _)) => {
483 // Normalize decimals to handle precision differences (e.g., 1.0 == 1.00)
484 a.normalize() == b.normalize()
485 }
486 (EvaluationResult::Integer(a, _), EvaluationResult::Integer(b, _)) => a == b,
487 (EvaluationResult::Integer64(a, _), EvaluationResult::Integer64(b, _)) => a == b,
488 (EvaluationResult::Date(a, _), EvaluationResult::Date(b, _)) => a == b,
489 (EvaluationResult::DateTime(a, _), EvaluationResult::DateTime(b, _)) => a == b,
490 (EvaluationResult::Time(a, _), EvaluationResult::Time(b, _)) => a == b,
491 (
492 EvaluationResult::Quantity(val_a, unit_a, _),
493 EvaluationResult::Quantity(val_b, unit_b, _),
494 ) => {
495 // Quantities are equal if both value and unit match (normalized values)
496 val_a.normalize() == val_b.normalize() && unit_a == unit_b
497 }
498 (
499 EvaluationResult::Collection {
500 items: a_items,
501 has_undefined_order: a_undef,
502 ..
503 },
504 EvaluationResult::Collection {
505 items: b_items,
506 has_undefined_order: b_undef,
507 ..
508 },
509 ) => {
510 // Collections are equal if both order flags and items match
511 a_undef == b_undef && a_items == b_items
512 }
513 (EvaluationResult::Object { map: a, .. }, EvaluationResult::Object { map: b, .. }) => {
514 a == b
515 }
516 _ => false,
517 }
518 }
519}
520/// Marker trait implementation indicating that `EvaluationResult` has total equality.
521///
522/// Since we implement `PartialEq` with total equality semantics (no NaN-like values),
523/// we can safely implement `Eq`.
524impl Eq for EvaluationResult {}
525
526/// Implements partial ordering for `EvaluationResult`.
527///
528/// This provides a consistent ordering for sorting operations, but note that this
529/// ordering is primarily for internal use (e.g., in collections) and may not
530/// reflect FHIRPath's comparison semantics, which are handled separately.
531impl PartialOrd for EvaluationResult {
532 /// Compares two evaluation results for partial ordering.
533 ///
534 /// Since we implement total ordering, this always returns `Some`.
535 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
536 Some(self.cmp(other))
537 }
538}
539
540/// Implements total ordering for `EvaluationResult`.
541///
542/// This provides a deterministic ordering for all evaluation results, enabling
543/// their use in sorted collections. The ordering is defined by:
544/// 1. Variant precedence (Empty < Boolean < Integer < ... < Object)
545/// 2. Value comparison within the same variant
546///
547/// Note: This is an arbitrary but consistent ordering for internal use.
548/// FHIRPath comparison operators use different semantics.
549impl Ord for EvaluationResult {
550 /// Compares two evaluation results for total ordering.
551 ///
552 /// Returns the ordering relationship between `self` and `other`.
553 fn cmp(&self, other: &Self) -> Ordering {
554 match (self, other) {
555 // Order variants by type precedence
556 (EvaluationResult::Empty, EvaluationResult::Empty) => Ordering::Equal,
557 (EvaluationResult::Empty, _) => Ordering::Less,
558 (_, EvaluationResult::Empty) => Ordering::Greater,
559
560 (EvaluationResult::Boolean(a, _), EvaluationResult::Boolean(b, _)) => a.cmp(b),
561 (EvaluationResult::Boolean(_, _), _) => Ordering::Less,
562 (_, EvaluationResult::Boolean(_, _)) => Ordering::Greater,
563
564 (EvaluationResult::Integer(a, _), EvaluationResult::Integer(b, _)) => a.cmp(b),
565 (EvaluationResult::Integer(_, _), _) => Ordering::Less,
566 (_, EvaluationResult::Integer(_, _)) => Ordering::Greater,
567
568 (EvaluationResult::Integer64(a, _), EvaluationResult::Integer64(b, _)) => a.cmp(b),
569 (EvaluationResult::Integer64(_, _), _) => Ordering::Less,
570 (_, EvaluationResult::Integer64(_, _)) => Ordering::Greater,
571
572 (EvaluationResult::Decimal(a, _), EvaluationResult::Decimal(b, _)) => a.cmp(b),
573 (EvaluationResult::Decimal(_, _), _) => Ordering::Less,
574 (_, EvaluationResult::Decimal(_, _)) => Ordering::Greater,
575
576 (EvaluationResult::String(a, _), EvaluationResult::String(b, _)) => a.cmp(b),
577 (EvaluationResult::String(_, _), _) => Ordering::Less,
578 (_, EvaluationResult::String(_, _)) => Ordering::Greater,
579
580 (EvaluationResult::Date(a, _), EvaluationResult::Date(b, _)) => a.cmp(b),
581 (EvaluationResult::Date(_, _), _) => Ordering::Less,
582 (_, EvaluationResult::Date(_, _)) => Ordering::Greater,
583
584 (EvaluationResult::DateTime(a, _), EvaluationResult::DateTime(b, _)) => a.cmp(b),
585 (EvaluationResult::DateTime(_, _), _) => Ordering::Less,
586 (_, EvaluationResult::DateTime(_, _)) => Ordering::Greater,
587
588 (EvaluationResult::Time(a, _), EvaluationResult::Time(b, _)) => a.cmp(b),
589 (EvaluationResult::Time(_, _), _) => Ordering::Less,
590 (_, EvaluationResult::Time(_, _)) => Ordering::Greater,
591
592 (
593 EvaluationResult::Quantity(val_a, unit_a, _),
594 EvaluationResult::Quantity(val_b, unit_b, _),
595 ) => {
596 // Order by value first, then by unit string
597 match val_a.cmp(val_b) {
598 Ordering::Equal => unit_a.cmp(unit_b),
599 other => other,
600 }
601 }
602 (EvaluationResult::Quantity(_, _, _), _) => Ordering::Less,
603 (_, EvaluationResult::Quantity(_, _, _)) => Ordering::Greater,
604
605 (
606 EvaluationResult::Collection {
607 items: a_items,
608 has_undefined_order: a_undef,
609 ..
610 },
611 EvaluationResult::Collection {
612 items: b_items,
613 has_undefined_order: b_undef,
614 ..
615 },
616 ) => {
617 // Order by undefined_order flag first (false < true), then by items
618 match a_undef.cmp(b_undef) {
619 Ordering::Equal => {
620 // Compare items as ordered lists (FHIRPath collections maintain order)
621 a_items.cmp(b_items)
622 }
623 other => other,
624 }
625 }
626 (EvaluationResult::Collection { .. }, _) => Ordering::Less,
627 (_, EvaluationResult::Collection { .. }) => Ordering::Greater,
628
629 (EvaluationResult::Object { map: a, .. }, EvaluationResult::Object { map: b, .. }) => {
630 // Compare objects by sorted keys, then by values
631 let mut a_keys: Vec<_> = a.keys().collect();
632 let mut b_keys: Vec<_> = b.keys().collect();
633 a_keys.sort();
634 b_keys.sort();
635
636 match a_keys.cmp(&b_keys) {
637 Ordering::Equal => {
638 // Same keys: compare values in sorted key order
639 for key in a_keys {
640 match a[key].cmp(&b[key]) {
641 Ordering::Equal => continue,
642 non_equal => return non_equal,
643 }
644 }
645 Ordering::Equal
646 }
647 non_equal => non_equal,
648 }
649 } // Note: Object is the last variant, so no additional arms needed
650 }
651 }
652}
653/// Implements hashing for `EvaluationResult`.
654///
655/// This implementation enables use of `EvaluationResult` in hash-based collections
656/// like `HashSet` and `HashMap`. The hash implementation is consistent with equality:
657/// values that are equal will have the same hash.
658///
659/// # Hash Stability
660///
661/// - Decimal values are normalized before hashing for consistency
662/// - Collections hash both the items and the order flag
663/// - Objects hash keys in sorted order for deterministic results
664/// - All variants include a discriminant hash to avoid collisions
665///
666/// # Use Cases
667///
668/// This implementation enables FHIRPath operations like:
669/// - `distinct()` function using `HashSet` for deduplication
670/// - `intersect()` and `union()` set operations
671/// - Efficient lookups in evaluation contexts
672impl Hash for EvaluationResult {
673 /// Computes the hash of this evaluation result.
674 ///
675 /// The hash implementation ensures that equal values produce equal hashes
676 /// and provides good distribution for hash-based collections.
677 fn hash<H: Hasher>(&self, state: &mut H) {
678 // Hash the enum variant first to avoid cross-variant collisions
679 core::mem::discriminant(self).hash(state);
680 match self {
681 // Empty has no additional data to hash
682 EvaluationResult::Empty => {}
683 EvaluationResult::Boolean(b, _) => b.hash(state),
684 EvaluationResult::String(s, _) => s.hash(state),
685 // Hash normalized decimal for consistency with equality
686 EvaluationResult::Decimal(d, _) => d.normalize().hash(state),
687 EvaluationResult::Integer(i, _) => i.hash(state),
688 EvaluationResult::Integer64(i, _) => i.hash(state),
689 EvaluationResult::Date(d, _) => d.hash(state),
690 EvaluationResult::DateTime(dt, _) => dt.hash(state),
691 EvaluationResult::Time(t, _) => t.hash(state),
692 EvaluationResult::Quantity(val, unit, _) => {
693 // Hash both normalized value and unit
694 val.normalize().hash(state);
695 unit.hash(state);
696 }
697 EvaluationResult::Collection {
698 items,
699 has_undefined_order,
700 ..
701 } => {
702 // Hash order flag and items
703 has_undefined_order.hash(state);
704 items.len().hash(state);
705 for item in items {
706 item.hash(state);
707 }
708 }
709 EvaluationResult::Object { map, .. } => {
710 // Hash objects with sorted keys for deterministic results
711 // Note: We don't hash type_namespace/type_name to maintain compatibility
712 let mut keys: Vec<_> = map.keys().collect();
713 keys.sort();
714 keys.len().hash(state);
715 for key in keys {
716 key.hash(state);
717 map[key].hash(state);
718 }
719 }
720 }
721 }
722}
723
724// === EvaluationResult Methods ===
725
726impl EvaluationResult {
727 // === Constructor Methods ===
728
729 /// Creates a Boolean result with System type.
730 pub fn boolean(value: bool) -> Self {
731 EvaluationResult::Boolean(value, Some(TypeInfoResult::new("System", "Boolean")))
732 }
733
734 /// Creates a Boolean result with FHIR type.
735 pub fn fhir_boolean(value: bool) -> Self {
736 EvaluationResult::Boolean(value, Some(TypeInfoResult::new("FHIR", "boolean")))
737 }
738
739 /// Creates a String result with System type.
740 pub fn string(value: String) -> Self {
741 EvaluationResult::String(value, Some(TypeInfoResult::new("System", "String")))
742 }
743
744 /// Creates a String result with FHIR type.
745 pub fn fhir_string(value: String, fhir_type: &str) -> Self {
746 EvaluationResult::String(value, Some(TypeInfoResult::new("FHIR", fhir_type)))
747 }
748
749 /// Creates an Integer result with System type.
750 pub fn integer(value: i64) -> Self {
751 EvaluationResult::Integer(value, Some(TypeInfoResult::new("System", "Integer")))
752 }
753
754 /// Creates an Integer result with FHIR type.
755 pub fn fhir_integer(value: i64) -> Self {
756 EvaluationResult::Integer(value, Some(TypeInfoResult::new("FHIR", "integer")))
757 }
758
759 /// Creates an Integer64 result with System type.
760 pub fn integer64(value: i64) -> Self {
761 EvaluationResult::Integer64(value, Some(TypeInfoResult::new("System", "Integer64")))
762 }
763
764 /// Creates an Integer64 result with FHIR type.
765 pub fn fhir_integer64(value: i64) -> Self {
766 EvaluationResult::Integer64(value, Some(TypeInfoResult::new("FHIR", "integer64")))
767 }
768
769 /// Creates a Decimal result with System type.
770 pub fn decimal(value: Decimal) -> Self {
771 EvaluationResult::Decimal(value, Some(TypeInfoResult::new("System", "Decimal")))
772 }
773
774 /// Creates a Decimal result with FHIR type.
775 pub fn fhir_decimal(value: Decimal) -> Self {
776 EvaluationResult::Decimal(value, Some(TypeInfoResult::new("FHIR", "decimal")))
777 }
778
779 /// Creates a Date result with System type.
780 pub fn date(value: String) -> Self {
781 EvaluationResult::Date(value, Some(TypeInfoResult::new("System", "Date")))
782 }
783
784 /// Creates a DateTime result with System type.
785 pub fn datetime(value: String) -> Self {
786 EvaluationResult::DateTime(value, Some(TypeInfoResult::new("System", "DateTime")))
787 }
788
789 /// Creates a Time result with System type.
790 pub fn time(value: String) -> Self {
791 EvaluationResult::Time(value, Some(TypeInfoResult::new("System", "Time")))
792 }
793
794 /// Creates a Quantity result with System type.
795 pub fn quantity(value: Decimal, unit: String) -> Self {
796 EvaluationResult::Quantity(value, unit, Some(TypeInfoResult::new("System", "Quantity")))
797 }
798
799 /// Creates a Collection result.
800 pub fn collection(items: Vec<EvaluationResult>) -> Self {
801 EvaluationResult::Collection {
802 items,
803 has_undefined_order: false,
804 type_info: None,
805 }
806 }
807
808 /// Creates an Object variant with just the map, no type information.
809 pub fn object(map: HashMap<String, EvaluationResult>) -> Self {
810 EvaluationResult::Object {
811 map,
812 type_info: None,
813 }
814 }
815
816 /// Creates an Object variant with type information.
817 pub fn typed_object(
818 map: HashMap<String, EvaluationResult>,
819 type_namespace: &str,
820 type_name: &str,
821 ) -> Self {
822 EvaluationResult::Object {
823 map,
824 type_info: Some(TypeInfoResult::new(type_namespace, type_name)),
825 }
826 }
827
828 // === Value Extraction Methods ===
829
830 /// Extracts the boolean value if this is a Boolean variant.
831 pub fn as_boolean(&self) -> Option<bool> {
832 match self {
833 EvaluationResult::Boolean(val, _) => Some(*val),
834 _ => None,
835 }
836 }
837
838 /// Extracts the string value if this is a String variant.
839 pub fn as_string(&self) -> Option<&String> {
840 match self {
841 EvaluationResult::String(val, _) => Some(val),
842 _ => None,
843 }
844 }
845
846 /// Extracts the integer value if this is an Integer variant.
847 pub fn as_integer(&self) -> Option<i64> {
848 match self {
849 EvaluationResult::Integer(val, _) => Some(*val),
850 _ => None,
851 }
852 }
853
854 /// Extracts the integer value if this is an Integer64 variant.
855 pub fn as_integer64(&self) -> Option<i64> {
856 match self {
857 EvaluationResult::Integer64(val, _) => Some(*val),
858 _ => None,
859 }
860 }
861
862 /// Extracts the decimal value if this is a Decimal variant.
863 pub fn as_decimal(&self) -> Option<Decimal> {
864 match self {
865 EvaluationResult::Decimal(val, _) => Some(*val),
866 _ => None,
867 }
868 }
869
870 /// Extracts the date value if this is a Date variant.
871 pub fn as_date(&self) -> Option<&String> {
872 match self {
873 EvaluationResult::Date(val, _) => Some(val),
874 _ => None,
875 }
876 }
877
878 /// Extracts the datetime value if this is a DateTime variant.
879 pub fn as_datetime(&self) -> Option<&String> {
880 match self {
881 EvaluationResult::DateTime(val, _) => Some(val),
882 _ => None,
883 }
884 }
885
886 /// Extracts the time value if this is a Time variant.
887 pub fn as_time(&self) -> Option<&String> {
888 match self {
889 EvaluationResult::Time(val, _) => Some(val),
890 _ => None,
891 }
892 }
893
894 /// Extracts the quantity value if this is a Quantity variant.
895 pub fn as_quantity(&self) -> Option<(Decimal, &String)> {
896 match self {
897 EvaluationResult::Quantity(val, unit, _) => Some((*val, unit)),
898 _ => None,
899 }
900 }
901 /// Checks if this result represents a collection.
902 ///
903 /// Returns `true` only for the `Collection` variant, not for other
904 /// multi-valued representations like `Object`.
905 ///
906 /// # Examples
907 ///
908 /// ```rust
909 /// use helios_fhirpath_support::EvaluationResult;
910 ///
911 /// let collection = EvaluationResult::Collection {
912 /// items: vec![],
913 /// has_undefined_order: false,
914 /// type_info: None,
915 /// };
916 /// assert!(collection.is_collection());
917 ///
918 /// let string = EvaluationResult::String("test".to_string(), None);
919 /// assert!(!string.is_collection());
920 /// ```
921 pub fn is_collection(&self) -> bool {
922 matches!(self, EvaluationResult::Collection { .. })
923 }
924
925 /// Returns the count of items according to FHIRPath counting rules.
926 ///
927 /// FHIRPath counting semantics:
928 /// - `Empty`: 0 items
929 /// - `Collection`: number of items in the collection
930 /// - All other variants: 1 item (single values)
931 ///
932 /// This matches the behavior of FHIRPath's `count()` function.
933 ///
934 /// # Examples
935 ///
936 /// ```rust
937 /// use helios_fhirpath_support::EvaluationResult;
938 ///
939 /// assert_eq!(EvaluationResult::Empty.count(), 0);
940 /// assert_eq!(EvaluationResult::String("test".to_string(), None).count(), 1);
941 ///
942 /// let collection = EvaluationResult::Collection {
943 /// items: vec![
944 /// EvaluationResult::Integer(1, None),
945 /// EvaluationResult::Integer(2, None),
946 /// ],
947 /// has_undefined_order: false,
948 /// type_info: None,
949 /// };
950 /// assert_eq!(collection.count(), 2);
951 /// ```
952 pub fn count(&self) -> usize {
953 match self {
954 EvaluationResult::Empty => 0,
955 EvaluationResult::Collection { items, .. } => items.len(),
956 _ => 1, // All non-collection variants count as 1
957 }
958 }
959 /// Converts the result to a boolean value according to FHIRPath truthiness rules.
960 ///
961 /// FHIRPath truthiness semantics:
962 /// - `Empty`: `false`
963 /// - `Boolean`: the boolean value itself
964 /// - `String`: `false` if empty, `true` otherwise
965 /// - `Decimal`/`Integer`: `false` if zero, `true` otherwise
966 /// - `Quantity`: `false` if value is zero, `true` otherwise
967 /// - `Collection`: `false` if empty, `true` otherwise
968 /// - Other types: `true` (Date, DateTime, Time, Object)
969 ///
970 /// Note: This is different from boolean conversion for logical operators.
971 ///
972 /// # Examples
973 ///
974 /// ```rust
975 /// use helios_fhirpath_support::EvaluationResult;
976 /// use rust_decimal::Decimal;
977 ///
978 /// assert_eq!(EvaluationResult::Empty.to_boolean(), false);
979 /// assert_eq!(EvaluationResult::Boolean(true, None).to_boolean(), true);
980 /// assert_eq!(EvaluationResult::String("".to_string(), None).to_boolean(), false);
981 /// assert_eq!(EvaluationResult::String("text".to_string(), None).to_boolean(), true);
982 /// assert_eq!(EvaluationResult::Integer(0, None).to_boolean(), false);
983 /// assert_eq!(EvaluationResult::Integer(42, None).to_boolean(), true);
984 /// ```
985 pub fn to_boolean(&self) -> bool {
986 match self {
987 EvaluationResult::Empty => false,
988 EvaluationResult::Boolean(b, _) => *b,
989 EvaluationResult::String(s, _) => !s.is_empty(),
990 EvaluationResult::Decimal(d, _) => !d.is_zero(),
991 EvaluationResult::Integer(i, _) => *i != 0,
992 EvaluationResult::Integer64(i, _) => *i != 0,
993 EvaluationResult::Quantity(q, _, _) => !q.is_zero(), // Truthy if value is non-zero
994 EvaluationResult::Collection { items, .. } => !items.is_empty(),
995 _ => true, // Date, DateTime, Time, Object are always truthy
996 }
997 }
998
999 /// Converts the result to its string representation.
1000 ///
1001 /// This method provides the string representation used by FHIRPath's
1002 /// `toString()` function and string conversion operations.
1003 ///
1004 /// # Conversion Rules
1005 ///
1006 /// - `Empty`: empty string
1007 /// - `Boolean`: "true" or "false"
1008 /// - `String`: the string value itself
1009 /// - Numeric types: string representation of the number
1010 /// - Date/Time types: the ISO format string
1011 /// - `Quantity`: formatted as "value 'unit'"
1012 /// - `Collection`: if single item, its string value; otherwise bracketed list
1013 /// - `Object`: "\[object\]" placeholder
1014 ///
1015 /// # Examples
1016 ///
1017 /// ```rust
1018 /// use helios_fhirpath_support::EvaluationResult;
1019 /// use rust_decimal::Decimal;
1020 ///
1021 /// assert_eq!(EvaluationResult::Empty.to_string_value(), "");
1022 /// assert_eq!(EvaluationResult::Boolean(true, None).to_string_value(), "true");
1023 /// assert_eq!(EvaluationResult::Integer(42, None).to_string_value(), "42");
1024 ///
1025 /// let quantity = EvaluationResult::Quantity(Decimal::new(54, 1), "mg".to_string(), None);
1026 /// assert_eq!(quantity.to_string_value(), "5.4 'mg'");
1027 /// ```
1028 pub fn to_string_value(&self) -> String {
1029 match self {
1030 EvaluationResult::Empty => "".to_string(),
1031 EvaluationResult::Boolean(b, _) => b.to_string(),
1032 EvaluationResult::String(s, _) => s.clone(),
1033 EvaluationResult::Decimal(d, _) => d.to_string(),
1034 EvaluationResult::Integer(i, _) => i.to_string(),
1035 EvaluationResult::Integer64(i, _) => i.to_string(),
1036 EvaluationResult::Date(d, _) => d.clone(), // Return stored string
1037 EvaluationResult::DateTime(dt, _) => dt.clone(), // Return stored string
1038 EvaluationResult::Time(t, _) => t.clone(), // Return stored string
1039 EvaluationResult::Quantity(val, unit, _) => {
1040 // Format as "value unit" for toString()
1041 // The FHIRPath spec for toString() doesn't require quotes around the unit
1042 let formatted_unit = format_unit_for_display(unit);
1043 format!("{} {}", val, formatted_unit)
1044 }
1045 EvaluationResult::Collection { items, .. } => {
1046 // FHIRPath toString rules for collections
1047 if items.len() == 1 {
1048 // Single item: return its string value
1049 items[0].to_string_value()
1050 } else {
1051 // Multiple items: return bracketed comma-separated list
1052 format!(
1053 "[{}]",
1054 items
1055 .iter()
1056 .map(|r| r.to_string_value())
1057 .collect::<Vec<_>>()
1058 .join(", ")
1059 )
1060 }
1061 }
1062 EvaluationResult::Object { .. } => "[object]".to_string(),
1063 }
1064 }
1065
1066 /// Converts the result to Boolean for logical operators (and, or, xor, implies).
1067 ///
1068 /// This method implements the specific boolean conversion rules used by FHIRPath
1069 /// logical operators, which are different from general truthiness rules.
1070 ///
1071 /// # Conversion Rules
1072 ///
1073 /// - `Boolean`: returns the boolean value unchanged
1074 /// - `String`: converts "true"/"t"/"yes"/"1"/"1.0" to `true`,
1075 /// "false"/"f"/"no"/"0"/"0.0" to `false`, others to `Empty`
1076 /// - `Collection`: single items are recursively converted, empty becomes `Empty`,
1077 /// multiple items cause an error
1078 /// - Other types: result in `Empty`
1079 ///
1080 /// # Errors
1081 ///
1082 /// Returns `SingletonEvaluationError` if called on a collection with multiple items.
1083 ///
1084 /// # Examples
1085 ///
1086 /// ```rust
1087 /// use helios_fhirpath_support::{EvaluationResult, EvaluationError};
1088 ///
1089 /// let true_str = EvaluationResult::String("true".to_string(), None);
1090 /// assert_eq!(true_str.to_boolean_for_logic().unwrap(), EvaluationResult::Boolean(true, None));
1091 ///
1092 /// let false_str = EvaluationResult::String("false".to_string(), None);
1093 /// assert_eq!(false_str.to_boolean_for_logic().unwrap(), EvaluationResult::Boolean(false, None));
1094 ///
1095 /// let other_str = EvaluationResult::String("maybe".to_string(), None);
1096 /// assert_eq!(other_str.to_boolean_for_logic().unwrap(), EvaluationResult::Empty);
1097 ///
1098 /// let integer = EvaluationResult::Integer(42, None);
1099 /// assert_eq!(integer.to_boolean_for_logic().unwrap(), EvaluationResult::Boolean(true, None));
1100 /// ```
1101 pub fn to_boolean_for_logic(&self) -> Result<EvaluationResult, EvaluationError> {
1102 // Default to R5 behavior for backward compatibility
1103 self.to_boolean_for_logic_with_r4_compat(false)
1104 }
1105
1106 /// Converts this evaluation result to its boolean representation for logical operations
1107 /// with R4 compatibility mode for integer handling
1108 ///
1109 /// # Arguments
1110 /// * `r4_compat` - If true, uses R4 semantics where 0 is false and non-zero is true.
1111 /// If false, uses R5+ semantics where all integers are truthy.
1112 pub fn to_boolean_for_logic_with_r4_compat(
1113 &self,
1114 r4_compat: bool,
1115 ) -> Result<EvaluationResult, EvaluationError> {
1116 match self {
1117 EvaluationResult::Boolean(b, type_info) => {
1118 Ok(EvaluationResult::Boolean(*b, type_info.clone()))
1119 }
1120 EvaluationResult::String(s, _) => {
1121 // Convert string to boolean based on recognized values
1122 Ok(match s.to_lowercase().as_str() {
1123 "true" | "t" | "yes" | "1" | "1.0" => EvaluationResult::boolean(true),
1124 "false" | "f" | "no" | "0" | "0.0" => EvaluationResult::boolean(false),
1125 _ => EvaluationResult::Empty, // Unrecognized strings become Empty
1126 })
1127 }
1128 EvaluationResult::Collection { items, .. } => {
1129 match items.len() {
1130 0 => Ok(EvaluationResult::Empty),
1131 1 => items[0].to_boolean_for_logic_with_r4_compat(r4_compat), // Recursive conversion
1132 n => Err(EvaluationError::SingletonEvaluationError(format!(
1133 "Boolean logic requires singleton collection, found {} items",
1134 n
1135 ))),
1136 }
1137 }
1138 EvaluationResult::Integer(i, _) => {
1139 if r4_compat {
1140 // R4/R4B: C-like semantics - 0 is false, non-zero is true
1141 Ok(EvaluationResult::boolean(*i != 0))
1142 } else {
1143 // R5/R6: All integers are truthy (even 0)
1144 Ok(EvaluationResult::boolean(true))
1145 }
1146 }
1147 EvaluationResult::Integer64(i, _) => {
1148 if r4_compat {
1149 // R4/R4B: C-like semantics - 0 is false, non-zero is true
1150 Ok(EvaluationResult::boolean(*i != 0))
1151 } else {
1152 // R5/R6: All integers are truthy (even 0)
1153 Ok(EvaluationResult::boolean(true))
1154 }
1155 }
1156 // Per FHIRPath spec section 5.2: other types evaluate to Empty for logical operators
1157 EvaluationResult::Decimal(_, _)
1158 | EvaluationResult::Date(_, _)
1159 | EvaluationResult::DateTime(_, _)
1160 | EvaluationResult::Time(_, _)
1161 | EvaluationResult::Quantity(_, _, _)
1162 | EvaluationResult::Object { .. } => Ok(EvaluationResult::Empty),
1163 EvaluationResult::Empty => Ok(EvaluationResult::Empty),
1164 }
1165 }
1166
1167 /// Checks if the result is a String or Empty variant.
1168 ///
1169 /// This is a utility method used in various FHIRPath operations that
1170 /// need to distinguish string-like values from other types.
1171 ///
1172 /// # Examples
1173 ///
1174 /// ```rust
1175 /// use helios_fhirpath_support::EvaluationResult;
1176 ///
1177 /// assert!(EvaluationResult::Empty.is_string_or_empty());
1178 /// assert!(EvaluationResult::String("test".to_string(), None).is_string_or_empty());
1179 /// assert!(!EvaluationResult::Integer(42, None).is_string_or_empty());
1180 /// ```
1181 pub fn is_string_or_empty(&self) -> bool {
1182 matches!(
1183 self,
1184 EvaluationResult::String(_, _) | EvaluationResult::Empty
1185 )
1186 }
1187
1188 /// Returns the type name of this evaluation result.
1189 ///
1190 /// This method returns a string representation of the variant type,
1191 /// useful for error messages, debugging, and type checking operations.
1192 ///
1193 /// # Examples
1194 ///
1195 /// ```rust
1196 /// use helios_fhirpath_support::EvaluationResult;
1197 ///
1198 /// assert_eq!(EvaluationResult::Empty.type_name(), "Empty");
1199 /// assert_eq!(EvaluationResult::String("test".to_string(), None).type_name(), "String");
1200 /// assert_eq!(EvaluationResult::Integer(42, None).type_name(), "Integer");
1201 ///
1202 /// let collection = EvaluationResult::Collection {
1203 /// items: vec![],
1204 /// has_undefined_order: false,
1205 /// type_info: None,
1206 /// };
1207 /// assert_eq!(collection.type_name(), "Collection");
1208 /// ```
1209 pub fn type_name(&self) -> &'static str {
1210 match self {
1211 EvaluationResult::Empty => "Empty",
1212 EvaluationResult::Boolean(_, _) => "Boolean",
1213 EvaluationResult::String(_, _) => "String",
1214 EvaluationResult::Decimal(_, _) => "Decimal",
1215 EvaluationResult::Integer(_, _) => "Integer",
1216 EvaluationResult::Integer64(_, _) => "Integer64",
1217 EvaluationResult::Date(_, _) => "Date",
1218 EvaluationResult::DateTime(_, _) => "DateTime",
1219 EvaluationResult::Time(_, _) => "Time",
1220 EvaluationResult::Quantity(_, _, _) => "Quantity",
1221 EvaluationResult::Collection { .. } => "Collection",
1222 EvaluationResult::Object { .. } => "Object",
1223 }
1224 }
1225}
1226
1227// === IntoEvaluationResult Implementations ===
1228//
1229// The following implementations provide conversions from standard Rust types
1230// and common patterns into EvaluationResult variants. These enable seamless
1231// integration between Rust code and the FHIRPath evaluation system.
1232
1233/// Converts a `String` to `EvaluationResult::String`.
1234///
1235/// This is the most direct conversion for text values in the FHIRPath system.
1236impl IntoEvaluationResult for String {
1237 fn to_evaluation_result(&self) -> EvaluationResult {
1238 EvaluationResult::string(self.clone())
1239 }
1240}
1241
1242/// Converts a `bool` to `EvaluationResult::Boolean`.
1243///
1244/// Enables direct use of Rust boolean values in FHIRPath expressions.
1245impl IntoEvaluationResult for bool {
1246 fn to_evaluation_result(&self) -> EvaluationResult {
1247 EvaluationResult::boolean(*self)
1248 }
1249}
1250
1251/// Converts an `i32` to `EvaluationResult::Integer`.
1252///
1253/// Automatically promotes to `i64` for consistent integer handling.
1254impl IntoEvaluationResult for i32 {
1255 fn to_evaluation_result(&self) -> EvaluationResult {
1256 EvaluationResult::integer(*self as i64)
1257 }
1258}
1259
1260/// Converts an `i64` to `EvaluationResult::Integer`.
1261///
1262/// This is the primary integer type used in FHIRPath evaluation.
1263impl IntoEvaluationResult for i64 {
1264 fn to_evaluation_result(&self) -> EvaluationResult {
1265 EvaluationResult::integer64(*self)
1266 }
1267}
1268
1269/// Converts an `f64` to `EvaluationResult::Decimal` with error handling.
1270///
1271/// Uses high-precision `Decimal` type to avoid floating-point errors.
1272/// Returns `Empty` for invalid values like NaN or Infinity.
1273impl IntoEvaluationResult for f64 {
1274 fn to_evaluation_result(&self) -> EvaluationResult {
1275 Decimal::from_f64(*self)
1276 .map(EvaluationResult::decimal)
1277 .unwrap_or(EvaluationResult::Empty)
1278 }
1279}
1280
1281/// Converts a `rust_decimal::Decimal` to `EvaluationResult::Decimal`.
1282///
1283/// This is the preferred conversion for precise decimal values in FHIR.
1284impl IntoEvaluationResult for Decimal {
1285 fn to_evaluation_result(&self) -> EvaluationResult {
1286 EvaluationResult::decimal(*self)
1287 }
1288}
1289
1290// === Generic Container Implementations ===
1291//
1292// These implementations handle common Rust container types, enabling
1293// seamless conversion of complex data structures to FHIRPath results.
1294
1295/// Converts `Option<T>` to either the inner value's result or `Empty`.
1296///
1297/// This is fundamental for handling FHIR's optional fields and nullable values.
1298/// `Some(value)` converts the inner value, `None` becomes `Empty`.
1299impl<T> IntoEvaluationResult for Option<T>
1300where
1301 T: IntoEvaluationResult,
1302{
1303 fn to_evaluation_result(&self) -> EvaluationResult {
1304 match self {
1305 Some(value) => value.to_evaluation_result(),
1306 None => EvaluationResult::Empty,
1307 }
1308 }
1309}
1310
1311/// Converts `Vec<T>` to `EvaluationResult::Collection`.
1312///
1313/// Each item in the vector is converted to an `EvaluationResult`. The resulting
1314/// collection is marked as having defined order (FHIRPath collections maintain order).
1315impl<T> IntoEvaluationResult for Vec<T>
1316where
1317 T: IntoEvaluationResult,
1318{
1319 fn to_evaluation_result(&self) -> EvaluationResult {
1320 let collection: Vec<EvaluationResult> = self
1321 .iter()
1322 .map(|item| item.to_evaluation_result())
1323 .collect();
1324 EvaluationResult::Collection {
1325 items: collection,
1326 has_undefined_order: false,
1327 type_info: None,
1328 }
1329 }
1330}
1331
1332/// Converts `Box<T>` to the result of the boxed value.
1333///
1334/// This enables use of boxed values (often used to break circular references
1335/// in FHIR data structures) directly in FHIRPath evaluation.
1336impl<T> IntoEvaluationResult for Box<T>
1337where
1338 T: IntoEvaluationResult + ?Sized,
1339{
1340 fn to_evaluation_result(&self) -> EvaluationResult {
1341 (**self).to_evaluation_result()
1342 }
1343}
1344
1345/// Convenience function for converting values to evaluation results.
1346///
1347/// This function provides a unified interface for conversion that can be used
1348/// by the evaluator and macro systems. It's particularly useful when working
1349/// with trait objects or in generic contexts.
1350///
1351/// # Arguments
1352///
1353/// * `value` - Any value implementing `IntoEvaluationResult`
1354///
1355/// # Returns
1356///
1357/// The `EvaluationResult` representation of the input value.
1358///
1359/// # Examples
1360///
1361/// ```rust
1362/// use helios_fhirpath_support::{convert_value_to_evaluation_result, EvaluationResult};
1363///
1364/// let result = convert_value_to_evaluation_result(&"hello".to_string());
1365/// assert_eq!(result, EvaluationResult::String("hello".to_string(), None));
1366///
1367/// let numbers = vec![1, 2, 3];
1368/// let collection_result = convert_value_to_evaluation_result(&numbers);
1369/// assert_eq!(collection_result.count(), 3);
1370/// ```
1371pub fn convert_value_to_evaluation_result<T>(value: &T) -> EvaluationResult
1372where
1373 T: IntoEvaluationResult + ?Sized,
1374{
1375 value.to_evaluation_result()
1376}
1377
1378/// Formats a unit for display in toString() output
1379fn format_unit_for_display(unit: &str) -> String {
1380 // FHIRPath spec formatting for units in toString():
1381 // - Calendar word units (week, day, etc.): displayed without quotes
1382 // - UCUM code units ('wk', 'mg', etc.): displayed with quotes
1383
1384 // Calendar word units that don't need quotes
1385 const CALENDAR_WORDS: &[&str] = &[
1386 "year",
1387 "years",
1388 "month",
1389 "months",
1390 "week",
1391 "weeks",
1392 "day",
1393 "days",
1394 "hour",
1395 "hours",
1396 "minute",
1397 "minutes",
1398 "second",
1399 "seconds",
1400 "millisecond",
1401 "milliseconds",
1402 ];
1403
1404 if CALENDAR_WORDS.contains(&unit) {
1405 // Calendar word units: display without quotes (R5 behavior, likely correct)
1406 unit.to_string()
1407 } else {
1408 // UCUM code units: display with quotes
1409 format!("'{}'", unit)
1410 }
1411}