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}