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