octofhir_fhir_model/evaluator.rs
1//! FHIRPath evaluator trait for dependency injection
2//!
3//! This module provides the abstract evaluator interface that enables
4//! fhirschema and other libraries to use FHIRPath evaluation with enhanced
5//! validation capabilities through ModelProvider injection.
6
7use async_trait::async_trait;
8use serde_json::Value as JsonValue;
9use std::collections::HashMap;
10use std::sync::Arc;
11
12use crate::error::Result;
13use crate::evaluation::EvaluationResult;
14use crate::provider::ModelProvider;
15
16/// Variables for FHIRPath evaluation context
17pub type Variables = HashMap<String, EvaluationResult>;
18
19/// Compiled FHIRPath expression for reuse
20#[derive(Debug, Clone)]
21pub struct CompiledExpression {
22 /// The original expression string
23 pub expression: String,
24 /// Internal representation (implementation-specific)
25 pub compiled_form: String,
26 /// Whether the expression is valid
27 pub is_valid: bool,
28}
29
30impl CompiledExpression {
31 /// Create a new compiled expression
32 pub fn new(expression: String, compiled_form: String, is_valid: bool) -> Self {
33 Self {
34 expression,
35 compiled_form,
36 is_valid,
37 }
38 }
39
40 /// Create an invalid expression
41 pub fn invalid(expression: String, error: String) -> Self {
42 Self {
43 expression,
44 compiled_form: error,
45 is_valid: false,
46 }
47 }
48}
49
50/// Validation result for FHIRPath expressions and constraints
51#[derive(Debug, Clone)]
52pub struct ValidationResult {
53 /// Whether validation passed
54 pub is_valid: bool,
55 /// Validation errors if any
56 pub errors: Vec<ValidationError>,
57 /// Warnings that don't prevent validation
58 pub warnings: Vec<ValidationWarning>,
59}
60
61impl ValidationResult {
62 /// Create a successful validation result
63 pub fn success() -> Self {
64 Self {
65 is_valid: true,
66 errors: Vec::new(),
67 warnings: Vec::new(),
68 }
69 }
70
71 /// Create a failed validation result with errors
72 pub fn with_errors(errors: Vec<ValidationError>) -> Self {
73 Self {
74 is_valid: false,
75 errors,
76 warnings: Vec::new(),
77 }
78 }
79
80 /// Add a warning to the validation result
81 pub fn with_warning(mut self, warning: ValidationWarning) -> Self {
82 self.warnings.push(warning);
83 self
84 }
85
86 /// Add an error to the validation result
87 pub fn with_error(mut self, error: ValidationError) -> Self {
88 self.is_valid = false;
89 self.errors.push(error);
90 self
91 }
92}
93
94/// Validation error details
95#[derive(Debug, Clone)]
96pub struct ValidationError {
97 /// Error message
98 pub message: String,
99 /// Error code if available
100 pub code: Option<String>,
101 /// Location in the expression or resource
102 pub location: Option<String>,
103 /// Severity level
104 pub severity: ErrorSeverity,
105}
106
107impl ValidationError {
108 /// Create a new validation error
109 pub fn new(message: String) -> Self {
110 Self {
111 message,
112 code: None,
113 location: None,
114 severity: ErrorSeverity::Error,
115 }
116 }
117
118 /// Create with code
119 pub fn with_code(mut self, code: String) -> Self {
120 self.code = Some(code);
121 self
122 }
123
124 /// Create with location
125 pub fn with_location(mut self, location: String) -> Self {
126 self.location = Some(location);
127 self
128 }
129}
130
131/// Validation warning details
132#[derive(Debug, Clone)]
133pub struct ValidationWarning {
134 /// Warning message
135 pub message: String,
136 /// Warning code if available
137 pub code: Option<String>,
138 /// Location in the expression or resource
139 pub location: Option<String>,
140}
141
142impl ValidationWarning {
143 /// Create a new validation warning
144 pub fn new(message: String) -> Self {
145 Self {
146 message,
147 code: None,
148 location: None,
149 }
150 }
151
152 /// Create with code
153 pub fn with_code(mut self, code: String) -> Self {
154 self.code = Some(code);
155 self
156 }
157
158 /// Create with location
159 pub fn with_location(mut self, location: String) -> Self {
160 self.location = Some(location);
161 self
162 }
163}
164
165/// Error severity levels
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum ErrorSeverity {
168 /// Fatal error - stops processing
169 Fatal,
170 /// Error - validation fails
171 Error,
172 /// Warning - validation continues
173 Warning,
174 /// Information - no impact on validation
175 Information,
176}
177
178/// FHIRPath constraint for validation
179#[derive(Debug, Clone)]
180pub struct FhirPathConstraint {
181 /// Constraint identifier
182 pub key: String,
183 /// Human-readable description
184 pub description: String,
185 /// FHIRPath expression to evaluate
186 pub expression: String,
187 /// Severity if constraint fails
188 pub severity: ErrorSeverity,
189 /// Whether this constraint is required
190 pub required: bool,
191}
192
193impl FhirPathConstraint {
194 /// Create a new constraint
195 pub fn new(key: String, description: String, expression: String) -> Self {
196 Self {
197 key,
198 description,
199 expression,
200 severity: ErrorSeverity::Error,
201 required: true,
202 }
203 }
204
205 /// Set severity level
206 pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
207 self.severity = severity;
208 self
209 }
210
211 /// Set as optional constraint
212 pub fn optional(mut self) -> Self {
213 self.required = false;
214 self
215 }
216}
217
218/// Abstract FHIRPath evaluator interface
219///
220/// This trait enables dependency injection of FHIRPath evaluators into
221/// libraries like fhirschema, allowing them to use enhanced validation
222/// capabilities without circular dependencies.
223#[async_trait]
224pub trait FhirPathEvaluator: Send + Sync {
225 /// Core evaluation method
226 ///
227 /// Evaluates a FHIRPath expression against the given context.
228 ///
229 /// # Arguments
230 /// * `expression` - The FHIRPath expression to evaluate
231 /// * `context` - The JSON context for evaluation (usually a FHIR resource)
232 ///
233 /// # Returns
234 /// The evaluation result or an error
235 async fn evaluate(&self, expression: &str, context: &JsonValue) -> Result<EvaluationResult>;
236
237 /// Evaluate with variables
238 ///
239 /// Evaluates a FHIRPath expression with additional variable context.
240 ///
241 /// # Arguments
242 /// * `expression` - The FHIRPath expression to evaluate
243 /// * `context` - The JSON context for evaluation
244 /// * `variables` - Additional variables available during evaluation
245 ///
246 /// # Returns
247 /// The evaluation result or an error
248 async fn evaluate_with_variables(
249 &self,
250 expression: &str,
251 context: &JsonValue,
252 variables: &Variables,
253 ) -> Result<EvaluationResult>;
254
255 /// Compile an expression for reuse
256 ///
257 /// Pre-compiles a FHIRPath expression for efficient repeated evaluation.
258 ///
259 /// # Arguments
260 /// * `expression` - The FHIRPath expression to compile
261 ///
262 /// # Returns
263 /// A compiled expression that can be reused
264 async fn compile(&self, expression: &str) -> Result<CompiledExpression>;
265
266 /// Validate expression syntax
267 ///
268 /// Checks if a FHIRPath expression is syntactically valid.
269 ///
270 /// # Arguments
271 /// * `expression` - The FHIRPath expression to validate
272 ///
273 /// # Returns
274 /// Validation result with any syntax errors
275 async fn validate_expression(&self, expression: &str) -> Result<ValidationResult>;
276
277 /// Get the ModelProvider for this evaluator
278 ///
279 /// Provides access to the injected ModelProvider for type information
280 /// and validation capabilities.
281 ///
282 /// # Returns
283 /// Reference to the ModelProvider
284 fn model_provider(&self) -> &dyn ModelProvider;
285
286 /// Get the ValidationProvider for this evaluator (if available)
287 ///
288 /// Provides access to enhanced validation capabilities for profile
289 /// validation and conformance checking. Returns None if the evaluator
290 /// was created without validation provider support.
291 ///
292 /// # Returns
293 /// Optional reference to the ValidationProvider
294 fn validation_provider(&self) -> Option<&dyn ValidationProvider> {
295 // Default implementation returns None - override in concrete evaluators
296 None
297 }
298
299 /// Validate FHIR constraints
300 ///
301 /// Evaluates multiple FHIRPath constraints against a resource for
302 /// comprehensive validation.
303 ///
304 /// # Arguments
305 /// * `resource` - The FHIR resource to validate
306 /// * `constraints` - The FHIRPath constraints to evaluate
307 ///
308 /// # Returns
309 /// Validation result with any constraint violations
310 async fn validate_constraints(
311 &self,
312 resource: &JsonValue,
313 constraints: &[FhirPathConstraint],
314 ) -> Result<ValidationResult>;
315
316 /// Evaluate compiled expression
317 ///
318 /// Evaluates a pre-compiled expression for better performance.
319 ///
320 /// # Arguments
321 /// * `compiled` - The compiled expression
322 /// * `context` - The JSON context for evaluation
323 ///
324 /// # Returns
325 /// The evaluation result or an error
326 async fn evaluate_compiled(
327 &self,
328 compiled: &CompiledExpression,
329 context: &JsonValue,
330 ) -> Result<EvaluationResult> {
331 // Default implementation falls back to regular evaluation
332 self.evaluate(&compiled.expression, context).await
333 }
334
335 /// Evaluate compiled expression with variables
336 ///
337 /// Evaluates a pre-compiled expression with variables for better performance.
338 ///
339 /// # Arguments
340 /// * `compiled` - The compiled expression
341 /// * `context` - The JSON context for evaluation
342 /// * `variables` - Additional variables available during evaluation
343 ///
344 /// # Returns
345 /// The evaluation result or an error
346 async fn evaluate_compiled_with_variables(
347 &self,
348 compiled: &CompiledExpression,
349 context: &JsonValue,
350 variables: &Variables,
351 ) -> Result<EvaluationResult> {
352 // Default implementation falls back to regular evaluation
353 self.evaluate_with_variables(&compiled.expression, context, variables)
354 .await
355 }
356
357 /// Check if the evaluator supports a specific feature
358 ///
359 /// Allows callers to check for optional features before using them.
360 ///
361 /// # Arguments
362 /// * `feature` - The feature name to check
363 ///
364 /// # Returns
365 /// True if the feature is supported
366 fn supports_feature(&self, feature: &str) -> bool {
367 // Default implementation - override in concrete evaluators
368 matches!(feature, "compilation" | "variables" | "constraints")
369 }
370}
371
372/// Factory trait for creating FHIRPath evaluators
373///
374/// Enables creation of evaluators with dependency injection.
375#[async_trait]
376pub trait FhirPathEvaluatorFactory {
377 /// Create a new evaluator with the given ModelProvider
378 ///
379 /// # Arguments
380 /// * `model_provider` - The ModelProvider to inject for type information
381 ///
382 /// # Returns
383 /// A new evaluator instance
384 async fn create_evaluator(
385 &self,
386 model_provider: Arc<dyn ModelProvider>,
387 ) -> Result<Arc<dyn FhirPathEvaluator>>;
388}
389
390/// Minimal validation interface - wraps existing ModelProvider
391///
392/// This trait provides a simple validation interface without creating
393/// circular dependencies between ModelProvider and FhirPathEvaluator.
394/// It leverages existing ModelProvider functionality for maximum reuse.
395#[async_trait]
396pub trait ValidationProvider: Send + Sync {
397 /// Check if a resource conforms to a profile
398 ///
399 /// This method checks if a resource conforms to the given profile URL
400 /// by validating constraints and structural requirements.
401 ///
402 /// # Arguments
403 /// * `resource` - The FHIR resource to validate
404 /// * `profile_url` - The canonical URL of the profile to validate against
405 ///
406 /// # Returns
407 /// True if the resource conforms to the profile
408 async fn validate(&self, resource: &JsonValue, profile_url: &str) -> Result<bool>;
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_compiled_expression_creation() {
417 let expr = CompiledExpression::new(
418 "Patient.name".to_string(),
419 "compiled_form".to_string(),
420 true,
421 );
422 assert!(expr.is_valid);
423 assert_eq!(expr.expression, "Patient.name");
424 }
425
426 #[test]
427 fn test_validation_result() {
428 let result = ValidationResult::success();
429 assert!(result.is_valid);
430 assert!(result.errors.is_empty());
431
432 let error = ValidationError::new("Test error".to_string());
433 let result = ValidationResult::with_errors(vec![error]);
434 assert!(!result.is_valid);
435 assert_eq!(result.errors.len(), 1);
436 }
437
438 #[test]
439 fn test_constraint_creation() {
440 let constraint = FhirPathConstraint::new(
441 "test-1".to_string(),
442 "Test constraint".to_string(),
443 "Patient.name.exists()".to_string(),
444 );
445 assert!(constraint.required);
446 assert_eq!(constraint.severity, ErrorSeverity::Error);
447
448 let optional_constraint = constraint.optional().with_severity(ErrorSeverity::Warning);
449 assert!(!optional_constraint.required);
450 assert_eq!(optional_constraint.severity, ErrorSeverity::Warning);
451 }
452}