octofhir_fhirpath_core/
error.rs

1// Copyright 2024 OctoFHIR Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Error types for FHIRPath evaluation
16//!
17//! This module defines the error types used throughout the FHIRPath engine.
18
19use thiserror::Error;
20
21/// Result type alias for FHIRPath operations
22pub type Result<T> = std::result::Result<T, FhirPathError>;
23
24/// Source location information for error reporting
25#[derive(Debug, Clone, PartialEq)]
26pub struct SourceLocation {
27    /// Line number (1-based)
28    pub line: usize,
29    /// Column number (1-based)
30    pub column: usize,
31    /// Character position in the source (0-based)
32    pub position: usize,
33}
34
35impl SourceLocation {
36    /// Create a new source location
37    pub fn new(line: usize, column: usize, position: usize) -> Self {
38        Self {
39            line,
40            column,
41            position,
42        }
43    }
44}
45
46/// Comprehensive error type for FHIRPath operations
47#[derive(Error, Debug, Clone, PartialEq)]
48pub enum FhirPathError {
49    /// Parsing errors
50    #[error("Parse error at position {position}: {message}")]
51    ParseError {
52        /// Position in the input where the parse error occurred
53        position: usize,
54        /// Human-readable error message
55        message: String,
56    },
57
58    /// Type errors during evaluation
59    #[error("Type error: {message}")]
60    TypeError {
61        /// Human-readable type error message
62        message: String,
63    },
64
65    /// Runtime evaluation errors
66    #[error("Evaluation error: {message}{}{}", 
67        expression.as_ref().map(|e| format!(" in expression: {e}")).unwrap_or_default(),
68        location.as_ref().map(|l| format!(" at line {}, column {}", l.line, l.column)).unwrap_or_default()
69    )]
70    EvaluationError {
71        /// Human-readable evaluation error message
72        message: String,
73        /// Expression being evaluated when error occurred
74        expression: Option<String>,
75        /// Source location where error occurred
76        location: Option<SourceLocation>,
77    },
78
79    /// Function call errors
80    #[error("Function '{function_name}' error: {message}{}", arguments.as_ref().map(|args| format!(" with arguments: {}", args.join(", "))).unwrap_or_default())]
81    FunctionError {
82        /// Name of the function that caused the error
83        function_name: String,
84        /// Human-readable error message
85        message: String,
86        /// Arguments passed to the function
87        arguments: Option<Vec<String>>,
88    },
89
90    /// Invalid expression structure
91    #[error("Invalid expression: {message}")]
92    InvalidExpression {
93        /// Human-readable invalid expression error message
94        message: String,
95    },
96
97    /// Division by zero or other arithmetic errors
98    #[error("Arithmetic error: {message}")]
99    ArithmeticError {
100        /// Human-readable arithmetic error message
101        message: String,
102    },
103
104    /// Index out of bounds
105    #[error("Index out of bounds: {index} for collection of size {size}")]
106    IndexOutOfBounds {
107        /// The index that was out of bounds
108        index: i64,
109        /// The size of the collection
110        size: usize,
111    },
112
113    /// Unknown function
114    #[error("Unknown function: {function_name}")]
115    UnknownFunction {
116        /// Name of the unknown function
117        function_name: String,
118    },
119
120    /// Invalid argument count
121    #[error("Function '{function_name}' expects {expected} arguments, got {actual}")]
122    InvalidArgumentCount {
123        /// Name of the function with invalid argument count
124        function_name: String,
125        /// Expected number of arguments
126        expected: usize,
127        /// Actual number of arguments received
128        actual: usize,
129    },
130
131    /// Conversion errors
132    #[error("Conversion error: cannot convert {from} to {to}")]
133    ConversionError {
134        /// Source type that could not be converted
135        from: String,
136        /// Target type for the conversion
137        to: String,
138    },
139
140    /// Generic error for compatibility
141    #[error("FHIRPath error: {message}")]
142    Generic {
143        /// Generic error message
144        message: String,
145    },
146
147    /// Invalid operation for given types or context
148    #[error("Invalid operation '{operation}': {message}{}", 
149        match (left_type.as_ref(), right_type.as_ref()) {
150            (Some(left), Some(right)) => format!(" (left: {left}, right: {right})"),
151            _ => String::new()
152        }
153    )]
154    InvalidOperation {
155        /// The operation that was invalid
156        operation: String,
157        /// Type of the left operand (if applicable)
158        left_type: Option<String>,
159        /// Type of the right operand (if applicable)  
160        right_type: Option<String>,
161        /// Human-readable error message
162        message: String,
163    },
164
165    /// Type mismatch error with context
166    #[error("Type mismatch: expected {expected}, got {actual}{}", context.as_ref().map(|c| format!(" in {c}")).unwrap_or_default())]
167    TypeMismatch {
168        /// Expected type
169        expected: String,
170        /// Actual type received
171        actual: String,
172        /// Additional context about where the mismatch occurred
173        context: Option<String>,
174    },
175
176    /// Timeout error during evaluation
177    #[error("Evaluation timeout after {timeout_ms}ms{}", expression.as_ref().map(|e| format!(" in expression: {e}")).unwrap_or_default())]
178    TimeoutError {
179        /// Timeout duration in milliseconds
180        timeout_ms: u64,
181        /// Expression being evaluated when timeout occurred
182        expression: Option<String>,
183    },
184
185    /// Recursion limit exceeded
186    #[error("Recursion limit of {limit} exceeded{}", expression.as_ref().map(|e| format!(" in expression: {e}")).unwrap_or_default())]
187    RecursionLimitExceeded {
188        /// The recursion limit that was exceeded
189        limit: usize,
190        /// Expression being evaluated when limit was exceeded
191        expression: Option<String>,
192    },
193
194    /// Memory limit exceeded
195    #[error("Memory limit of {limit_mb}MB exceeded (current: {current_mb}MB)")]
196    MemoryLimitExceeded {
197        /// Memory limit in megabytes
198        limit_mb: usize,
199        /// Current memory usage in megabytes
200        current_mb: usize,
201    },
202
203    /// Invalid function arguments
204    #[error("Invalid arguments: {message}")]
205    InvalidArguments {
206        /// Human-readable error message
207        message: String,
208    },
209
210    /// Unknown operator
211    #[error("Unknown operator: '{operator}'")]
212    UnknownOperator {
213        /// The unknown operator
214        operator: String,
215    },
216
217    /// Invalid operand types for operator
218    #[error("Invalid operand types for operator '{operator}': {left_type} and {right_type}")]
219    InvalidOperandTypes {
220        /// The operator with invalid operand types
221        operator: String,
222        /// Type of the left operand
223        left_type: String,
224        /// Type of the right operand
225        right_type: String,
226    },
227
228    /// Incompatible units
229    #[error("Incompatible units: '{left_unit}' and '{right_unit}'")]
230    IncompatibleUnits {
231        /// Unit of the left operand
232        left_unit: String,
233        /// Unit of the right operand
234        right_unit: String,
235    },
236
237    /// Division by zero
238    #[error("Division by zero")]
239    DivisionByZero,
240
241    /// Arithmetic overflow
242    #[error("Arithmetic overflow in {operation}")]
243    ArithmeticOverflow {
244        /// The operation that caused the overflow
245        operation: String,
246    },
247
248    /// Invalid type specifier
249    #[error("Invalid type specifier")]
250    InvalidTypeSpecifier,
251
252    /// Invalid function arity
253    #[error("Function '{name}' expects {min_arity}{} arguments, got {actual}",
254            max_arity.map(|m| format!("-{m}")).unwrap_or_else(|| String::from(" or more")))]
255    InvalidArity {
256        /// Name of the function with invalid arity
257        name: String,
258        /// Minimum number of arguments required
259        min_arity: usize,
260        /// Maximum number of arguments allowed (None for unlimited)
261        max_arity: Option<usize>,
262        /// Actual number of arguments provided
263        actual: usize,
264    },
265}
266
267impl FhirPathError {
268    /// Create a parse error
269    pub fn parse_error(position: usize, message: impl Into<String>) -> Self {
270        Self::ParseError {
271            position,
272            message: message.into(),
273        }
274    }
275
276    /// Create a type error
277    pub fn type_error(message: impl Into<String>) -> Self {
278        Self::TypeError {
279            message: message.into(),
280        }
281    }
282
283    /// Create an evaluation error
284    pub fn evaluation_error(message: impl Into<String>) -> Self {
285        Self::EvaluationError {
286            message: message.into(),
287            expression: None,
288            location: None,
289        }
290    }
291
292    /// Create an evaluation error with expression context
293    pub fn evaluation_error_with_context(
294        message: impl Into<String>,
295        expression: Option<String>,
296        location: Option<SourceLocation>,
297    ) -> Self {
298        Self::EvaluationError {
299            message: message.into(),
300            expression,
301            location,
302        }
303    }
304
305    /// Create a function error
306    pub fn function_error(function_name: impl Into<String>, message: impl Into<String>) -> Self {
307        Self::FunctionError {
308            function_name: function_name.into(),
309            message: message.into(),
310            arguments: None,
311        }
312    }
313
314    /// Create a function error with arguments context
315    pub fn function_error_with_args(
316        function_name: impl Into<String>,
317        message: impl Into<String>,
318        arguments: Option<Vec<String>>,
319    ) -> Self {
320        Self::FunctionError {
321            function_name: function_name.into(),
322            message: message.into(),
323            arguments,
324        }
325    }
326
327    /// Create an invalid expression error
328    pub fn invalid_expression(message: impl Into<String>) -> Self {
329        Self::InvalidExpression {
330            message: message.into(),
331        }
332    }
333
334    /// Create an arithmetic error
335    pub fn arithmetic_error(message: impl Into<String>) -> Self {
336        Self::ArithmeticError {
337            message: message.into(),
338        }
339    }
340
341    /// Create an index out of bounds error
342    pub fn index_out_of_bounds(index: i64, size: usize) -> Self {
343        Self::IndexOutOfBounds { index, size }
344    }
345
346    /// Create an unknown function error
347    pub fn unknown_function(function_name: impl Into<String>) -> Self {
348        Self::UnknownFunction {
349            function_name: function_name.into(),
350        }
351    }
352
353    /// Create an invalid argument count error
354    pub fn invalid_argument_count(
355        function_name: impl Into<String>,
356        expected: usize,
357        actual: usize,
358    ) -> Self {
359        Self::InvalidArgumentCount {
360            function_name: function_name.into(),
361            expected,
362            actual,
363        }
364    }
365
366    /// Create a conversion error
367    pub fn conversion_error(from: impl Into<String>, to: impl Into<String>) -> Self {
368        Self::ConversionError {
369            from: from.into(),
370            to: to.into(),
371        }
372    }
373
374    /// Create a generic error
375    pub fn generic(message: impl Into<String>) -> Self {
376        Self::Generic {
377            message: message.into(),
378        }
379    }
380
381    /// Create an unknown operator error
382    pub fn unknown_operator(operator: impl Into<String>) -> Self {
383        Self::UnknownOperator {
384            operator: operator.into(),
385        }
386    }
387
388    /// Create an invalid operand types error
389    pub fn invalid_operand_types(
390        operator: impl Into<String>,
391        left_type: impl Into<String>,
392        right_type: impl Into<String>,
393    ) -> Self {
394        Self::InvalidOperandTypes {
395            operator: operator.into(),
396            left_type: left_type.into(),
397            right_type: right_type.into(),
398        }
399    }
400
401    /// Create an incompatible units error
402    pub fn incompatible_units(left_unit: impl Into<String>, right_unit: impl Into<String>) -> Self {
403        Self::IncompatibleUnits {
404            left_unit: left_unit.into(),
405            right_unit: right_unit.into(),
406        }
407    }
408
409    /// Create a division by zero error
410    pub fn division_by_zero() -> Self {
411        Self::DivisionByZero
412    }
413
414    /// Create an arithmetic overflow error
415    pub fn arithmetic_overflow(operation: impl Into<String>) -> Self {
416        Self::ArithmeticOverflow {
417            operation: operation.into(),
418        }
419    }
420
421    /// Create an invalid type specifier error
422    pub fn invalid_type_specifier() -> Self {
423        Self::InvalidTypeSpecifier
424    }
425
426    /// Create an invalid arity error
427    pub fn invalid_arity(
428        name: impl Into<String>,
429        min_arity: usize,
430        max_arity: Option<usize>,
431        actual: usize,
432    ) -> Self {
433        Self::InvalidArity {
434            name: name.into(),
435            min_arity,
436            max_arity,
437            actual,
438        }
439    }
440
441    /// Create an invalid operation error
442    pub fn invalid_operation(operation: impl Into<String>, message: impl Into<String>) -> Self {
443        Self::InvalidOperation {
444            operation: operation.into(),
445            left_type: None,
446            right_type: None,
447            message: message.into(),
448        }
449    }
450
451    /// Create an invalid operation error with type context
452    pub fn invalid_operation_with_types(
453        operation: impl Into<String>,
454        left_type: Option<String>,
455        right_type: Option<String>,
456        message: impl Into<String>,
457    ) -> Self {
458        Self::InvalidOperation {
459            operation: operation.into(),
460            left_type,
461            right_type,
462            message: message.into(),
463        }
464    }
465
466    /// Create a type mismatch error
467    pub fn type_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
468        Self::TypeMismatch {
469            expected: expected.into(),
470            actual: actual.into(),
471            context: None,
472        }
473    }
474
475    /// Create a type mismatch error with context
476    pub fn type_mismatch_with_context(
477        expected: impl Into<String>,
478        actual: impl Into<String>,
479        context: impl Into<String>,
480    ) -> Self {
481        Self::TypeMismatch {
482            expected: expected.into(),
483            actual: actual.into(),
484            context: Some(context.into()),
485        }
486    }
487
488    /// Create a timeout error
489    pub fn timeout_error(timeout_ms: u64) -> Self {
490        Self::TimeoutError {
491            timeout_ms,
492            expression: None,
493        }
494    }
495
496    /// Create a timeout error with expression context
497    pub fn timeout_error_with_expression(timeout_ms: u64, expression: impl Into<String>) -> Self {
498        Self::TimeoutError {
499            timeout_ms,
500            expression: Some(expression.into()),
501        }
502    }
503
504    /// Create a recursion limit exceeded error
505    pub fn recursion_limit_exceeded(limit: usize) -> Self {
506        Self::RecursionLimitExceeded {
507            limit,
508            expression: None,
509        }
510    }
511
512    /// Create a recursion limit exceeded error with expression context
513    pub fn recursion_limit_exceeded_with_expression(
514        limit: usize,
515        expression: impl Into<String>,
516    ) -> Self {
517        Self::RecursionLimitExceeded {
518            limit,
519            expression: Some(expression.into()),
520        }
521    }
522
523    /// Create a memory limit exceeded error
524    pub fn memory_limit_exceeded(limit_mb: usize, current_mb: usize) -> Self {
525        Self::MemoryLimitExceeded {
526            limit_mb,
527            current_mb,
528        }
529    }
530
531    /// Add context to an existing error
532    pub fn with_context(mut self, context: &str) -> Self {
533        match &mut self {
534            Self::EvaluationError { message, .. } => {
535                *message = format!("{message} (context: {context})");
536            }
537            Self::TypeMismatch { context: ctx, .. } => {
538                *ctx = Some(context.to_string());
539            }
540            Self::FunctionError { message, .. } => {
541                *message = format!("{message} (context: {context})");
542            }
543            Self::InvalidOperation { message, .. } => {
544                *message = format!("{message} (context: {context})");
545            }
546            Self::TypeError { message } => {
547                *message = format!("{message} (context: {context})");
548            }
549            Self::ArithmeticError { message } => {
550                *message = format!("{message} (context: {context})");
551            }
552            _ => {}
553        }
554        self
555    }
556
557    /// Add expression context to an error
558    pub fn with_expression(mut self, expression: &str) -> Self {
559        match &mut self {
560            Self::EvaluationError {
561                expression: expr, ..
562            } => {
563                *expr = Some(expression.to_string());
564            }
565            Self::TimeoutError {
566                expression: expr, ..
567            } => {
568                *expr = Some(expression.to_string());
569            }
570            Self::RecursionLimitExceeded {
571                expression: expr, ..
572            } => {
573                *expr = Some(expression.to_string());
574            }
575            _ => {}
576        }
577        self
578    }
579
580    /// Add location context to an error
581    pub fn with_location(mut self, location: SourceLocation) -> Self {
582        if let Self::EvaluationError { location: loc, .. } = &mut self {
583            *loc = Some(location);
584        }
585        self
586    }
587}
588
589/// Convert from `Box<dyn std::error::Error>` for compatibility with tests
590impl From<Box<dyn std::error::Error>> for FhirPathError {
591    fn from(err: Box<dyn std::error::Error>) -> Self {
592        Self::Generic {
593            message: err.to_string(),
594        }
595    }
596}
597
598// Note: From<FhirPathError> for Box<dyn std::error::Error> is automatically provided by Rust
599// since FhirPathError implements std::error::Error via thiserror
600
601/// Helper trait for adding context to errors
602pub trait ErrorContext<T> {
603    /// Add function context to an error
604    fn with_function_context(self, function_name: &str) -> Result<T>;
605    /// Add operation context to an error
606    fn with_operation_context(self, operation: &str) -> Result<T>;
607    /// Add expression context to an error
608    fn with_expression_context(self, expression: &str) -> Result<T>;
609    /// Add location context to an error
610    fn with_location_context(self, location: SourceLocation) -> Result<T>;
611}
612
613impl<T> ErrorContext<T> for Result<T> {
614    fn with_function_context(self, function_name: &str) -> Result<T> {
615        self.map_err(|e| e.with_context(&format!("function {function_name}")))
616    }
617
618    fn with_operation_context(self, operation: &str) -> Result<T> {
619        self.map_err(|e| e.with_context(&format!("operation {operation}")))
620    }
621
622    fn with_expression_context(self, expression: &str) -> Result<T> {
623        self.map_err(|e| e.with_expression(expression))
624    }
625
626    fn with_location_context(self, location: SourceLocation) -> Result<T> {
627        self.map_err(|e| e.with_location(location))
628    }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::*;
634
635    #[test]
636    fn test_source_location_creation() {
637        let loc = SourceLocation::new(10, 5, 42);
638        assert_eq!(loc.line, 10);
639        assert_eq!(loc.column, 5);
640        assert_eq!(loc.position, 42);
641    }
642
643    #[test]
644    fn test_basic_error_constructors() {
645        // Test parse error
646        let parse_err = FhirPathError::parse_error(5, "Unexpected token");
647        assert!(matches!(
648            parse_err,
649            FhirPathError::ParseError { position: 5, .. }
650        ));
651
652        // Test type error
653        let type_err = FhirPathError::type_error("Type mismatch");
654        assert!(matches!(type_err, FhirPathError::TypeError { .. }));
655
656        // Test evaluation error
657        let eval_err = FhirPathError::evaluation_error("Evaluation failed");
658        assert!(matches!(eval_err, FhirPathError::EvaluationError { .. }));
659
660        // Test function error
661        let func_err = FhirPathError::function_error("count", "Invalid call");
662        assert!(matches!(func_err, FhirPathError::FunctionError { .. }));
663    }
664
665    #[test]
666    fn test_enhanced_error_constructors() {
667        // Test invalid operation
668        let invalid_op = FhirPathError::invalid_operation("division", "Division by zero");
669        assert!(matches!(invalid_op, FhirPathError::InvalidOperation { .. }));
670
671        // Test invalid operation with types
672        let invalid_op_types = FhirPathError::invalid_operation_with_types(
673            "addition",
674            Some("String".to_string()),
675            Some("Integer".to_string()),
676            "Cannot add string and integer",
677        );
678        assert!(matches!(
679            invalid_op_types,
680            FhirPathError::InvalidOperation {
681                left_type: Some(_),
682                right_type: Some(_),
683                ..
684            }
685        ));
686
687        // Test type mismatch
688        let type_mismatch = FhirPathError::type_mismatch("Integer", "String");
689        assert!(matches!(type_mismatch, FhirPathError::TypeMismatch { .. }));
690
691        // Test type mismatch with context
692        let type_mismatch_ctx =
693            FhirPathError::type_mismatch_with_context("Integer", "String", "arithmetic operation");
694        assert!(matches!(
695            type_mismatch_ctx,
696            FhirPathError::TypeMismatch {
697                context: Some(_),
698                ..
699            }
700        ));
701
702        // Test timeout error
703        let timeout_err = FhirPathError::timeout_error(5000);
704        assert!(matches!(
705            timeout_err,
706            FhirPathError::TimeoutError {
707                timeout_ms: 5000,
708                ..
709            }
710        ));
711
712        // Test recursion limit exceeded
713        let recursion_err = FhirPathError::recursion_limit_exceeded(100);
714        assert!(matches!(
715            recursion_err,
716            FhirPathError::RecursionLimitExceeded { limit: 100, .. }
717        ));
718
719        // Test memory limit exceeded
720        let memory_err = FhirPathError::memory_limit_exceeded(512, 600);
721        assert!(matches!(
722            memory_err,
723            FhirPathError::MemoryLimitExceeded {
724                limit_mb: 512,
725                current_mb: 600
726            }
727        ));
728    }
729
730    #[test]
731    fn test_error_context_helpers() {
732        let err = FhirPathError::evaluation_error("Test error");
733
734        // Test with_context
735        let err_with_ctx = err.clone().with_context("test context");
736        if let FhirPathError::EvaluationError { message, .. } = err_with_ctx {
737            assert!(message.contains("(context: test context)"));
738        } else {
739            panic!("Expected EvaluationError");
740        }
741
742        // Test with_expression
743        let err_with_expr = err.clone().with_expression("Patient.name");
744        if let FhirPathError::EvaluationError { expression, .. } = err_with_expr {
745            assert_eq!(expression, Some("Patient.name".to_string()));
746        } else {
747            panic!("Expected EvaluationError");
748        }
749
750        // Test with_location
751        let location = SourceLocation::new(5, 10, 42);
752        let err_with_loc = err.with_location(location.clone());
753        if let FhirPathError::EvaluationError { location: loc, .. } = err_with_loc {
754            assert_eq!(loc, Some(location));
755        } else {
756            panic!("Expected EvaluationError");
757        }
758    }
759
760    #[test]
761    fn test_error_context_trait() {
762        let ok_result: Result<i32> = Ok(42);
763        let err_result: Result<i32> = Err(FhirPathError::evaluation_error("Test error"));
764
765        // Test function context
766        let with_func_ctx = err_result.clone().with_function_context("count");
767        assert!(with_func_ctx.is_err());
768        if let Err(FhirPathError::EvaluationError { message, .. }) = with_func_ctx {
769            assert!(message.contains("(context: function count)"));
770        }
771
772        // Test operation context
773        let with_op_ctx = err_result.clone().with_operation_context("division");
774        assert!(with_op_ctx.is_err());
775        if let Err(FhirPathError::EvaluationError { message, .. }) = with_op_ctx {
776            assert!(message.contains("(context: operation division)"));
777        }
778
779        // Test expression context
780        let with_expr_ctx = err_result.clone().with_expression_context("Patient.name");
781        assert!(with_expr_ctx.is_err());
782        if let Err(FhirPathError::EvaluationError { expression, .. }) = with_expr_ctx {
783            assert_eq!(expression, Some("Patient.name".to_string()));
784        }
785
786        // Test location context
787        let location = SourceLocation::new(1, 1, 0);
788        let with_loc_ctx = err_result.with_location_context(location.clone());
789        assert!(with_loc_ctx.is_err());
790        if let Err(FhirPathError::EvaluationError { location: loc, .. }) = with_loc_ctx {
791            assert_eq!(loc, Some(location));
792        }
793
794        // Test that Ok results pass through unchanged
795        let unchanged = ok_result.with_function_context("test");
796        assert_eq!(unchanged.unwrap(), 42);
797    }
798
799    #[test]
800    fn test_error_display() {
801        // Test InvalidOperation display
802        let invalid_op = FhirPathError::invalid_operation_with_types(
803            "division",
804            Some("String".to_string()),
805            Some("Integer".to_string()),
806            "Cannot divide string by integer",
807        );
808        let display = format!("{invalid_op}");
809        assert!(display.contains("Invalid operation 'division'"));
810        assert!(display.contains("Cannot divide string by integer"));
811
812        // Test TypeMismatch display
813        let type_mismatch =
814            FhirPathError::type_mismatch_with_context("Integer", "String", "arithmetic operation");
815        let display = format!("{type_mismatch}");
816        assert!(display.contains("Type mismatch: expected Integer, got String"));
817        assert!(display.contains("in arithmetic operation"));
818
819        // Test TimeoutError display
820        let timeout = FhirPathError::timeout_error_with_expression(5000, "Patient.name.where(...)");
821        let display = format!("{timeout}");
822        assert!(display.contains("Evaluation timeout after 5000ms"));
823        assert!(display.contains("in expression: Patient.name.where(...)"));
824
825        // Test RecursionLimitExceeded display
826        let recursion = FhirPathError::recursion_limit_exceeded_with_expression(100, "recursive()");
827        let display = format!("{recursion}");
828        assert!(display.contains("Recursion limit of 100 exceeded"));
829        assert!(display.contains("in expression: recursive()"));
830
831        // Test MemoryLimitExceeded display
832        let memory = FhirPathError::memory_limit_exceeded(512, 600);
833        let display = format!("{memory}");
834        assert!(display.contains("Memory limit of 512MB exceeded (current: 600MB)"));
835    }
836
837    #[test]
838    fn test_function_error_with_arguments() {
839        let args = vec!["arg1".to_string(), "arg2".to_string()];
840        let func_err = FhirPathError::function_error_with_args(
841            "count",
842            "Invalid arguments",
843            Some(args.clone()),
844        );
845
846        if let FhirPathError::FunctionError { arguments, .. } = func_err {
847            assert_eq!(arguments, Some(args));
848        } else {
849            panic!("Expected FunctionError");
850        }
851    }
852
853    #[test]
854    fn test_evaluation_error_with_context() {
855        let location = SourceLocation::new(10, 5, 42);
856        let eval_err = FhirPathError::evaluation_error_with_context(
857            "Evaluation failed",
858            Some("Patient.name".to_string()),
859            Some(location.clone()),
860        );
861
862        if let FhirPathError::EvaluationError {
863            expression,
864            location: loc,
865            ..
866        } = eval_err
867        {
868            assert_eq!(expression, Some("Patient.name".to_string()));
869            assert_eq!(loc, Some(location));
870        } else {
871            panic!("Expected EvaluationError");
872        }
873    }
874
875    #[test]
876    fn test_legacy_error_constructors_still_work() {
877        // Ensure backward compatibility
878        let division_by_zero = FhirPathError::division_by_zero();
879        assert!(matches!(division_by_zero, FhirPathError::DivisionByZero));
880
881        let overflow = FhirPathError::arithmetic_overflow("multiplication");
882        assert!(matches!(overflow, FhirPathError::ArithmeticOverflow { .. }));
883
884        let unknown_func = FhirPathError::unknown_function("unknownFunction");
885        assert!(matches!(
886            unknown_func,
887            FhirPathError::UnknownFunction { .. }
888        ));
889
890        let invalid_args = FhirPathError::invalid_argument_count("count", 1, 2);
891        assert!(matches!(
892            invalid_args,
893            FhirPathError::InvalidArgumentCount { .. }
894        ));
895    }
896}