1use std::sync::Arc;
14
15use camel_api::{
16 BoxBoolFuture, BoxValueFuture, CamelError, ConversionDetail, ExpressionErrorClass, Value,
17};
18
19use crate::error::LanguageError;
20use crate::{Exchange, Expression, Predicate};
21
22const GENERIC_TARGETS: [&str; 2] = ["value", "body"];
34
35#[derive(Clone, Debug)]
41pub struct EvalMeta {
42 pub language: String,
44 pub route_id: String,
46 pub step_id: String,
48 pub verb: String,
50 pub target: Option<String>,
54}
55
56pub fn to_expression_failed(err: LanguageError, meta: &EvalMeta) -> CamelError {
67 let class = err.class().unwrap_or(ExpressionErrorClass::Runtime);
68 let position = err.position();
69 let conversion = match &err {
70 LanguageError::ConversionError {
71 source_type,
72 target,
73 } => {
74 let target = match meta.target.as_deref() {
75 Some(trusted) if GENERIC_TARGETS.contains(&target.as_str()) => trusted.to_string(),
76 _ => target.clone(),
77 };
78 Some(ConversionDetail {
79 source_type: source_type.clone(),
80 target,
81 })
82 }
83 _ => None,
84 };
85 CamelError::ExpressionFailed {
86 language: meta.language.clone(),
87 route_id: meta.route_id.clone(),
88 step_id: meta.step_id.clone(),
89 verb: meta.verb.clone(),
90 class,
91 position,
92 conversion,
93 cause: None,
94 }
95}
96
97#[derive(Clone)]
100pub struct LanguageExpressionEval {
101 expr: Arc<dyn Expression>,
102 meta: EvalMeta,
103}
104
105impl LanguageExpressionEval {
106 pub fn new(expr: Arc<dyn Expression>, meta: EvalMeta) -> Self {
108 Self { expr, meta }
109 }
110
111 pub fn meta(&self) -> &EvalMeta {
113 &self.meta
114 }
115
116 pub async fn evaluate(&self, exchange: &Exchange) -> Result<Value, CamelError> {
119 self.expr
120 .evaluate(exchange)
121 .await
122 .map_err(|err| to_expression_failed(err, &self.meta))
123 }
124
125 pub fn into_value_fn(self) -> Arc<dyn Fn(&Exchange) -> BoxValueFuture + Send + Sync> {
137 let expr = self.expr;
138 let meta = self.meta;
139 Arc::new(move |exchange: &Exchange| {
140 let expr = Arc::clone(&expr);
141 let meta = meta.clone();
142 let exchange = exchange.clone();
143 Box::pin(async move {
144 expr.evaluate(&exchange)
145 .await
146 .map_err(|err| to_expression_failed(err, &meta))
147 }) as BoxValueFuture
148 })
149 }
150}
151
152#[derive(Clone)]
155pub struct LanguagePredicateEval {
156 pred: Arc<dyn Predicate>,
157 meta: EvalMeta,
158}
159
160impl LanguagePredicateEval {
161 pub fn new(pred: Arc<dyn Predicate>, meta: EvalMeta) -> Self {
163 Self { pred, meta }
164 }
165
166 pub fn meta(&self) -> &EvalMeta {
168 &self.meta
169 }
170
171 pub async fn matches(&self, exchange: &Exchange) -> Result<bool, CamelError> {
174 self.pred
175 .matches(exchange)
176 .await
177 .map_err(|err| to_expression_failed(err, &self.meta))
178 }
179
180 pub fn into_bool_fn(self) -> Arc<dyn Fn(&Exchange) -> BoxBoolFuture + Send + Sync> {
193 let pred = self.pred;
194 let meta = self.meta;
195 Arc::new(move |exchange: &Exchange| {
196 let pred = Arc::clone(&pred);
197 let meta = meta.clone();
198 let exchange = exchange.clone();
199 Box::pin(async move {
200 pred.matches(&exchange)
201 .await
202 .map_err(|err| to_expression_failed(err, &meta))
203 }) as BoxBoolFuture
204 })
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::{Exchange, Message};
212 use camel_api::{ErrorPosition, ExpressionErrorClass};
213
214 fn test_meta(target: Option<&str>) -> EvalMeta {
215 EvalMeta {
216 language: "rhai".into(),
217 route_id: "r1".into(),
218 step_id: "set_property#0".into(),
219 verb: "set_property".into(),
220 target: target.map(str::to_string),
221 }
222 }
223
224 #[test]
225 fn to_expression_failed_maps_class_and_position() {
226 let err = LanguageError::EvalFailure {
227 class: ExpressionErrorClass::Arithmetic,
228 position: Some(ErrorPosition { line: 3, column: 8 }),
229 detail: None,
230 };
231 let out = to_expression_failed(err, &test_meta(None));
232 assert!(matches!(
233 out,
234 CamelError::ExpressionFailed {
235 class: ExpressionErrorClass::Arithmetic,
236 position: Some(p),
237 ..
238 } if p.line == 3 && p.column == 8
239 ));
240 }
241
242 #[test]
243 fn to_expression_failed_defaults_eval_error_to_runtime() {
244 let out = to_expression_failed(LanguageError::EvalError("x".into()), &test_meta(None));
245 assert!(matches!(
246 out,
247 CamelError::ExpressionFailed {
248 class: ExpressionErrorClass::Runtime,
249 position: None,
250 ..
251 }
252 ));
253 }
254
255 #[test]
256 fn to_expression_failed_rewrites_generic_conversion_target() {
257 let err = LanguageError::ConversionError {
261 source_type: "f64".into(),
262 target: "value".into(),
263 };
264 let out = to_expression_failed(err, &test_meta(Some("property m")));
265 if let CamelError::ExpressionFailed {
266 conversion: Some(detail),
267 ..
268 } = out
269 {
270 assert_eq!(detail.source_type, "f64");
271 assert_eq!(detail.target, "property m");
272 } else {
273 panic!("expected ExpressionFailed with conversion detail, got {out:?}");
274 }
275 }
276
277 #[test]
278 fn to_expression_failed_keeps_specific_conversion_target() {
279 let err = LanguageError::ConversionError {
282 source_type: "int".into(),
283 target: "f64".into(),
284 };
285 let out = to_expression_failed(err, &test_meta(Some("property m")));
286 if let CamelError::ExpressionFailed {
287 conversion: Some(detail),
288 ..
289 } = out
290 {
291 assert_eq!(detail.target, "f64");
292 } else {
293 panic!("expected ExpressionFailed with conversion detail, got {out:?}");
294 }
295 }
296
297 struct FailingExpression;
299
300 #[async_trait::async_trait]
301 impl crate::Expression for FailingExpression {
302 async fn evaluate(&self, _exchange: &Exchange) -> Result<crate::Value, LanguageError> {
303 Err(LanguageError::EvalFailure {
304 class: ExpressionErrorClass::Arithmetic,
305 position: Some(ErrorPosition { line: 1, column: 1 }),
306 detail: None,
307 })
308 }
309 }
310
311 #[tokio::test]
312 async fn language_expression_eval_wraps_error() {
313 let expr: Arc<dyn crate::Expression> = Arc::new(FailingExpression);
314 let eval = LanguageExpressionEval::new(expr, test_meta(None));
315 let exchange = Exchange::new(Message::default());
316 let err = eval.evaluate(&exchange).await.unwrap_err();
317 assert!(matches!(
318 err,
319 CamelError::ExpressionFailed { ref verb, .. } if verb == "set_property"
320 ));
321 }
322
323 #[tokio::test]
324 async fn value_fn_maps_errors_with_meta() {
325 let expr: Arc<dyn crate::Expression> = Arc::new(FailingExpression);
326 let value_fn = LanguageExpressionEval::new(expr, test_meta(Some("body"))).into_value_fn();
327 let exchange = Exchange::new(Message::default());
328 let err = value_fn(&exchange).await.unwrap_err();
329 assert!(matches!(
330 err,
331 CamelError::ExpressionFailed {
332 ref language,
333 ref route_id,
334 ref verb,
335 ..
336 } if language == "rhai" && route_id == "r1" && verb == "set_property"
337 ));
338 }
339
340 struct FailingPredicate;
343
344 #[async_trait::async_trait]
345 impl crate::Predicate for FailingPredicate {
346 async fn matches(&self, _exchange: &Exchange) -> Result<bool, LanguageError> {
347 Err(LanguageError::ConversionError {
348 source_type: "null".into(),
349 target: "header entry".into(),
350 })
351 }
352 }
353
354 #[tokio::test]
355 async fn predicate_matches_and_bool_fn_map_errors() {
356 let pred: Arc<dyn crate::Predicate> = Arc::new(FailingPredicate);
357 let eval = LanguagePredicateEval::new(pred, test_meta(Some("header x")));
358 let exchange = Exchange::new(Message::default());
359
360 let err = eval.matches(&exchange).await.unwrap_err();
361 if let CamelError::ExpressionFailed {
362 class: ExpressionErrorClass::Conversion,
363 conversion: Some(detail),
364 ..
365 } = err.clone()
366 {
367 assert_eq!(detail.target, "header entry");
370 } else {
371 panic!("expected ExpressionFailed conversion, got {err:?}");
372 }
373
374 let bool_fn = eval.into_bool_fn();
375 let err = bool_fn(&exchange).await.unwrap_err();
376 assert!(matches!(err, CamelError::ExpressionFailed { .. }));
377 }
378}