1use thiserror::Error;
20
21pub type Result<T> = std::result::Result<T, FhirPathError>;
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct SourceLocation {
27 pub line: usize,
29 pub column: usize,
31 pub position: usize,
33}
34
35impl SourceLocation {
36 pub fn new(line: usize, column: usize, position: usize) -> Self {
38 Self {
39 line,
40 column,
41 position,
42 }
43 }
44}
45
46#[derive(Error, Debug, Clone, PartialEq)]
48pub enum FhirPathError {
49 #[error("Parse error at position {position}: {message}")]
51 ParseError {
52 position: usize,
54 message: String,
56 },
57
58 #[error("Type error: {message}")]
60 TypeError {
61 message: String,
63 },
64
65 #[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 message: String,
73 expression: Option<String>,
75 location: Option<SourceLocation>,
77 },
78
79 #[error("Function '{function_name}' error: {message}{}", arguments.as_ref().map(|args| format!(" with arguments: {}", args.join(", "))).unwrap_or_default())]
81 FunctionError {
82 function_name: String,
84 message: String,
86 arguments: Option<Vec<String>>,
88 },
89
90 #[error("Invalid expression: {message}")]
92 InvalidExpression {
93 message: String,
95 },
96
97 #[error("Arithmetic error: {message}")]
99 ArithmeticError {
100 message: String,
102 },
103
104 #[error("Index out of bounds: {index} for collection of size {size}")]
106 IndexOutOfBounds {
107 index: i64,
109 size: usize,
111 },
112
113 #[error("Unknown function: {function_name}")]
115 UnknownFunction {
116 function_name: String,
118 },
119
120 #[error("Function '{function_name}' expects {expected} arguments, got {actual}")]
122 InvalidArgumentCount {
123 function_name: String,
125 expected: usize,
127 actual: usize,
129 },
130
131 #[error("Conversion error: cannot convert {from} to {to}")]
133 ConversionError {
134 from: String,
136 to: String,
138 },
139
140 #[error("FHIRPath error: {message}")]
142 Generic {
143 message: String,
145 },
146
147 #[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 operation: String,
157 left_type: Option<String>,
159 right_type: Option<String>,
161 message: String,
163 },
164
165 #[error("Type mismatch: expected {expected}, got {actual}{}", context.as_ref().map(|c| format!(" in {c}")).unwrap_or_default())]
167 TypeMismatch {
168 expected: String,
170 actual: String,
172 context: Option<String>,
174 },
175
176 #[error("Evaluation timeout after {timeout_ms}ms{}", expression.as_ref().map(|e| format!(" in expression: {e}")).unwrap_or_default())]
178 TimeoutError {
179 timeout_ms: u64,
181 expression: Option<String>,
183 },
184
185 #[error("Recursion limit of {limit} exceeded{}", expression.as_ref().map(|e| format!(" in expression: {e}")).unwrap_or_default())]
187 RecursionLimitExceeded {
188 limit: usize,
190 expression: Option<String>,
192 },
193
194 #[error("Memory limit of {limit_mb}MB exceeded (current: {current_mb}MB)")]
196 MemoryLimitExceeded {
197 limit_mb: usize,
199 current_mb: usize,
201 },
202
203 #[error("Invalid arguments: {message}")]
205 InvalidArguments {
206 message: String,
208 },
209
210 #[error("Unknown operator: '{operator}'")]
212 UnknownOperator {
213 operator: String,
215 },
216
217 #[error("Invalid operand types for operator '{operator}': {left_type} and {right_type}")]
219 InvalidOperandTypes {
220 operator: String,
222 left_type: String,
224 right_type: String,
226 },
227
228 #[error("Incompatible units: '{left_unit}' and '{right_unit}'")]
230 IncompatibleUnits {
231 left_unit: String,
233 right_unit: String,
235 },
236
237 #[error("Division by zero")]
239 DivisionByZero,
240
241 #[error("Arithmetic overflow in {operation}")]
243 ArithmeticOverflow {
244 operation: String,
246 },
247
248 #[error("Invalid type specifier")]
250 InvalidTypeSpecifier,
251
252 #[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: String,
258 min_arity: usize,
260 max_arity: Option<usize>,
262 actual: usize,
264 },
265}
266
267impl FhirPathError {
268 pub fn parse_error(position: usize, message: impl Into<String>) -> Self {
270 Self::ParseError {
271 position,
272 message: message.into(),
273 }
274 }
275
276 pub fn type_error(message: impl Into<String>) -> Self {
278 Self::TypeError {
279 message: message.into(),
280 }
281 }
282
283 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 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 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 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 pub fn invalid_expression(message: impl Into<String>) -> Self {
329 Self::InvalidExpression {
330 message: message.into(),
331 }
332 }
333
334 pub fn arithmetic_error(message: impl Into<String>) -> Self {
336 Self::ArithmeticError {
337 message: message.into(),
338 }
339 }
340
341 pub fn index_out_of_bounds(index: i64, size: usize) -> Self {
343 Self::IndexOutOfBounds { index, size }
344 }
345
346 pub fn unknown_function(function_name: impl Into<String>) -> Self {
348 Self::UnknownFunction {
349 function_name: function_name.into(),
350 }
351 }
352
353 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 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 pub fn generic(message: impl Into<String>) -> Self {
376 Self::Generic {
377 message: message.into(),
378 }
379 }
380
381 pub fn unknown_operator(operator: impl Into<String>) -> Self {
383 Self::UnknownOperator {
384 operator: operator.into(),
385 }
386 }
387
388 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 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 pub fn division_by_zero() -> Self {
411 Self::DivisionByZero
412 }
413
414 pub fn arithmetic_overflow(operation: impl Into<String>) -> Self {
416 Self::ArithmeticOverflow {
417 operation: operation.into(),
418 }
419 }
420
421 pub fn invalid_type_specifier() -> Self {
423 Self::InvalidTypeSpecifier
424 }
425
426 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 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 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 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 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 pub fn timeout_error(timeout_ms: u64) -> Self {
490 Self::TimeoutError {
491 timeout_ms,
492 expression: None,
493 }
494 }
495
496 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 pub fn recursion_limit_exceeded(limit: usize) -> Self {
506 Self::RecursionLimitExceeded {
507 limit,
508 expression: None,
509 }
510 }
511
512 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 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 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 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 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
589impl 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
598pub trait ErrorContext<T> {
603 fn with_function_context(self, function_name: &str) -> Result<T>;
605 fn with_operation_context(self, operation: &str) -> Result<T>;
607 fn with_expression_context(self, expression: &str) -> Result<T>;
609 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 let parse_err = FhirPathError::parse_error(5, "Unexpected token");
647 assert!(matches!(
648 parse_err,
649 FhirPathError::ParseError { position: 5, .. }
650 ));
651
652 let type_err = FhirPathError::type_error("Type mismatch");
654 assert!(matches!(type_err, FhirPathError::TypeError { .. }));
655
656 let eval_err = FhirPathError::evaluation_error("Evaluation failed");
658 assert!(matches!(eval_err, FhirPathError::EvaluationError { .. }));
659
660 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 let invalid_op = FhirPathError::invalid_operation("division", "Division by zero");
669 assert!(matches!(invalid_op, FhirPathError::InvalidOperation { .. }));
670
671 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 let type_mismatch = FhirPathError::type_mismatch("Integer", "String");
689 assert!(matches!(type_mismatch, FhirPathError::TypeMismatch { .. }));
690
691 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 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 let recursion_err = FhirPathError::recursion_limit_exceeded(100);
714 assert!(matches!(
715 recursion_err,
716 FhirPathError::RecursionLimitExceeded { limit: 100, .. }
717 ));
718
719 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 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 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 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 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 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 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 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 let unchanged = ok_result.with_function_context("test");
796 assert_eq!(unchanged.unwrap(), 42);
797 }
798
799 #[test]
800 fn test_error_display() {
801 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 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 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 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 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 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}