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