helios_fhirpath/lib.rs
1//! # FHIRPath Expression Engine
2//!
3//! This crate provides a complete implementation of the [FHIRPath 3.0.0 specification](https://hl7.org/fhirpath/2025Jan/)
4//! for evaluating FHIRPath expressions against FHIR resources. FHIRPath is a path-based navigation
5//! and extraction language designed specifically for FHIR resources, enabling powerful queries
6//! and data manipulation operations.
7
8//!
9//! ## Overview
10//!
11//! FHIRPath is a declarative language that allows you to:
12//! - **Navigate FHIR resources** using path expressions (e.g., `Patient.name.family`)
13//! - **Filter collections** with boolean predicates (e.g., `telecom.where(system = 'email')`)
14//! - **Transform data** using built-in functions (e.g., `name.given.first()`)
15//! - **Perform calculations** with mathematical operations (e.g., `birthDate.today() - birthDate`)
16//! - **Access extensions** in FHIR resources (e.g., `Patient.extension('http://example.org/birthPlace')`)
17//! - **Work with types** using type checking and conversion (e.g., `value.is(Quantity)`)
18//!
19//! ## Key Features
20//!
21//! ### Core Functionality
22//! - **Parser**: Complete FHIRPath syntax support including literals, operators, and function calls
23//! - **Evaluator**: Fast evaluation engine with proper type handling and error reporting
24//! - **Type System**: Support for both FHIR and System namespaces with automatic type inference
25//! - **Extension Support**: Native handling of FHIR extensions and choice elements
26//!
27//! ### Language Support
28//! - **Collections**: Comprehensive collection operations (where, select, all, exists, etc.)
29//! - **Mathematics**: Arithmetic operations with proper decimal precision handling
30//! - **String Operations**: Text manipulation and pattern matching functions
31//! - **Date/Time**: Temporal operations with timezone and precision support
32//! - **Type Operations**: Dynamic type checking with `is`, `as`, and `ofType` operators
33//! - **Variables**: Support for external variables and built-in constants
34//!
35//! ### FHIR Integration
36//! - **Multi-version Support**: Works with FHIR R4, R4B, R5, and R6 via feature flags
37//! - **Resource Navigation**: Smart navigation of FHIR choice elements (e.g., `value[x]`)
38//! - **Extension Access**: Built-in `extension()` function for FHIR extension handling
39//! - **Type Hierarchy**: Understanding of FHIR resource and data type relationships
40//!
41//! ## Architecture
42//!
43//! The crate is organized into several key components:
44//!
45//! - **Public API** (`lib.rs`): Simple interface with [`evaluate_expression`] function
46//! - **Parser** (`parser.rs`): Converts FHIRPath text into an Abstract Syntax Tree (AST)
47//! - **Evaluator** (`evaluator.rs`): Executes the AST against FHIR resources
48//! - **Function Modules**: Specialized implementations for FHIRPath functions
49//! - **Type System**: FHIR type hierarchy and namespace management
50//! - **Support Types**: Integration with the `fhirpath_support` crate for results
51//!
52//! ## Usage Examples
53//!
54//! ### Basic Navigation
55//!
56//! ```rust,no_run
57//! use helios_fhirpath::{evaluate_expression, EvaluationContext};
58//! # use helios_fhir::r4::{Patient, HumanName};
59//!
60//! # // Create a patient resource
61//! # let patient = Patient::default();
62//! # let context = EvaluationContext::new(vec![
63//! # helios_fhir::FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(Box::new(patient))))
64//! # ]);
65//!
66//! // Navigate to family name
67//! let result = evaluate_expression("Patient.name.family", &context)?;
68//! // Result: Collection containing family names
69//!
70//! // Get first given name
71//! let result = evaluate_expression("Patient.name.given.first()", &context)?;
72//! // Result: First given name as string
73//!
74//! // Check if patient is active
75//! let result = evaluate_expression("Patient.active", &context)?;
76//! // Result: Boolean value
77//! # Ok::<(), String>(())
78//! ```
79//!
80//! ### Collection Operations
81//!
82//! ```rust,no_run
83//! # use helios_fhirpath::{evaluate_expression, EvaluationContext};
84//! # use helios_fhir::r4::Patient;
85//! # let patient = Patient::default();
86//! # let context = EvaluationContext::new(vec![helios_fhir::FhirResource::R4(Box::new(helios_fhir::r4::Resource::Patient(Box::new(patient))))]);
87//!
88//! // Filter email addresses
89//! let result = evaluate_expression(
90//! "Patient.telecom.where(system = 'email')",
91//! &context
92//! )?;
93//!
94//! // Check if any email exists
95//! let result = evaluate_expression(
96//! "Patient.telecom.where(system = 'email').exists()",
97//! &context
98//! )?;
99//!
100//! // Count phone numbers
101//! let result = evaluate_expression(
102//! "Patient.telecom.where(system = 'phone').count()",
103//! &context
104//! )?;
105//! # Ok::<(), String>(())
106//! ```
107//!
108//! ### Type Operations
109//!
110//! ```rust,no_run
111//! # use helios_fhirpath::{evaluate_expression, EvaluationContext};
112//! # use helios_fhir::r4::Observation;
113//! # let observation = Observation::default();
114//! # let context = EvaluationContext::new(vec![helios_fhir::FhirResource::R4(Box::new(helios_fhir::r4::Resource::Observation(Box::new(observation))))]);
115//!
116//! // Check if observation value is a Quantity
117//! let result = evaluate_expression(
118//! "Observation.value.is(Quantity)",
119//! &context
120//! )?;
121//!
122//! // Cast value to Quantity and get unit
123//! let result = evaluate_expression(
124//! "Observation.value.as(Quantity).unit",
125//! &context
126//! )?;
127//!
128//! // Get type information
129//! let result = evaluate_expression(
130//! "Observation.value.type().name",
131//! &context
132//! )?;
133//! # Ok::<(), String>(())
134//! ```
135//!
136//! ### Extension Access
137//!
138//! ```rust,no_run
139//! # use helios_fhirpath::{evaluate_expression, EvaluationContext, EvaluationResult};
140//! # use helios_fhir::r4::Patient;
141//!
142//! // Create context with patient data
143//! let mut context = EvaluationContext::new(vec![]);
144//!
145//! // Access FHIR extension by URL
146//! let result = evaluate_expression(
147//! "Patient.extension('http://hl7.org/fhir/StructureDefinition/patient-birthPlace')",
148//! &context
149//! )?;
150//!
151//! // Extension with variable
152//! context.set_variable_result("birthPlaceUrl", EvaluationResult::string(
153//! "http://hl7.org/fhir/StructureDefinition/patient-birthPlace".to_string()
154//! ));
155//! let result = evaluate_expression(
156//! "Patient.extension(%birthPlaceUrl).value",
157//! &context
158//! )?;
159//! # Ok::<(), String>(())
160//! ```
161//!
162//! ### Mathematical Operations
163//!
164//! ```rust,no_run
165//! # use helios_fhirpath::{evaluate_expression, EvaluationContext};
166//! # let context = EvaluationContext::new(vec![]);
167//!
168//! // Basic arithmetic
169//! let result = evaluate_expression("1 + 2 * 3", &context)?; // Result: 7
170//!
171//! // Decimal operations
172//! let result = evaluate_expression("10.5 / 2.1", &context)?;
173//!
174//! // Age calculation (if Patient.birthDate exists)
175//! let result = evaluate_expression(
176//! "today() - Patient.birthDate",
177//! &context
178//! )?;
179//! # Ok::<(), String>(())
180//! ```
181//!
182//! ### Variables and Constants
183//!
184//! ```rust,no_run
185//! # use helios_fhirpath::{evaluate_expression, EvaluationContext, EvaluationResult};
186//! let mut context = EvaluationContext::new(vec![]);
187//!
188//! // Set custom variables
189//! context.set_variable_result("threshold", EvaluationResult::decimal(rust_decimal::Decimal::new(5, 0)));
190//! context.set_variable_result("unitSystem", EvaluationResult::string("metric".to_string()));
191//!
192//! // Use variables in expressions
193//! let result = evaluate_expression("value > %threshold", &context)?;
194//!
195//! // Built-in constants are automatically available
196//! let result = evaluate_expression("system = %ucum", &context)?; // %ucum = 'http://unitsofmeasure.org'
197//! # Ok::<(), String>(())
198//! ```
199//!
200//! ## Error Handling
201//!
202//! The [`evaluate_expression`] function returns detailed error messages for both parsing and evaluation failures:
203//!
204//! ```rust,no_run
205//! # use helios_fhirpath::{evaluate_expression, EvaluationContext};
206//! # let context = EvaluationContext::new(vec![]);
207//!
208//! // Syntax error
209//! match evaluate_expression("Patient.name.", &context) {
210//! Err(err) => println!("Parse error: {}", err),
211//! Ok(_) => {}
212//! }
213//!
214//! // Runtime error
215//! match evaluate_expression("Patient.nonExistentField", &context) {
216//! Err(err) => println!("Evaluation error: {}", err),
217//! Ok(_) => {}
218//! }
219//! ```
220//!
221//! ## Performance Considerations
222//!
223//! - **Parsing**: Expression parsing is relatively expensive; consider caching parsed expressions for repeated use
224//! - **Evaluation**: Evaluation performance depends on resource size and expression complexity
225//! - **Memory**: Large collections in FHIR resources may consume significant memory during evaluation
226//!
227//! ## Specification Compliance
228//!
229//! This implementation aims for full compliance with [FHIRPath 3.0.0](https://hl7.org/fhirpath/2025Jan/).
230//! Current implementation status includes:
231//!
232//! - ✅ **Core Language**: Literals, operators, path navigation
233//! - ✅ **Collection Functions**: where, select, first, last, tail, etc.
234//! - ✅ **Boolean Logic**: and, or, not, implies, xor
235//! - ✅ **Type Operations**: is, as, ofType with FHIR type system
236//! - ✅ **String Functions**: matches, contains, startsWith, etc.
237//! - ✅ **Math Functions**: abs, ceiling, floor, round, sqrt, etc.
238//! - ✅ **Date Functions**: today, now, date/time arithmetic
239//! - ✅ **Extension Functions**: FHIR extension access
240//! - ✅ **Variables**: External variables and built-in constants
241//! - 🟡 **Advanced Features**: Some STU (Standard for Trial Use) functions
242//!
243//! See the [FHIRPath README](https://github.com/HeliosSoftware/hfs/blob/main/crates/fhirpath/README.md)
244//! for detailed implementation status.
245//!
246//! ## FHIR Version Support
247//!
248//! This crate supports multiple FHIR versions through Cargo feature flags:
249//!
250//! ```toml
251//! [dependencies]
252//! fhirpath = { version = "0.1", features = ["R4"] } # FHIR R4 support
253//! fhirpath = { version = "0.1", features = ["R5"] } # FHIR R5 support
254//! fhirpath = { version = "0.1", features = ["R4", "R5"] } # Multiple versions
255//! ```
256//!
257//! Available features:
258//! - `R4`: FHIR 4.0.1 (normative)
259//! - `R4B`: FHIR 4.3.0 (ballot)
260//! - `R5`: FHIR 5.0.0 (ballot)
261//! - `R6`: FHIR 6.0.0 (draft)
262
263// Internal modules - not part of the public API
264mod aggregate_function;
265mod aggregate_math_functions;
266mod boolean_functions;
267mod boundary_functions;
268mod collection_functions;
269mod collection_navigation;
270mod contains_function;
271mod conversion_functions;
272mod format_functions;
273mod interval_functions;
274mod json_utils;
275pub mod ucum;
276// Public for internal testing only - not part of the public API
277#[doc(hidden)]
278pub mod date_operation;
279mod datetime_impl;
280pub mod debug_trace;
281mod distinct_functions;
282mod extension_function;
283mod fhir_type_hierarchy;
284mod long_conversion;
285mod not_function;
286mod polymorphic_access;
287mod reference_key_functions;
288mod repeat_all_function;
289mod repeat_function;
290mod resolve_function;
291mod resource_type;
292mod set_operations;
293mod subset_functions;
294mod terminology_client;
295mod terminology_functions;
296mod trace_function;
297mod type_function;
298pub mod type_inference;
299
300// Modules for CLI and server functionality
301pub mod cli;
302pub mod error;
303pub mod handlers;
304pub mod models;
305pub mod parse_debug;
306pub mod server;
307
308// Public modules needed for the public API
309pub mod evaluator;
310pub mod parser;
311
312// Public API exports - this is what users of the fhirpath crate should use
313pub use evaluator::EvaluationContext;
314pub use helios_fhirpath_support::EvaluationResult;
315
316/// Evaluates a FHIRPath expression against a given context.
317///
318/// This is the primary interface for FHIRPath evaluation. It combines parsing and evaluation
319/// into a single convenient function call.
320///
321/// # Arguments
322///
323/// * `expression` - The FHIRPath expression string to evaluate
324/// * `context` - The evaluation context containing the FHIR resource(s) to evaluate against
325///
326/// # Returns
327///
328/// Returns a `Result` containing either:
329/// - `Ok(EvaluationResult)` - The result of evaluating the expression
330/// - `Err(String)` - An error message if parsing or evaluation fails
331///
332/// # Examples
333///
334/// ```rust,no_run
335/// use helios_fhirpath::{evaluate_expression, EvaluationContext};
336/// use helios_fhir::r4::Observation;
337///
338/// // Create a context with a FHIR resource
339/// # let observation = Observation::default();
340/// let context = EvaluationContext::new(vec![helios_fhir::FhirResource::R4(Box::new(helios_fhir::r4::Resource::Observation(Box::new(observation))))]);
341///
342/// // Evaluate a simple expression
343/// let result = evaluate_expression("value.unit", &context)?;
344/// # Ok::<(), String>(())
345/// ```
346///
347/// # Notes
348///
349/// - The expression is parsed using the FHIRPath parser, which follows the FHIRPath 3.0.0 specification
350/// - Evaluation is performed against the resources in the provided context
351/// - Variables should be set on the context before calling this function
352/// - The function handles all parsing errors and evaluation errors uniformly
353pub fn evaluate_expression(
354 expression: &str,
355 context: &EvaluationContext,
356) -> Result<EvaluationResult, String> {
357 let parsed = parse_expression(expression)?;
358
359 // Evaluate the parsed expression
360 evaluator::evaluate(&parsed, context, None).map_err(|e| {
361 format!(
362 "Failed to evaluate FHIRPath expression '{}': {}",
363 expression, e
364 )
365 })
366}
367
368/// Parse a FHIRPath expression source string into a typed [`parser::Expression`] AST.
369///
370/// Provides a chumsky-free entry point for consumers that need the AST
371/// (e.g. compiling FHIRPath to SQL) without taking a dependency on the
372/// parser-combinator crate.
373pub fn parse_expression(expression: &str) -> Result<parser::Expression, String> {
374 use chumsky::Parser;
375
376 parser::parser()
377 .parse(expression)
378 .into_result()
379 .map_err(|e| {
380 format!(
381 "Failed to parse FHIRPath expression '{}': {:?}",
382 expression, e
383 )
384 })
385}