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