Skip to main content

camel_language_simple/
lib.rs

1//! Simple expression language for rust-camel — lightweight header/body/property expression parser and evaluator.
2//!
3//! Main types: `SimpleLanguage`, `SimpleExpression`, `SimplePredicate`.
4//! Main modules: `parser`, `evaluator`. No scripting dependencies.
5
6mod evaluator;
7mod parser;
8
9use std::sync::Arc;
10
11use async_trait::async_trait;
12use camel_language_api::{Exchange, Expression, Language, LanguageError, Predicate, Value};
13
14pub type ResolverFn = Arc<dyn Fn(&str) -> Option<Arc<dyn Language>> + Send + Sync>;
15
16pub struct SimpleLanguage {
17    resolver: Option<ResolverFn>,
18}
19
20struct SimpleExpression {
21    expr: parser::Expr,
22    resolver: Option<ResolverFn>,
23}
24
25struct SimplePredicate {
26    expr: parser::Expr,
27    resolver: Option<ResolverFn>,
28}
29
30impl SimpleLanguage {
31    pub fn new() -> Self {
32        Self { resolver: None }
33    }
34
35    pub fn with_resolver(resolver: ResolverFn) -> Self {
36        Self {
37            resolver: Some(resolver),
38        }
39    }
40}
41
42impl Default for SimpleLanguage {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48#[async_trait]
49impl Expression for SimpleExpression {
50    async fn evaluate(&self, exchange: &Exchange) -> Result<Value, LanguageError> {
51        evaluator::evaluate(&self.expr, exchange, &self.resolver).await
52    }
53}
54
55#[async_trait]
56impl Predicate for SimplePredicate {
57    async fn matches(&self, exchange: &Exchange) -> Result<bool, LanguageError> {
58        let val = evaluator::evaluate(&self.expr, exchange, &self.resolver).await?;
59        Ok(match &val {
60            Value::Bool(b) => *b,
61            Value::Null => false,
62            _ => true,
63        })
64    }
65}
66
67impl Language for SimpleLanguage {
68    fn name(&self) -> &'static str {
69        "simple"
70    }
71
72    fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
73        let ast = parser::parse(script)?;
74        Ok(Box::new(SimpleExpression {
75            expr: ast,
76            resolver: self.resolver.clone(),
77        }))
78    }
79
80    fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
81        let ast = parser::parse(script)?;
82        Ok(Box::new(SimplePredicate {
83            expr: ast,
84            resolver: self.resolver.clone(),
85        }))
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::SimpleLanguage;
92    use camel_language_api::Language;
93    use camel_language_api::{Exchange, Message, Value};
94
95    fn exchange_with_header(key: &str, val: &str) -> Exchange {
96        let mut msg = Message::default();
97        msg.set_header(key, Value::String(val.to_string()));
98        Exchange::new(msg)
99    }
100
101    fn exchange_with_body(body: &str) -> Exchange {
102        Exchange::new(Message::new(body))
103    }
104
105    #[tokio::test]
106    async fn test_header_equals_string() {
107        let lang = SimpleLanguage::new();
108        let pred = lang.create_predicate("${header.type} == 'order'").unwrap();
109        let ex = exchange_with_header("type", "order");
110        assert!(pred.matches(&ex).await.unwrap());
111    }
112
113    #[tokio::test]
114    async fn test_header_not_equals() {
115        let lang = SimpleLanguage::new();
116        let pred = lang.create_predicate("${header.type} != 'order'").unwrap();
117        let ex = exchange_with_header("type", "invoice");
118        assert!(pred.matches(&ex).await.unwrap());
119    }
120
121    #[tokio::test]
122    async fn test_body_contains() {
123        let lang = SimpleLanguage::new();
124        let pred = lang.create_predicate("${body} contains 'hello'").unwrap();
125        let ex = exchange_with_body("say hello world");
126        assert!(pred.matches(&ex).await.unwrap());
127    }
128
129    #[tokio::test]
130    async fn test_header_null_check() {
131        let lang = SimpleLanguage::new();
132        let pred = lang.create_predicate("${header.missing} == null").unwrap();
133        let ex = exchange_with_body("anything");
134        assert!(pred.matches(&ex).await.unwrap());
135    }
136
137    #[tokio::test]
138    async fn test_header_not_null() {
139        let lang = SimpleLanguage::new();
140        let pred = lang.create_predicate("${header.type} != null").unwrap();
141        let ex = exchange_with_header("type", "order");
142        assert!(pred.matches(&ex).await.unwrap());
143    }
144
145    #[tokio::test]
146    async fn test_expression_header_value() {
147        let lang = SimpleLanguage::new();
148        let expr = lang.create_expression("${header.type}").unwrap();
149        let ex = exchange_with_header("type", "order");
150        let val = expr.evaluate(&ex).await.unwrap();
151        assert_eq!(val, Value::String("order".to_string()));
152    }
153
154    #[tokio::test]
155    async fn test_expression_body() {
156        let lang = SimpleLanguage::new();
157        let expr = lang.create_expression("${body}").unwrap();
158        let ex = exchange_with_body("hello");
159        let val = expr.evaluate(&ex).await.unwrap();
160        assert_eq!(val, Value::String("hello".to_string()));
161    }
162
163    #[tokio::test]
164    async fn test_numeric_comparison() {
165        let lang = SimpleLanguage::new();
166        let pred = lang.create_predicate("${header.age} > 18").unwrap();
167        let mut ex = Exchange::new(Message::default());
168        ex.input.set_header("age", Value::Number(25.into()));
169        assert!(pred.matches(&ex).await.unwrap());
170    }
171
172    // --- Edge case tests ---
173
174    #[tokio::test]
175    async fn test_empty_body() {
176        let lang = SimpleLanguage::new();
177        let expr = lang.create_expression("${body}").unwrap();
178        let ex = Exchange::new(Message::default());
179        let val = expr.evaluate(&ex).await.unwrap();
180        assert_eq!(val, Value::Null);
181    }
182
183    #[tokio::test]
184    async fn test_parse_error_unrecognized_token() {
185        // A pure `${...}` token that doesn't match any known form is a parse error.
186        // For example `${unknown}` is not a valid Simple expression.
187        let lang = SimpleLanguage::new();
188        let result = lang.create_expression("${unknown}");
189        assert!(result.is_err(), "unknown token should be a parse error");
190    }
191
192    #[tokio::test]
193    async fn test_empty_header_key_is_parse_error() {
194        let lang = SimpleLanguage::new();
195        let result = lang.create_expression("${header.}");
196        let err = result.err().expect("should be a parse error");
197        let err = format!("{err}");
198        assert!(
199            err.contains("empty"),
200            "error should mention empty key, got: {err}"
201        );
202    }
203
204    #[tokio::test]
205    async fn test_empty_exchange_property_key_is_parse_error() {
206        let lang = SimpleLanguage::new();
207        let result = lang.create_expression("${exchangeProperty.}");
208        let err = result.err().expect("should be a parse error");
209        let err = format!("{err}");
210        assert!(
211            err.contains("empty"),
212            "error should mention empty key, got: {err}"
213        );
214    }
215
216    #[tokio::test]
217    async fn test_missing_header_returns_null() {
218        let lang = SimpleLanguage::new();
219        let expr = lang.create_expression("${header.nonexistent}").unwrap();
220        let ex = exchange_with_body("anything");
221        let val = expr.evaluate(&ex).await.unwrap();
222        assert_eq!(val, Value::Null);
223    }
224
225    #[tokio::test]
226    async fn test_exchange_property_expression() {
227        let lang = SimpleLanguage::new();
228        let expr = lang
229            .create_expression("${exchangeProperty.myProp}")
230            .unwrap();
231        let mut ex = exchange_with_body("test");
232        ex.set_property("myProp".to_string(), Value::String("propVal".to_string()));
233        let val = expr.evaluate(&ex).await.unwrap();
234        assert_eq!(val, Value::String("propVal".to_string()));
235    }
236
237    #[tokio::test]
238    async fn test_missing_property_returns_null() {
239        let lang = SimpleLanguage::new();
240        let expr = lang
241            .create_expression("${exchangeProperty.missing}")
242            .unwrap();
243        let ex = exchange_with_body("test");
244        let val = expr.evaluate(&ex).await.unwrap();
245        assert_eq!(val, Value::Null);
246    }
247
248    #[tokio::test]
249    async fn test_property_and_header_keys_preserve_hyphens() {
250        // Regression guard for rc-o6o.4: a reporter claimed
251        // `${exchangeProperty.my-decision}` resolved only `my`, silently
252        // skipping the choice.when branch. Probe (2026-06-20) showed the
253        // parser preserves the full key — `parser.rs` uses
254        // `strip_prefix("exchangeProperty.")` / `strip_prefix("header.")`,
255        // both of which keep hyphens. This test locks the behaviour in for
256        // property + header keys with hyphens, in both expression and
257        // predicate positions.
258        let lang = SimpleLanguage::new();
259
260        // Property expression: ${exchangeProperty.my-decision} -> "reject"
261        let prop_expr = lang
262            .create_expression("${exchangeProperty.my-decision}")
263            .unwrap();
264        let mut ex = exchange_with_body("anything");
265        ex.set_property(
266            "my-decision".to_string(),
267            Value::String("reject".to_string()),
268        );
269        let val = prop_expr.evaluate(&ex).await.unwrap();
270        assert_eq!(val, Value::String("reject".to_string()));
271
272        // Predicate: ${exchangeProperty.my-decision} == 'reject' -> true
273        let pred = lang
274            .create_predicate("${exchangeProperty.my-decision} == 'reject'")
275            .unwrap();
276        assert!(pred.matches(&ex).await.unwrap());
277
278        // Header expression: ${header.my-header} -> "hv"
279        let header_expr = lang.create_expression("${header.my-header}").unwrap();
280        ex.input
281            .set_header("my-header".to_string(), Value::String("hv".to_string()));
282        let header_val = header_expr.evaluate(&ex).await.unwrap();
283        assert_eq!(header_val, Value::String("hv".to_string()));
284    }
285
286    #[tokio::test]
287    async fn test_string_literal_expression() {
288        let lang = SimpleLanguage::new();
289        let expr = lang.create_expression("'hello'").unwrap();
290        let ex = exchange_with_body("test");
291        let val = expr.evaluate(&ex).await.unwrap();
292        assert_eq!(val, Value::String("hello".to_string()));
293    }
294
295    #[tokio::test]
296    async fn test_double_quoted_literal_unescapes_newline() {
297        let lang = SimpleLanguage::new();
298        let expr = lang.create_expression("\"line1\\nline2\"").unwrap();
299        let ex = exchange_with_body("test");
300        let val = expr.evaluate(&ex).await.unwrap();
301        assert_eq!(val, Value::String("line1\nline2".to_string()));
302    }
303
304    #[tokio::test]
305    async fn test_double_quoted_literal_unescapes_tab_and_quote() {
306        let lang = SimpleLanguage::new();
307        let expr = lang.create_expression("\"col1\\t\\\"quoted\\\"\"").unwrap();
308        let ex = exchange_with_body("test");
309        let val = expr.evaluate(&ex).await.unwrap();
310        assert_eq!(val, Value::String("col1\t\"quoted\"".to_string()));
311    }
312
313    #[tokio::test]
314    async fn test_double_quoted_literal_unescapes_backspace_formfeed_and_slash() {
315        let lang = SimpleLanguage::new();
316        let expr = lang.create_expression("\"a\\bb\\fc\\/d\"").unwrap();
317        let ex = exchange_with_body("test");
318        let val = expr.evaluate(&ex).await.unwrap();
319        assert_eq!(val, Value::String("a\u{0008}b\u{000C}c/d".to_string()));
320    }
321
322    #[tokio::test]
323    async fn test_null_expression() {
324        let lang = SimpleLanguage::new();
325        let expr = lang.create_expression("null").unwrap();
326        let ex = exchange_with_body("test");
327        let val = expr.evaluate(&ex).await.unwrap();
328        assert_eq!(val, Value::Null);
329    }
330
331    #[tokio::test]
332    async fn test_predicate_null_is_false() {
333        let lang = SimpleLanguage::new();
334        let pred = lang.create_predicate("${header.missing}").unwrap();
335        let ex = exchange_with_body("test");
336        assert!(!pred.matches(&ex).await.unwrap());
337    }
338
339    #[tokio::test]
340    async fn test_predicate_non_null_is_true() {
341        let lang = SimpleLanguage::new();
342        let pred = lang.create_predicate("${header.type}").unwrap();
343        let ex = exchange_with_header("type", "order");
344        assert!(pred.matches(&ex).await.unwrap());
345    }
346
347    #[tokio::test]
348    async fn test_contains_not_found() {
349        let lang = SimpleLanguage::new();
350        let pred = lang.create_predicate("${body} contains 'xyz'").unwrap();
351        let ex = exchange_with_body("hello world");
352        assert!(!pred.matches(&ex).await.unwrap());
353    }
354
355    #[tokio::test]
356    async fn test_less_than_or_equal() {
357        let lang = SimpleLanguage::new();
358        let pred = lang.create_predicate("${header.age} <= 18").unwrap();
359        let mut ex = Exchange::new(Message::default());
360        ex.input.set_header("age", Value::Number(18.into()));
361        assert!(pred.matches(&ex).await.unwrap());
362    }
363
364    // --- Mixed interpolation tests ---
365
366    #[tokio::test]
367    async fn test_interpolated_text_with_header() {
368        // "Exchange #${header.CamelTimerCounter}" → "Exchange #42"
369        let lang = SimpleLanguage::new();
370        let expr = lang
371            .create_expression("Exchange #${header.CamelTimerCounter}")
372            .unwrap();
373        let ex = exchange_with_header("CamelTimerCounter", "42");
374        let val = expr.evaluate(&ex).await.unwrap();
375        assert_eq!(val, Value::String("Exchange #42".to_string()));
376    }
377
378    #[tokio::test]
379    async fn test_interpolated_text_with_body() {
380        // "Got ${body}" → "Got hello"
381        let lang = SimpleLanguage::new();
382        let expr = lang.create_expression("Got ${body}").unwrap();
383        let ex = exchange_with_body("hello");
384        let val = expr.evaluate(&ex).await.unwrap();
385        assert_eq!(val, Value::String("Got hello".to_string()));
386    }
387
388    #[tokio::test]
389    async fn test_interpolated_multiple_expressions() {
390        // "Transformed: ${body} (source=${header.source})" → real values
391        let lang = SimpleLanguage::new();
392        let expr = lang
393            .create_expression("Transformed: ${body} (source=${header.source})")
394            .unwrap();
395        let mut msg = Message::new("data");
396        msg.set_header("source", Value::String("kafka".to_string()));
397        let ex = Exchange::new(msg);
398        let val = expr.evaluate(&ex).await.unwrap();
399        assert_eq!(
400            val,
401            Value::String("Transformed: data (source=kafka)".to_string())
402        );
403    }
404
405    #[tokio::test]
406    async fn test_interpolated_missing_header_becomes_empty() {
407        // Missing header in interpolated string yields empty string for that slot
408        let lang = SimpleLanguage::new();
409        let expr = lang
410            .create_expression("prefix-${header.missing}-suffix")
411            .unwrap();
412        let ex = exchange_with_body("x");
413        let val = expr.evaluate(&ex).await.unwrap();
414        assert_eq!(val, Value::String("prefix--suffix".to_string()));
415    }
416
417    #[tokio::test]
418    async fn test_interpolated_text_only_no_expressions() {
419        // Plain text with no ${...} — treated as a literal (no interpolation needed,
420        // but must still work without error)
421        let lang = SimpleLanguage::new();
422        let expr = lang.create_expression("Hello World").unwrap();
423        let ex = exchange_with_body("x");
424        let val = expr.evaluate(&ex).await.unwrap();
425        assert_eq!(val, Value::String("Hello World".to_string()));
426    }
427
428    #[tokio::test]
429    async fn test_interpolated_unclosed_brace_treated_as_literal() {
430        // An unclosed `${` has no matching `}` — the remainder is treated as
431        // plain literal text rather than causing a parse error.
432        let lang = SimpleLanguage::new();
433        let expr = lang.create_expression("Got ${body").unwrap();
434        let ex = exchange_with_body("hello");
435        let val = expr.evaluate(&ex).await.unwrap();
436        assert_eq!(val, Value::String("Got ${body".to_string()));
437    }
438
439    #[tokio::test]
440    async fn test_operator_inside_string_literal_not_split() {
441        // The string literal 'a>=b' contains '>=' but must NOT be parsed as a
442        // BinOp split — the whole thing is a StringLit atom.
443        let lang = SimpleLanguage::new();
444        let result = lang.create_expression("'a>=b'");
445        let val = result
446            .unwrap()
447            .evaluate(&Exchange::new(Message::default()))
448            .await
449            .unwrap();
450        assert_eq!(
451            val,
452            Value::String("a>=b".to_string()),
453            "string literal 'a>=b' should be parsed as a plain string, not split on >="
454        );
455    }
456
457    #[tokio::test]
458    async fn test_header_eq_string_literal_containing_operator() {
459        // ${header.x} == 'a>=b' — the operator inside the RHS string must not
460        // cause the parser to split the LHS at the wrong position.
461        let lang = SimpleLanguage::new();
462        let pred = lang.create_predicate("${header.x} == 'a>=b'").unwrap();
463        let ex = exchange_with_header("x", "a>=b");
464        assert!(
465            pred.matches(&ex).await.unwrap(),
466            "predicate should match when header equals 'a>=b'"
467        );
468    }
469
470    #[tokio::test]
471    async fn test_body_empty_is_null() {
472        let lang = SimpleLanguage::new();
473        let expr = lang.create_expression("${body}").unwrap();
474        let ex = Exchange::new(Message::default());
475        let val = expr.evaluate(&ex).await.unwrap();
476        assert_eq!(val, Value::Null);
477    }
478
479    #[tokio::test]
480    async fn test_body_empty_not_null_is_false() {
481        let lang = SimpleLanguage::new();
482        let pred = lang.create_predicate("${body} != null").unwrap();
483        let ex = Exchange::new(Message::default());
484        assert!(!pred.matches(&ex).await.unwrap());
485    }
486
487    #[tokio::test]
488    async fn test_body_empty_predicate_is_false() {
489        let lang = SimpleLanguage::new();
490        let pred = lang.create_predicate("${body}").unwrap();
491        let ex = Exchange::new(Message::default());
492        assert!(!pred.matches(&ex).await.unwrap());
493    }
494
495    #[tokio::test]
496    async fn test_logical_and_true_true() {
497        let lang = SimpleLanguage::new();
498        let pred = lang
499            .create_predicate("${header.a} == '1' && ${header.b} == '2'")
500            .unwrap();
501        let mut msg = Message::default();
502        msg.set_header("a", Value::String("1".to_string()));
503        msg.set_header("b", Value::String("2".to_string()));
504        let ex = Exchange::new(msg);
505        assert!(pred.matches(&ex).await.unwrap());
506    }
507
508    #[tokio::test]
509    async fn test_logical_and_true_false() {
510        let lang = SimpleLanguage::new();
511        let pred = lang
512            .create_predicate("${header.a} == '1' && ${header.b} == '2'")
513            .unwrap();
514        let mut msg = Message::default();
515        msg.set_header("a", Value::String("1".to_string()));
516        msg.set_header("b", Value::String("99".to_string()));
517        let ex = Exchange::new(msg);
518        assert!(!pred.matches(&ex).await.unwrap());
519    }
520
521    #[tokio::test]
522    async fn test_logical_or_false_true() {
523        let lang = SimpleLanguage::new();
524        let pred = lang
525            .create_predicate("${header.a} == '1' || ${header.b} == '2'")
526            .unwrap();
527        let mut msg = Message::default();
528        msg.set_header("a", Value::String("0".to_string()));
529        msg.set_header("b", Value::String("2".to_string()));
530        let ex = Exchange::new(msg);
531        assert!(pred.matches(&ex).await.unwrap());
532    }
533
534    #[tokio::test]
535    async fn test_logical_or_false_false() {
536        let lang = SimpleLanguage::new();
537        let pred = lang
538            .create_predicate("${header.a} == '1' || ${header.b} == '2'")
539            .unwrap();
540        let mut msg = Message::default();
541        msg.set_header("a", Value::String("0".to_string()));
542        msg.set_header("b", Value::String("0".to_string()));
543        let ex = Exchange::new(msg);
544        assert!(!pred.matches(&ex).await.unwrap());
545    }
546
547    #[tokio::test]
548    async fn test_logical_and_precedence_over_or() {
549        let lang = SimpleLanguage::new();
550        let pred = lang
551            .create_predicate("${header.a} == '1' || ${header.b} == '2' && ${header.c} == '3'")
552            .unwrap();
553        let mut msg = Message::default();
554        msg.set_header("a", Value::String("1".to_string()));
555        msg.set_header("b", Value::String("2".to_string()));
556        msg.set_header("c", Value::String("999".to_string()));
557        let ex = Exchange::new(msg);
558        assert!(pred.matches(&ex).await.unwrap());
559    }
560
561    #[tokio::test]
562    async fn test_logical_and_short_circuit() {
563        let lang = SimpleLanguage::new();
564        let pred = lang
565            .create_predicate("${header.a} == 'x' && ${header.nonexistent} > 999")
566            .unwrap();
567        let mut msg = Message::default();
568        msg.set_header("a", Value::String("not-x".to_string()));
569        let ex = Exchange::new(msg);
570        assert!(!pred.matches(&ex).await.unwrap());
571    }
572
573    #[tokio::test]
574    async fn test_logical_or_short_circuit() {
575        let lang = SimpleLanguage::new();
576        let pred = lang
577            .create_predicate("${header.a} == '1' || ${header.nonexistent} > 999")
578            .unwrap();
579        let mut msg = Message::default();
580        msg.set_header("a", Value::String("1".to_string()));
581        let ex = Exchange::new(msg);
582        assert!(pred.matches(&ex).await.unwrap());
583    }
584
585    #[tokio::test]
586    async fn test_compound_filter_body_not_null_and_body_not_empty() {
587        let lang = SimpleLanguage::new();
588        let pred = lang
589            .create_predicate("${body} != null && ${body} != ''")
590            .unwrap();
591        let ex = exchange_with_body("hello");
592        assert!(pred.matches(&ex).await.unwrap());
593
594        let ex_empty = Exchange::new(Message::default());
595        assert!(!pred.matches(&ex_empty).await.unwrap());
596    }
597
598    #[tokio::test]
599    async fn test_header_with_gt_in_key() {
600        let lang = SimpleLanguage::new();
601        let mut msg = Message::default();
602        msg.set_header("a>b", Value::String("found".to_string()));
603        let pred = lang.create_predicate("${header.a>b} == 'found'").unwrap();
604        let ex = Exchange::new(msg);
605        assert!(pred.matches(&ex).await.unwrap());
606    }
607
608    #[tokio::test]
609    async fn test_double_quoted_string_with_operator() {
610        let lang = SimpleLanguage::new();
611        let expr = lang.create_expression("\"a >= b\"").unwrap();
612        let ex = exchange_with_body("test");
613        let val = expr.evaluate(&ex).await.unwrap();
614        assert_eq!(val, Value::String("a >= b".to_string()));
615    }
616
617    #[tokio::test]
618    async fn test_bool_literal_true() {
619        let lang = SimpleLanguage::new();
620        let expr = lang.create_expression("true").unwrap();
621        let ex = exchange_with_body("test");
622        let val = expr.evaluate(&ex).await.unwrap();
623        assert_eq!(val, Value::Bool(true));
624    }
625
626    #[tokio::test]
627    async fn test_bool_literal_false() {
628        let lang = SimpleLanguage::new();
629        let expr = lang.create_expression("false").unwrap();
630        let ex = exchange_with_body("test");
631        let val = expr.evaluate(&ex).await.unwrap();
632        assert_eq!(val, Value::Bool(false));
633    }
634
635    #[tokio::test]
636    async fn test_bool_literal_in_comparison() {
637        use camel_language_api::Body;
638
639        let lang = SimpleLanguage::new();
640        let mut ex = Exchange::default();
641        ex.input.body = Body::Json(serde_json::json!({"active": true}));
642        let pred = lang.create_predicate("${body.active} == true").unwrap();
643        assert!(pred.matches(&ex).await.unwrap());
644    }
645
646    #[tokio::test]
647    async fn test_null_gt_number_is_false() {
648        let lang = SimpleLanguage::new();
649        let pred = lang.create_predicate("${header.missing} > 5").unwrap();
650        let ex = exchange_with_body("test");
651        assert!(!pred.matches(&ex).await.unwrap());
652    }
653
654    #[tokio::test]
655    async fn test_null_lt_number_is_false() {
656        let lang = SimpleLanguage::new();
657        let pred = lang.create_predicate("${header.missing} < 10").unwrap();
658        let ex = exchange_with_body("test");
659        assert!(!pred.matches(&ex).await.unwrap());
660    }
661
662    #[tokio::test]
663    async fn test_null_gte_number_is_false() {
664        let lang = SimpleLanguage::new();
665        let pred = lang.create_predicate("${header.missing} >= 0").unwrap();
666        let ex = exchange_with_body("test");
667        assert!(!pred.matches(&ex).await.unwrap());
668    }
669
670    #[tokio::test]
671    async fn test_null_lte_number_is_false() {
672        let lang = SimpleLanguage::new();
673        let pred = lang.create_predicate("${header.missing} <= 100").unwrap();
674        let ex = exchange_with_body("test");
675        assert!(!pred.matches(&ex).await.unwrap());
676    }
677
678    #[tokio::test]
679    async fn test_number_gt_null_is_false() {
680        let lang = SimpleLanguage::new();
681        let pred = lang.create_predicate("5 > ${header.missing}").unwrap();
682        let ex = exchange_with_body("test");
683        assert!(!pred.matches(&ex).await.unwrap());
684    }
685
686    #[tokio::test]
687    async fn test_true_or_false() {
688        let lang = SimpleLanguage::new();
689        let pred = lang.create_predicate("true || false").unwrap();
690        let ex = exchange_with_body("test");
691        assert!(pred.matches(&ex).await.unwrap());
692    }
693
694    #[tokio::test]
695    async fn test_false_and_true() {
696        let lang = SimpleLanguage::new();
697        let pred = lang.create_predicate("false && true").unwrap();
698        let ex = exchange_with_body("test");
699        assert!(!pred.matches(&ex).await.unwrap());
700    }
701
702    #[tokio::test]
703    async fn test_contains_null_left_is_false() {
704        let lang = SimpleLanguage::new();
705        let pred = lang
706            .create_predicate("${header.missing} contains 'x'")
707            .unwrap();
708        let ex = exchange_with_body("test");
709        assert!(!pred.matches(&ex).await.unwrap());
710    }
711
712    #[tokio::test]
713    async fn test_contains_null_right_is_false() {
714        let lang = SimpleLanguage::new();
715        let pred = lang.create_predicate("${body} contains null").unwrap();
716        let ex = exchange_with_body("test");
717        assert!(!pred.matches(&ex).await.unwrap());
718    }
719
720    #[tokio::test]
721    async fn test_contains_without_spaces() {
722        let lang = SimpleLanguage::new();
723        let pred = lang.create_predicate("${body}contains'hello'").unwrap();
724        let ex = exchange_with_body("say hello world");
725        assert!(pred.matches(&ex).await.unwrap());
726    }
727
728    #[tokio::test]
729    async fn test_non_finite_number_parse_error() {
730        let lang = SimpleLanguage::new();
731        // Very long digit string that overflows f64 to infinity
732        let result = lang.create_expression(
733            "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
734        );
735        assert!(result.is_err(), "non-finite number should be a parse error");
736    }
737
738    #[tokio::test]
739    async fn test_interpolation_with_empty_body_still_produces_text() {
740        let lang = SimpleLanguage::new();
741        let expr = lang.create_expression("Got ${body}").unwrap();
742        let ex = Exchange::new(Message::default());
743        let val = expr.evaluate(&ex).await.unwrap();
744        assert_eq!(val, Value::String("Got ".to_string()));
745    }
746
747    #[tokio::test]
748    async fn test_interpolation_with_empty_body_and_trailing_text() {
749        let lang = SimpleLanguage::new();
750        let expr = lang.create_expression("${body} tail").unwrap();
751        let ex = Exchange::new(Message::default());
752        let val = expr.evaluate(&ex).await.unwrap();
753        assert_eq!(val, Value::String(" tail".to_string()));
754    }
755
756    // --- SMP-001: string-to-number coercion for comparisons ---
757
758    #[tokio::test]
759    async fn test_string_header_eq_number_literal() {
760        // "5" == 5 should evaluate true via string-to-number coercion
761        let lang = SimpleLanguage::new();
762        let mut msg = Message::default();
763        msg.set_header("count", Value::String("5".to_string()));
764        let ex = Exchange::new(msg);
765        let pred = lang.create_predicate("${header.count} == 5").unwrap();
766        assert!(pred.matches(&ex).await.unwrap());
767    }
768
769    #[tokio::test]
770    async fn test_string_header_gt_number_literal() {
771        // "10" > 5 should evaluate true
772        let lang = SimpleLanguage::new();
773        let mut msg = Message::default();
774        msg.set_header("count", Value::String("10".to_string()));
775        let ex = Exchange::new(msg);
776        let pred = lang.create_predicate("${header.count} > 5").unwrap();
777        assert!(pred.matches(&ex).await.unwrap());
778    }
779
780    #[tokio::test]
781    async fn test_string_header_lt_number_literal() {
782        // "3" < 5 should evaluate true
783        let lang = SimpleLanguage::new();
784        let mut msg = Message::default();
785        msg.set_header("count", Value::String("3".to_string()));
786        let ex = Exchange::new(msg);
787        let pred = lang.create_predicate("${header.count} < 5").unwrap();
788        assert!(pred.matches(&ex).await.unwrap());
789    }
790
791    #[tokio::test]
792    async fn test_non_numeric_string_comparison_error() {
793        // "abc" > 5 should return EvalError (coercion fails)
794        let lang = SimpleLanguage::new();
795        let mut msg = Message::default();
796        msg.set_header("val", Value::String("abc".to_string()));
797        let ex = Exchange::new(msg);
798        let pred = lang.create_predicate("${header.val} > 5").unwrap();
799        assert!(pred.matches(&ex).await.is_err());
800    }
801
802    // --- SMP-002: negative number literals ---
803
804    #[tokio::test]
805    async fn test_negative_number_literal() {
806        let lang = SimpleLanguage::new();
807        let pred = lang.create_predicate("${header.count} > -1").unwrap();
808        let mut msg = Message::default();
809        msg.set_header("count", Value::Number(5.into()));
810        let ex = Exchange::new(msg);
811        assert!(pred.matches(&ex).await.unwrap());
812    }
813
814    #[tokio::test]
815    async fn test_negative_float_literal() {
816        let lang = SimpleLanguage::new();
817        let pred = lang.create_predicate("${header.val} > -3.14").unwrap();
818        let mut msg = Message::default();
819        msg.set_header(
820            "val",
821            Value::Number(serde_json::Number::from_f64(0.0).unwrap()),
822        );
823        let ex = Exchange::new(msg);
824        assert!(pred.matches(&ex).await.unwrap());
825    }
826
827    #[tokio::test]
828    async fn test_negative_number_equality() {
829        let lang = SimpleLanguage::new();
830        let pred = lang.create_predicate("${header.val} == -10").unwrap();
831        let mut msg = Message::default();
832        msg.set_header(
833            "val",
834            Value::Number(serde_json::Number::from_f64(-10.0).unwrap()),
835        );
836        let ex = Exchange::new(msg);
837        assert!(pred.matches(&ex).await.unwrap());
838    }
839}
840
841#[cfg(test)]
842mod body_field_parser_tests {
843    use crate::parser::{Expr, PathSegment, parse};
844
845    #[tokio::test]
846    async fn parse_body_field_simple_key() {
847        let expr = parse("${body.name}").unwrap();
848        assert_eq!(
849            expr,
850            Expr::BodyField(vec![PathSegment::Key("name".to_string())])
851        );
852    }
853
854    #[tokio::test]
855    async fn parse_body_field_nested() {
856        let expr = parse("${body.user.city}").unwrap();
857        assert_eq!(
858            expr,
859            Expr::BodyField(vec![
860                PathSegment::Key("user".to_string()),
861                PathSegment::Key("city".to_string()),
862            ])
863        );
864    }
865
866    #[tokio::test]
867    async fn parse_body_field_array_index() {
868        let expr = parse("${body.items.0}").unwrap();
869        assert_eq!(
870            expr,
871            Expr::BodyField(vec![
872                PathSegment::Key("items".to_string()),
873                PathSegment::Index(0),
874            ])
875        );
876    }
877
878    #[tokio::test]
879    async fn parse_body_field_array_nested() {
880        let expr = parse("${body.users.0.name}").unwrap();
881        assert_eq!(
882            expr,
883            Expr::BodyField(vec![
884                PathSegment::Key("users".to_string()),
885                PathSegment::Index(0),
886                PathSegment::Key("name".to_string()),
887            ])
888        );
889    }
890
891    #[tokio::test]
892    async fn parse_body_field_empty_segment_error() {
893        let result = parse("${body.}");
894        assert!(result.is_err());
895    }
896
897    #[tokio::test]
898    async fn parse_body_field_exact_still_works() {
899        // Regression: ${body} must still produce Expr::Body, not BodyField
900        let expr = parse("${body}").unwrap();
901        assert_eq!(expr, Expr::Body);
902    }
903
904    #[tokio::test]
905    async fn parse_body_field_double_dots_error() {
906        // ${body..name} has an empty segment between the two dots
907        let result = parse("${body..name}");
908        assert!(result.is_err());
909    }
910
911    #[tokio::test]
912    async fn parse_body_field_index_only() {
913        // ${body.0} — single index segment (e.g. body is a JSON array)
914        let expr = parse("${body.0}").unwrap();
915        assert_eq!(expr, Expr::BodyField(vec![PathSegment::Index(0)]));
916    }
917
918    #[tokio::test]
919    async fn parse_body_field_leading_zero_is_key() {
920        // ${body.01} — leading zero means it's a string key, not an array index
921        let expr = parse("${body.01}").unwrap();
922        assert_eq!(
923            expr,
924            Expr::BodyField(vec![PathSegment::Key("01".to_string())])
925        );
926    }
927
928    #[tokio::test]
929    async fn parse_language_delegate_jsonpath() {
930        let expr = parse("${jsonpath:$.store.book[0].title}").unwrap();
931        assert_eq!(
932            expr,
933            Expr::LanguageDelegate {
934                language: "jsonpath".to_string(),
935                expression: "$.store.book[0].title".to_string(),
936            }
937        );
938    }
939
940    #[tokio::test]
941    async fn parse_language_delegate_xpath() {
942        let expr = parse("${xpath:/order/@id}").unwrap();
943        assert_eq!(
944            expr,
945            Expr::LanguageDelegate {
946                language: "xpath".to_string(),
947                expression: "/order/@id".to_string(),
948            }
949        );
950    }
951
952    #[tokio::test]
953    async fn parse_language_delegate_allows_hyphen_and_digits() {
954        let expr = parse("${json-path2:$.a}").unwrap();
955        assert_eq!(
956            expr,
957            Expr::LanguageDelegate {
958                language: "json-path2".to_string(),
959                expression: "$.a".to_string(),
960            }
961        );
962    }
963
964    #[tokio::test]
965    async fn parse_language_delegate_rejects_uppercase_language() {
966        let result = parse("${JsonPath:$.a}");
967        assert!(result.is_err());
968    }
969}
970
971#[cfg(test)]
972mod language_delegate_eval_tests {
973    use std::sync::Arc;
974
975    use crate::SimpleLanguage;
976    use camel_language_api::{Exchange, Expression, Language, LanguageError, Value};
977
978    struct MockExpression {
979        value: String,
980    }
981
982    #[async_trait::async_trait]
983    impl Expression for MockExpression {
984        async fn evaluate(&self, _exchange: &Exchange) -> Result<Value, LanguageError> {
985            Ok(Value::String(self.value.clone()))
986        }
987    }
988
989    struct MockLanguage;
990
991    impl Language for MockLanguage {
992        fn name(&self) -> &'static str {
993            "mock"
994        }
995
996        fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
997            Ok(Box::new(MockExpression {
998                value: format!("mock:{script}"),
999            }))
1000        }
1001
1002        fn create_predicate(
1003            &self,
1004            _script: &str,
1005        ) -> Result<Box<dyn camel_language_api::Predicate>, LanguageError> {
1006            Err(LanguageError::NotSupported {
1007                feature: "predicate".to_string(),
1008                language: "mock".to_string(),
1009            })
1010        }
1011    }
1012
1013    #[tokio::test]
1014    async fn evaluate_language_delegate_uses_resolver() {
1015        let lang = SimpleLanguage::with_resolver(Arc::new(|name| {
1016            if name == "mock" {
1017                Some(Arc::new(MockLanguage))
1018            } else {
1019                None
1020            }
1021        }));
1022        let expr = lang.create_expression("${mock:hello}").unwrap();
1023        let ex = Exchange::default();
1024        let out = expr.evaluate(&ex).await.unwrap();
1025        assert_eq!(out, Value::String("mock:hello".to_string()));
1026    }
1027
1028    #[tokio::test]
1029    async fn evaluate_language_delegate_without_resolver_errors() {
1030        let lang = SimpleLanguage::new();
1031        let expr = lang.create_expression("${mock:hello}").unwrap();
1032        let ex = Exchange::default();
1033        let err = expr
1034            .evaluate(&ex)
1035            .await
1036            .expect_err("expected evaluation error");
1037        assert!(format!("{err}").contains("No language resolver configured"));
1038    }
1039}
1040
1041#[cfg(test)]
1042mod body_field_eval_tests {
1043    use crate::SimpleLanguage;
1044    use camel_language_api::Language;
1045    use camel_language_api::{Body, Exchange, Value};
1046    use serde_json::json;
1047
1048    async fn eval(expr_str: &str, body: Body) -> Value {
1049        let mut ex = Exchange::default();
1050        ex.input.body = body;
1051        let lang = SimpleLanguage::new();
1052        lang.create_expression(expr_str)
1053            .unwrap()
1054            .evaluate(&ex)
1055            .await
1056            .unwrap()
1057    }
1058
1059    #[tokio::test]
1060    async fn body_field_simple_key() {
1061        let result = eval("${body.name}", Body::Json(json!({"name": "Alice"}))).await;
1062        assert_eq!(result, json!("Alice"));
1063    }
1064
1065    #[tokio::test]
1066    async fn body_field_number_value() {
1067        let result = eval("${body.age}", Body::Json(json!({"age": 30}))).await;
1068        assert_eq!(result, json!(30));
1069    }
1070
1071    #[tokio::test]
1072    async fn body_field_bool_value() {
1073        let result = eval("${body.active}", Body::Json(json!({"active": true}))).await;
1074        assert_eq!(result, json!(true));
1075    }
1076
1077    #[tokio::test]
1078    async fn body_field_nested() {
1079        let result = eval(
1080            "${body.user.city}",
1081            Body::Json(json!({"user": {"city": "Madrid"}})),
1082        )
1083        .await;
1084        assert_eq!(result, json!("Madrid"));
1085    }
1086
1087    #[tokio::test]
1088    async fn body_field_array_index() {
1089        let result = eval("${body.items.0}", Body::Json(json!({"items": ["a", "b"]}))).await;
1090        assert_eq!(result, json!("a"));
1091    }
1092
1093    #[tokio::test]
1094    async fn body_field_array_nested() {
1095        let result = eval(
1096            "${body.users.0.name}",
1097            Body::Json(json!({"users": [{"name": "Bob"}]})),
1098        )
1099        .await;
1100        assert_eq!(result, json!("Bob"));
1101    }
1102
1103    #[tokio::test]
1104    async fn body_field_missing_key_returns_null() {
1105        let result = eval("${body.missing}", Body::Json(json!({"name": "Alice"}))).await;
1106        assert_eq!(result, Value::Null);
1107    }
1108
1109    #[tokio::test]
1110    async fn body_field_missing_nested_returns_null() {
1111        let result = eval("${body.a.b.c}", Body::Json(json!({"a": {"x": 1}}))).await;
1112        assert_eq!(result, Value::Null);
1113    }
1114
1115    #[tokio::test]
1116    async fn body_field_out_of_bounds_index_returns_null() {
1117        let result = eval("${body.items.5}", Body::Json(json!({"items": ["a"]}))).await;
1118        assert_eq!(result, Value::Null);
1119    }
1120
1121    #[tokio::test]
1122    async fn body_field_non_json_body_returns_null() {
1123        let result = eval(
1124            "${body.name}",
1125            Body::Text(r#"{"name":"Alice"}"#.to_string()),
1126        )
1127        .await;
1128        assert_eq!(result, Value::Null);
1129    }
1130
1131    #[tokio::test]
1132    async fn body_field_empty_body_returns_null() {
1133        let result = eval("${body.name}", Body::Empty).await;
1134        assert_eq!(result, Value::Null);
1135    }
1136
1137    #[tokio::test]
1138    async fn body_field_in_interpolation() {
1139        let result = eval("Hello ${body.name}!", Body::Json(json!({"name": "Alice"}))).await;
1140        assert_eq!(result, json!("Hello Alice!"));
1141    }
1142
1143    #[tokio::test]
1144    async fn body_field_in_predicate_true() {
1145        let lang = SimpleLanguage::new();
1146        let mut ex = Exchange::default();
1147        ex.input.body = Body::Json(json!({"status": "active"}));
1148        let result = lang
1149            .create_expression("${body.status} == 'active'")
1150            .unwrap()
1151            .evaluate(&ex)
1152            .await
1153            .unwrap();
1154        assert_eq!(result, Value::Bool(true));
1155    }
1156
1157    #[tokio::test]
1158    async fn body_field_in_predicate_false() {
1159        let lang = SimpleLanguage::new();
1160        let mut ex = Exchange::default();
1161        ex.input.body = Body::Json(json!({"status": "inactive"}));
1162        let result = lang
1163            .create_expression("${body.status} == 'active'")
1164            .unwrap()
1165            .evaluate(&ex)
1166            .await
1167            .unwrap();
1168        assert_eq!(result, Value::Bool(false));
1169    }
1170
1171    #[tokio::test]
1172    async fn body_field_bytes_body_returns_null() {
1173        // Note: Using Body::Text here instead of Body::Bytes since bytes crate
1174        // is not available in test context. Both should behave the same for JSON field access.
1175        let result = eval(
1176            "${body.name}",
1177            Body::Text(r#"{"name":"Alice"}"#.to_string()),
1178        )
1179        .await;
1180        assert_eq!(result, Value::Null);
1181    }
1182
1183    #[tokio::test]
1184    async fn body_field_json_null_value_returns_null() {
1185        // key exists but its value is JSON null → returns Value::Null
1186        let result = eval(
1187            "${body.name}",
1188            Body::Json(serde_json::json!({"name": null})),
1189        )
1190        .await;
1191        assert_eq!(result, Value::Null);
1192    }
1193
1194    #[tokio::test]
1195    async fn body_field_numeric_predicate() {
1196        // JSON number 42.0 compares equal to the parsed number 42 from the
1197        // Simple Language expression, because both resolve to the same f64.
1198        let lang = SimpleLanguage::new();
1199        let mut ex = Exchange::default();
1200        ex.input.body = Body::Json(json!({"score": 42.0}));
1201        let result = lang
1202            .create_expression("${body.score} == 42")
1203            .unwrap()
1204            .evaluate(&ex)
1205            .await
1206            .unwrap();
1207        assert_eq!(result, Value::Bool(true));
1208    }
1209
1210    #[tokio::test]
1211    async fn body_bytes_utf8_returns_string() {
1212        // Body::Bytes with valid UTF-8 content should be readable via ${body}
1213        let lang = SimpleLanguage::new();
1214        let mut ex = Exchange::default();
1215        ex.input.body = Body::from(b"hello from bytes".to_vec());
1216        let val = lang
1217            .create_expression("${body}")
1218            .unwrap()
1219            .evaluate(&ex)
1220            .await
1221            .unwrap();
1222        assert_eq!(val, Value::String("hello from bytes".to_string()));
1223    }
1224
1225    #[tokio::test]
1226    async fn body_json_returns_serialized_string() {
1227        // Body::Json should be serialized to a JSON string when accessed via ${body}
1228        let lang = SimpleLanguage::new();
1229        let mut ex = Exchange::default();
1230        ex.input.body = Body::Json(json!({"msg": "world"}));
1231        let val = lang
1232            .create_expression("${body}")
1233            .unwrap()
1234            .evaluate(&ex)
1235            .await
1236            .unwrap();
1237        // The result should be the JSON serialization, not empty
1238        let s = match val {
1239            Value::String(s) => s,
1240            other => panic!("expected String, got {other:?}"),
1241        };
1242        assert!(!s.is_empty(), "${{body}} on Body::Json should not be empty");
1243        let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
1244        assert_eq!(parsed["msg"], "world");
1245    }
1246
1247    #[tokio::test]
1248    async fn body_bytes_in_interpolation() {
1249        // Body::Bytes should work in interpolated expressions like "Received: ${body}"
1250        let lang = SimpleLanguage::new();
1251        let mut ex = Exchange::default();
1252        ex.input.body = Body::from(b"ping".to_vec());
1253        let val = lang
1254            .create_expression("Received: ${body}")
1255            .unwrap()
1256            .evaluate(&ex)
1257            .await
1258            .unwrap();
1259        assert_eq!(val, Value::String("Received: ping".to_string()));
1260    }
1261
1262    // --- XML→Json→Simple integration tests ---
1263    // These verify that JSON keys produced by xml_to_json() (@attr, #text, arrays)
1264    // are navigable via Simple language BodyField expressions.
1265
1266    #[tokio::test]
1267    async fn body_field_xml_attr_key() {
1268        // XML: <order id="123"> → JSON: {"order": {"@id": "123"}}
1269        let lang = SimpleLanguage::new();
1270        let mut ex = Exchange::default();
1271        ex.input.body = Body::Json(json!({"order": {"@id": "123"}}));
1272        let val = lang
1273            .create_expression("${body.order.@id}")
1274            .unwrap()
1275            .evaluate(&ex)
1276            .await
1277            .unwrap();
1278        assert_eq!(val, json!("123"));
1279    }
1280
1281    #[tokio::test]
1282    async fn body_field_xml_hash_text() {
1283        // XML: <status active="true">pending</status> → JSON: {"status": {"@active": "true", "#text": "pending"}}
1284        let lang = SimpleLanguage::new();
1285        let mut ex = Exchange::default();
1286        ex.input.body = Body::Json(json!({"status": {"@active": "true", "#text": "pending"}}));
1287        let val = lang
1288            .create_expression("${body.status.#text}")
1289            .unwrap()
1290            .evaluate(&ex)
1291            .await
1292            .unwrap();
1293        assert_eq!(val, json!("pending"));
1294    }
1295
1296    #[tokio::test]
1297    async fn body_field_xml_array_items() {
1298        // XML: <order><item>coffee</item><item>tea</item></order> → JSON: {"order": {"item": ["coffee", "tea"]}}
1299        let lang = SimpleLanguage::new();
1300        let mut ex = Exchange::default();
1301        ex.input.body = Body::Json(json!({"order": {"item": ["coffee", "tea"]}}));
1302        let val0 = lang
1303            .create_expression("${body.order.item.0}")
1304            .unwrap()
1305            .evaluate(&ex)
1306            .await
1307            .unwrap();
1308        assert_eq!(val0, json!("coffee"));
1309        let val1 = lang
1310            .create_expression("${body.order.item.1}")
1311            .unwrap()
1312            .evaluate(&ex)
1313            .await
1314            .unwrap();
1315        assert_eq!(val1, json!("tea"));
1316    }
1317
1318    #[tokio::test]
1319    async fn body_field_xml_array_with_attrs() {
1320        // XML: <root><item id="1">a</item><item id="2">b</item></root>
1321        // → JSON: {"root": {"item": [{"@id": "1", "#text": "a"}, {"@id": "2", "#text": "b"}]}}
1322        let lang = SimpleLanguage::new();
1323        let mut ex = Exchange::default();
1324        ex.input.body = Body::Json(
1325            json!({"root": {"item": [{"@id": "1", "#text": "a"}, {"@id": "2", "#text": "b"}]}}),
1326        );
1327        let val = lang
1328            .create_expression("${body.root.item.0.@id}")
1329            .unwrap()
1330            .evaluate(&ex)
1331            .await
1332            .unwrap();
1333        assert_eq!(val, json!("1"));
1334        let val_text = lang
1335            .create_expression("${body.root.item.1.#text}")
1336            .unwrap()
1337            .evaluate(&ex)
1338            .await
1339            .unwrap();
1340        assert_eq!(val_text, json!("b"));
1341    }
1342
1343    #[tokio::test]
1344    async fn body_field_xml_predicate_on_attr() {
1345        // Predicate: ${body.order.@id} == '123'
1346        let lang = SimpleLanguage::new();
1347        let mut ex = Exchange::default();
1348        ex.input.body = Body::Json(json!({"order": {"@id": "123", "name": "test"}}));
1349        let pred = lang.create_predicate("${body.order.@id} == '123'").unwrap();
1350        assert!(pred.matches(&ex).await.unwrap());
1351    }
1352}
1353
1354#[cfg(test)]
1355mod public_api_tests {
1356    use super::SimpleLanguage;
1357    use camel_language_api::{Exchange, Expression, Language, LanguageError, Predicate, Value};
1358    use std::sync::Arc;
1359
1360    struct BoolExpression(bool);
1361
1362    #[async_trait::async_trait]
1363    impl Expression for BoolExpression {
1364        async fn evaluate(&self, _exchange: &Exchange) -> Result<Value, LanguageError> {
1365            Ok(Value::Bool(self.0))
1366        }
1367    }
1368
1369    struct BoolLanguage;
1370
1371    impl Language for BoolLanguage {
1372        fn name(&self) -> &'static str {
1373            "boollang"
1374        }
1375
1376        fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
1377            Ok(Box::new(BoolExpression(script == "yes")))
1378        }
1379
1380        fn create_predicate(&self, _script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
1381            Err(LanguageError::NotSupported {
1382                feature: "predicate".to_string(),
1383                language: "boollang".to_string(),
1384            })
1385        }
1386    }
1387
1388    #[tokio::test]
1389    async fn language_name_and_default() {
1390        let lang = SimpleLanguage::default();
1391        assert_eq!(lang.name(), "simple");
1392    }
1393
1394    #[tokio::test]
1395    async fn create_expression_and_predicate_propagate_parse_errors() {
1396        let lang = SimpleLanguage::new();
1397        assert!(lang.create_expression("${header.}").is_err());
1398        assert!(lang.create_predicate("${exchangeProperty.}").is_err());
1399    }
1400
1401    #[tokio::test]
1402    async fn with_resolver_allows_delegate_expressions() {
1403        let resolver = Arc::new(|name: &str| {
1404            if name == "boollang" {
1405                Some(Arc::new(BoolLanguage) as Arc<dyn Language>)
1406            } else {
1407                None
1408            }
1409        });
1410        let lang = SimpleLanguage::with_resolver(resolver);
1411        let expr = lang.create_expression("${boollang:yes}").unwrap();
1412        let val = expr.evaluate(&Exchange::default()).await.unwrap();
1413        assert_eq!(val, Value::Bool(true));
1414    }
1415}