Skip to main content

camel_language_api/
error.rs

1use camel_api::{ErrorPosition, ExpressionErrorClass};
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5#[non_exhaustive]
6pub enum LanguageError {
7    #[error("parse error in expression `{expr}`: {reason}")]
8    ParseError { expr: String, reason: String },
9
10    #[error("evaluation error: {0}")]
11    EvalError(String),
12
13    #[error("unknown variable: {0}")]
14    UnknownVariable(String),
15
16    #[error("language `{0}` not found in registry")]
17    NotFound(String),
18
19    #[error("feature '{feature}' not supported by language '{language}'")]
20    NotSupported { feature: String, language: String },
21
22    /// Structured evaluation failure with a typed class and an optional
23    /// engine-reported position inside the expression source.
24    ///
25    /// `detail` is optional already-redacted diagnostic text: the emitting
26    /// crate MUST strip exchange data before storing it here. The `Display`
27    /// output stays short and carries no value text.
28    #[error(
29        "evaluation failure: {class}{}{}",
30        position.map(|p| format!(" at {p}")).unwrap_or_default(),
31        detail.as_deref().map(|d| format!(": {d}")).unwrap_or_default()
32    )]
33    EvalFailure {
34        class: ExpressionErrorClass,
35        position: Option<ErrorPosition>,
36        detail: Option<String>,
37    },
38
39    /// A value's type did not match the type expected by the destination.
40    /// `expected`/`actual` are TYPE names, never runtime values.
41    #[error(
42        "type mismatch: expected {expected}, actual {actual}{}",
43        position.map(|p| format!(" at {p}")).unwrap_or_default()
44    )]
45    TypeMismatch {
46        expected: String,
47        actual: String,
48        position: Option<ErrorPosition>,
49    },
50
51    /// The evaluation result could not be converted to the declared
52    /// destination type. `source_type`/`target` are type/destination names,
53    /// never runtime values (see `crate::eval::to_expression_failed` for the
54    /// trusted-target rewrite).
55    #[error("conversion error: cannot convert {source_type} to {target}")]
56    ConversionError { source_type: String, target: String },
57}
58
59impl LanguageError {
60    /// Create an `EvalError` that includes the expression being evaluated.
61    ///
62    /// This preserves the expression context in the error message for easier debugging.
63    pub fn eval_error(expr: &str, message: impl std::fmt::Display) -> Self {
64        LanguageError::EvalError(format!("in expression `{expr}`: {message}"))
65    }
66
67    /// Typed failure classification for structured transport
68    /// (see `crate::eval::to_expression_failed`).
69    ///
70    /// Registry/lifecycle variants (`UnknownVariable`, `NotFound`,
71    /// `NotSupported`) are not evaluation classes and yield `None`.
72    pub fn class(&self) -> Option<ExpressionErrorClass> {
73        match self {
74            Self::ParseError { .. } => Some(ExpressionErrorClass::Parse),
75            Self::EvalError(_) => Some(ExpressionErrorClass::Runtime),
76            Self::EvalFailure { class, .. } => Some(*class),
77            Self::TypeMismatch { .. } => Some(ExpressionErrorClass::TypeMismatch),
78            Self::ConversionError { .. } => Some(ExpressionErrorClass::Conversion),
79            Self::UnknownVariable(_) | Self::NotFound(_) | Self::NotSupported { .. } => None,
80        }
81    }
82
83    /// Engine-reported 1-based position inside the expression source, when
84    /// the variant carries one.
85    pub fn position(&self) -> Option<ErrorPosition> {
86        match self {
87            Self::EvalFailure { position, .. } | Self::TypeMismatch { position, .. } => *position,
88            _ => None,
89        }
90    }
91}