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    /// Contractually redacted diagnostic detail, when the variant carries one.
68    ///
69    /// `Some` is returned ONLY for payloads whose contract excludes runtime
70    /// exchange and provider data: `EvalFailure::detail` (already redacted by
71    /// the emitting language), and the type/destination names of
72    /// `TypeMismatch` and `ConversionError`. Free-form payloads (`EvalError`,
73    /// `ParseError`, and the registry/lifecycle variants) may embed
74    /// expression, exchange, or provider text, so they return `None`; callers
75    /// must not forward them as diagnostic causes.
76    pub fn redacted_detail(&self) -> Option<String> {
77        match self {
78            Self::EvalFailure { detail, .. } => detail.clone(),
79            Self::TypeMismatch {
80                expected, actual, ..
81            } => Some(format!("expected {expected}, actual {actual}")),
82            Self::ConversionError {
83                source_type,
84                target,
85            } => Some(format!("cannot convert {source_type} to {target}")),
86            Self::ParseError { .. }
87            | Self::EvalError(_)
88            | Self::UnknownVariable(_)
89            | Self::NotFound(_)
90            | Self::NotSupported { .. } => None,
91        }
92    }
93
94    /// Typed failure classification for structured transport
95    /// (see `crate::eval::to_expression_failed`).
96    ///
97    /// Registry/lifecycle variants (`UnknownVariable`, `NotFound`,
98    /// `NotSupported`) are not evaluation classes and yield `None`.
99    pub fn class(&self) -> Option<ExpressionErrorClass> {
100        match self {
101            Self::ParseError { .. } => Some(ExpressionErrorClass::Parse),
102            Self::EvalError(_) => Some(ExpressionErrorClass::Runtime),
103            Self::EvalFailure { class, .. } => Some(*class),
104            Self::TypeMismatch { .. } => Some(ExpressionErrorClass::TypeMismatch),
105            Self::ConversionError { .. } => Some(ExpressionErrorClass::Conversion),
106            Self::UnknownVariable(_) | Self::NotFound(_) | Self::NotSupported { .. } => None,
107        }
108    }
109
110    /// Engine-reported 1-based position inside the expression source, when
111    /// the variant carries one.
112    pub fn position(&self) -> Option<ErrorPosition> {
113        match self {
114            Self::EvalFailure { position, .. } | Self::TypeMismatch { position, .. } => *position,
115            _ => None,
116        }
117    }
118}