Skip to main content

camel_language_simple/
lib.rs

1mod evaluator;
2mod parser;
3
4use camel_language_api::{Exchange, Expression, Language, LanguageError, Predicate, Value};
5
6pub struct SimpleLanguage;
7
8struct SimpleExpression(parser::Expr);
9struct SimplePredicate(parser::Expr);
10
11impl Expression for SimpleExpression {
12    fn evaluate(&self, exchange: &Exchange) -> Result<Value, LanguageError> {
13        evaluator::evaluate(&self.0, exchange)
14    }
15}
16
17impl Predicate for SimplePredicate {
18    fn matches(&self, exchange: &Exchange) -> Result<bool, LanguageError> {
19        let val = evaluator::evaluate(&self.0, exchange)?;
20        Ok(match &val {
21            Value::Bool(b) => *b,
22            Value::Null => false,
23            _ => true,
24        })
25    }
26}
27
28impl Language for SimpleLanguage {
29    fn name(&self) -> &'static str {
30        "simple"
31    }
32
33    fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
34        let ast = parser::parse(script)?;
35        Ok(Box::new(SimpleExpression(ast)))
36    }
37
38    fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
39        let ast = parser::parse(script)?;
40        Ok(Box::new(SimplePredicate(ast)))
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::SimpleLanguage;
47    use camel_language_api::Language;
48    use camel_language_api::{Exchange, Message, Value};
49
50    fn exchange_with_header(key: &str, val: &str) -> Exchange {
51        let mut msg = Message::default();
52        msg.set_header(key, Value::String(val.to_string()));
53        Exchange::new(msg)
54    }
55
56    fn exchange_with_body(body: &str) -> Exchange {
57        Exchange::new(Message::new(body))
58    }
59
60    #[test]
61    fn test_header_equals_string() {
62        let lang = SimpleLanguage;
63        let pred = lang.create_predicate("${header.type} == 'order'").unwrap();
64        let ex = exchange_with_header("type", "order");
65        assert!(pred.matches(&ex).unwrap());
66    }
67
68    #[test]
69    fn test_header_not_equals() {
70        let lang = SimpleLanguage;
71        let pred = lang.create_predicate("${header.type} != 'order'").unwrap();
72        let ex = exchange_with_header("type", "invoice");
73        assert!(pred.matches(&ex).unwrap());
74    }
75
76    #[test]
77    fn test_body_contains() {
78        let lang = SimpleLanguage;
79        let pred = lang.create_predicate("${body} contains 'hello'").unwrap();
80        let ex = exchange_with_body("say hello world");
81        assert!(pred.matches(&ex).unwrap());
82    }
83
84    #[test]
85    fn test_header_null_check() {
86        let lang = SimpleLanguage;
87        let pred = lang.create_predicate("${header.missing} == null").unwrap();
88        let ex = exchange_with_body("anything");
89        assert!(pred.matches(&ex).unwrap());
90    }
91
92    #[test]
93    fn test_header_not_null() {
94        let lang = SimpleLanguage;
95        let pred = lang.create_predicate("${header.type} != null").unwrap();
96        let ex = exchange_with_header("type", "order");
97        assert!(pred.matches(&ex).unwrap());
98    }
99
100    #[test]
101    fn test_expression_header_value() {
102        let lang = SimpleLanguage;
103        let expr = lang.create_expression("${header.type}").unwrap();
104        let ex = exchange_with_header("type", "order");
105        let val = expr.evaluate(&ex).unwrap();
106        assert_eq!(val, Value::String("order".to_string()));
107    }
108
109    #[test]
110    fn test_expression_body() {
111        let lang = SimpleLanguage;
112        let expr = lang.create_expression("${body}").unwrap();
113        let ex = exchange_with_body("hello");
114        let val = expr.evaluate(&ex).unwrap();
115        assert_eq!(val, Value::String("hello".to_string()));
116    }
117
118    #[test]
119    fn test_numeric_comparison() {
120        let lang = SimpleLanguage;
121        let pred = lang.create_predicate("${header.age} > 18").unwrap();
122        let mut ex = Exchange::new(Message::default());
123        ex.input.set_header("age", Value::Number(25.into()));
124        assert!(pred.matches(&ex).unwrap());
125    }
126
127    // --- Edge case tests ---
128
129    #[test]
130    fn test_empty_body() {
131        let lang = SimpleLanguage;
132        let expr = lang.create_expression("${body}").unwrap();
133        let ex = Exchange::new(Message::default());
134        let val = expr.evaluate(&ex).unwrap();
135        assert_eq!(val, Value::Null);
136    }
137
138    #[test]
139    fn test_parse_error_unrecognized_token() {
140        // A pure `${...}` token that doesn't match any known form is a parse error.
141        // For example `${unknown}` is not a valid Simple expression.
142        let lang = SimpleLanguage;
143        let result = lang.create_expression("${unknown}");
144        assert!(result.is_err(), "unknown token should be a parse error");
145    }
146
147    #[test]
148    fn test_empty_header_key_is_parse_error() {
149        let lang = SimpleLanguage;
150        let result = lang.create_expression("${header.}");
151        let err = result.err().expect("should be a parse error");
152        let err = format!("{err}");
153        assert!(
154            err.contains("empty"),
155            "error should mention empty key, got: {err}"
156        );
157    }
158
159    #[test]
160    fn test_empty_exchange_property_key_is_parse_error() {
161        let lang = SimpleLanguage;
162        let result = lang.create_expression("${exchangeProperty.}");
163        let err = result.err().expect("should be a parse error");
164        let err = format!("{err}");
165        assert!(
166            err.contains("empty"),
167            "error should mention empty key, got: {err}"
168        );
169    }
170
171    #[test]
172    fn test_missing_header_returns_null() {
173        let lang = SimpleLanguage;
174        let expr = lang.create_expression("${header.nonexistent}").unwrap();
175        let ex = exchange_with_body("anything");
176        let val = expr.evaluate(&ex).unwrap();
177        assert_eq!(val, Value::Null);
178    }
179
180    #[test]
181    fn test_exchange_property_expression() {
182        let lang = SimpleLanguage;
183        let expr = lang
184            .create_expression("${exchangeProperty.myProp}")
185            .unwrap();
186        let mut ex = exchange_with_body("test");
187        ex.set_property("myProp".to_string(), Value::String("propVal".to_string()));
188        let val = expr.evaluate(&ex).unwrap();
189        assert_eq!(val, Value::String("propVal".to_string()));
190    }
191
192    #[test]
193    fn test_missing_property_returns_null() {
194        let lang = SimpleLanguage;
195        let expr = lang
196            .create_expression("${exchangeProperty.missing}")
197            .unwrap();
198        let ex = exchange_with_body("test");
199        let val = expr.evaluate(&ex).unwrap();
200        assert_eq!(val, Value::Null);
201    }
202
203    #[test]
204    fn test_string_literal_expression() {
205        let lang = SimpleLanguage;
206        let expr = lang.create_expression("'hello'").unwrap();
207        let ex = exchange_with_body("test");
208        let val = expr.evaluate(&ex).unwrap();
209        assert_eq!(val, Value::String("hello".to_string()));
210    }
211
212    #[test]
213    fn test_double_quoted_literal_unescapes_newline() {
214        let lang = SimpleLanguage;
215        let expr = lang.create_expression("\"line1\\nline2\"").unwrap();
216        let ex = exchange_with_body("test");
217        let val = expr.evaluate(&ex).unwrap();
218        assert_eq!(val, Value::String("line1\nline2".to_string()));
219    }
220
221    #[test]
222    fn test_double_quoted_literal_unescapes_tab_and_quote() {
223        let lang = SimpleLanguage;
224        let expr = lang.create_expression("\"col1\\t\\\"quoted\\\"\"").unwrap();
225        let ex = exchange_with_body("test");
226        let val = expr.evaluate(&ex).unwrap();
227        assert_eq!(val, Value::String("col1\t\"quoted\"".to_string()));
228    }
229
230    #[test]
231    fn test_double_quoted_literal_unescapes_backspace_formfeed_and_slash() {
232        let lang = SimpleLanguage;
233        let expr = lang.create_expression("\"a\\bb\\fc\\/d\"").unwrap();
234        let ex = exchange_with_body("test");
235        let val = expr.evaluate(&ex).unwrap();
236        assert_eq!(val, Value::String("a\u{0008}b\u{000C}c/d".to_string()));
237    }
238
239    #[test]
240    fn test_null_expression() {
241        let lang = SimpleLanguage;
242        let expr = lang.create_expression("null").unwrap();
243        let ex = exchange_with_body("test");
244        let val = expr.evaluate(&ex).unwrap();
245        assert_eq!(val, Value::Null);
246    }
247
248    #[test]
249    fn test_predicate_null_is_false() {
250        let lang = SimpleLanguage;
251        let pred = lang.create_predicate("${header.missing}").unwrap();
252        let ex = exchange_with_body("test");
253        assert!(!pred.matches(&ex).unwrap());
254    }
255
256    #[test]
257    fn test_predicate_non_null_is_true() {
258        let lang = SimpleLanguage;
259        let pred = lang.create_predicate("${header.type}").unwrap();
260        let ex = exchange_with_header("type", "order");
261        assert!(pred.matches(&ex).unwrap());
262    }
263
264    #[test]
265    fn test_contains_not_found() {
266        let lang = SimpleLanguage;
267        let pred = lang.create_predicate("${body} contains 'xyz'").unwrap();
268        let ex = exchange_with_body("hello world");
269        assert!(!pred.matches(&ex).unwrap());
270    }
271
272    #[test]
273    fn test_less_than_or_equal() {
274        let lang = SimpleLanguage;
275        let pred = lang.create_predicate("${header.age} <= 18").unwrap();
276        let mut ex = Exchange::new(Message::default());
277        ex.input.set_header("age", Value::Number(18.into()));
278        assert!(pred.matches(&ex).unwrap());
279    }
280
281    // --- Mixed interpolation tests ---
282
283    #[test]
284    fn test_interpolated_text_with_header() {
285        // "Exchange #${header.CamelTimerCounter}" → "Exchange #42"
286        let lang = SimpleLanguage;
287        let expr = lang
288            .create_expression("Exchange #${header.CamelTimerCounter}")
289            .unwrap();
290        let ex = exchange_with_header("CamelTimerCounter", "42");
291        let val = expr.evaluate(&ex).unwrap();
292        assert_eq!(val, Value::String("Exchange #42".to_string()));
293    }
294
295    #[test]
296    fn test_interpolated_text_with_body() {
297        // "Got ${body}" → "Got hello"
298        let lang = SimpleLanguage;
299        let expr = lang.create_expression("Got ${body}").unwrap();
300        let ex = exchange_with_body("hello");
301        let val = expr.evaluate(&ex).unwrap();
302        assert_eq!(val, Value::String("Got hello".to_string()));
303    }
304
305    #[test]
306    fn test_interpolated_multiple_expressions() {
307        // "Transformed: ${body} (source=${header.source})" → real values
308        let lang = SimpleLanguage;
309        let expr = lang
310            .create_expression("Transformed: ${body} (source=${header.source})")
311            .unwrap();
312        let mut msg = Message::new("data");
313        msg.set_header("source", Value::String("kafka".to_string()));
314        let ex = Exchange::new(msg);
315        let val = expr.evaluate(&ex).unwrap();
316        assert_eq!(
317            val,
318            Value::String("Transformed: data (source=kafka)".to_string())
319        );
320    }
321
322    #[test]
323    fn test_interpolated_missing_header_becomes_empty() {
324        // Missing header in interpolated string yields empty string for that slot
325        let lang = SimpleLanguage;
326        let expr = lang
327            .create_expression("prefix-${header.missing}-suffix")
328            .unwrap();
329        let ex = exchange_with_body("x");
330        let val = expr.evaluate(&ex).unwrap();
331        assert_eq!(val, Value::String("prefix--suffix".to_string()));
332    }
333
334    #[test]
335    fn test_interpolated_text_only_no_expressions() {
336        // Plain text with no ${...} — treated as a literal (no interpolation needed,
337        // but must still work without error)
338        let lang = SimpleLanguage;
339        let expr = lang.create_expression("Hello World").unwrap();
340        let ex = exchange_with_body("x");
341        let val = expr.evaluate(&ex).unwrap();
342        assert_eq!(val, Value::String("Hello World".to_string()));
343    }
344
345    #[test]
346    fn test_interpolated_unclosed_brace_treated_as_literal() {
347        // An unclosed `${` has no matching `}` — the remainder is treated as
348        // plain literal text rather than causing a parse error.
349        let lang = SimpleLanguage;
350        let expr = lang.create_expression("Got ${body").unwrap();
351        let ex = exchange_with_body("hello");
352        let val = expr.evaluate(&ex).unwrap();
353        assert_eq!(val, Value::String("Got ${body".to_string()));
354    }
355
356    #[test]
357    fn test_operator_inside_string_literal_not_split() {
358        // The string literal 'a>=b' contains '>=' but must NOT be parsed as a
359        // BinOp split — the whole thing is a StringLit atom.
360        let lang = SimpleLanguage;
361        let result = lang.create_expression("'a>=b'");
362        let val = result
363            .unwrap()
364            .evaluate(&Exchange::new(Message::default()))
365            .unwrap();
366        assert_eq!(
367            val,
368            Value::String("a>=b".to_string()),
369            "string literal 'a>=b' should be parsed as a plain string, not split on >="
370        );
371    }
372
373    #[test]
374    fn test_header_eq_string_literal_containing_operator() {
375        // ${header.x} == 'a>=b' — the operator inside the RHS string must not
376        // cause the parser to split the LHS at the wrong position.
377        let lang = SimpleLanguage;
378        let pred = lang.create_predicate("${header.x} == 'a>=b'").unwrap();
379        let ex = exchange_with_header("x", "a>=b");
380        assert!(
381            pred.matches(&ex).unwrap(),
382            "predicate should match when header equals 'a>=b'"
383        );
384    }
385
386    #[test]
387    fn test_body_empty_is_null() {
388        let lang = SimpleLanguage;
389        let expr = lang.create_expression("${body}").unwrap();
390        let ex = Exchange::new(Message::default());
391        let val = expr.evaluate(&ex).unwrap();
392        assert_eq!(val, Value::Null);
393    }
394
395    #[test]
396    fn test_body_empty_not_null_is_false() {
397        let lang = SimpleLanguage;
398        let pred = lang.create_predicate("${body} != null").unwrap();
399        let ex = Exchange::new(Message::default());
400        assert!(!pred.matches(&ex).unwrap());
401    }
402
403    #[test]
404    fn test_body_empty_predicate_is_false() {
405        let lang = SimpleLanguage;
406        let pred = lang.create_predicate("${body}").unwrap();
407        let ex = Exchange::new(Message::default());
408        assert!(!pred.matches(&ex).unwrap());
409    }
410
411    #[test]
412    fn test_logical_and_true_true() {
413        let lang = SimpleLanguage;
414        let pred = lang
415            .create_predicate("${header.a} == '1' && ${header.b} == '2'")
416            .unwrap();
417        let mut msg = Message::default();
418        msg.set_header("a", Value::String("1".to_string()));
419        msg.set_header("b", Value::String("2".to_string()));
420        let ex = Exchange::new(msg);
421        assert!(pred.matches(&ex).unwrap());
422    }
423
424    #[test]
425    fn test_logical_and_true_false() {
426        let lang = SimpleLanguage;
427        let pred = lang
428            .create_predicate("${header.a} == '1' && ${header.b} == '2'")
429            .unwrap();
430        let mut msg = Message::default();
431        msg.set_header("a", Value::String("1".to_string()));
432        msg.set_header("b", Value::String("99".to_string()));
433        let ex = Exchange::new(msg);
434        assert!(!pred.matches(&ex).unwrap());
435    }
436
437    #[test]
438    fn test_logical_or_false_true() {
439        let lang = SimpleLanguage;
440        let pred = lang
441            .create_predicate("${header.a} == '1' || ${header.b} == '2'")
442            .unwrap();
443        let mut msg = Message::default();
444        msg.set_header("a", Value::String("0".to_string()));
445        msg.set_header("b", Value::String("2".to_string()));
446        let ex = Exchange::new(msg);
447        assert!(pred.matches(&ex).unwrap());
448    }
449
450    #[test]
451    fn test_logical_or_false_false() {
452        let lang = SimpleLanguage;
453        let pred = lang
454            .create_predicate("${header.a} == '1' || ${header.b} == '2'")
455            .unwrap();
456        let mut msg = Message::default();
457        msg.set_header("a", Value::String("0".to_string()));
458        msg.set_header("b", Value::String("0".to_string()));
459        let ex = Exchange::new(msg);
460        assert!(!pred.matches(&ex).unwrap());
461    }
462
463    #[test]
464    fn test_logical_and_precedence_over_or() {
465        let lang = SimpleLanguage;
466        let pred = lang
467            .create_predicate("${header.a} == '1' || ${header.b} == '2' && ${header.c} == '3'")
468            .unwrap();
469        let mut msg = Message::default();
470        msg.set_header("a", Value::String("1".to_string()));
471        msg.set_header("b", Value::String("2".to_string()));
472        msg.set_header("c", Value::String("999".to_string()));
473        let ex = Exchange::new(msg);
474        assert!(pred.matches(&ex).unwrap());
475    }
476
477    #[test]
478    fn test_logical_and_short_circuit() {
479        let lang = SimpleLanguage;
480        let pred = lang
481            .create_predicate("${header.a} == 'x' && ${header.nonexistent} > 999")
482            .unwrap();
483        let mut msg = Message::default();
484        msg.set_header("a", Value::String("not-x".to_string()));
485        let ex = Exchange::new(msg);
486        assert!(!pred.matches(&ex).unwrap());
487    }
488
489    #[test]
490    fn test_logical_or_short_circuit() {
491        let lang = SimpleLanguage;
492        let pred = lang
493            .create_predicate("${header.a} == '1' || ${header.nonexistent} > 999")
494            .unwrap();
495        let mut msg = Message::default();
496        msg.set_header("a", Value::String("1".to_string()));
497        let ex = Exchange::new(msg);
498        assert!(pred.matches(&ex).unwrap());
499    }
500
501    #[test]
502    fn test_compound_filter_body_not_null_and_body_not_empty() {
503        let lang = SimpleLanguage;
504        let pred = lang
505            .create_predicate("${body} != null && ${body} != ''")
506            .unwrap();
507        let ex = exchange_with_body("hello");
508        assert!(pred.matches(&ex).unwrap());
509
510        let ex_empty = Exchange::new(Message::default());
511        assert!(!pred.matches(&ex_empty).unwrap());
512    }
513
514    #[test]
515    fn test_header_with_gt_in_key() {
516        let lang = SimpleLanguage;
517        let mut msg = Message::default();
518        msg.set_header("a>b", Value::String("found".to_string()));
519        let pred = lang.create_predicate("${header.a>b} == 'found'").unwrap();
520        let ex = Exchange::new(msg);
521        assert!(pred.matches(&ex).unwrap());
522    }
523
524    #[test]
525    fn test_double_quoted_string_with_operator() {
526        let lang = SimpleLanguage;
527        let expr = lang.create_expression("\"a >= b\"").unwrap();
528        let ex = exchange_with_body("test");
529        let val = expr.evaluate(&ex).unwrap();
530        assert_eq!(val, Value::String("a >= b".to_string()));
531    }
532
533    #[test]
534    fn test_bool_literal_true() {
535        let lang = SimpleLanguage;
536        let expr = lang.create_expression("true").unwrap();
537        let ex = exchange_with_body("test");
538        let val = expr.evaluate(&ex).unwrap();
539        assert_eq!(val, Value::Bool(true));
540    }
541
542    #[test]
543    fn test_bool_literal_false() {
544        let lang = SimpleLanguage;
545        let expr = lang.create_expression("false").unwrap();
546        let ex = exchange_with_body("test");
547        let val = expr.evaluate(&ex).unwrap();
548        assert_eq!(val, Value::Bool(false));
549    }
550
551    #[test]
552    fn test_bool_literal_in_comparison() {
553        use camel_language_api::Body;
554
555        let lang = SimpleLanguage;
556        let mut ex = Exchange::default();
557        ex.input.body = Body::Json(serde_json::json!({"active": true}));
558        let pred = lang.create_predicate("${body.active} == true").unwrap();
559        assert!(pred.matches(&ex).unwrap());
560    }
561
562    #[test]
563    fn test_null_gt_number_is_false() {
564        let lang = SimpleLanguage;
565        let pred = lang.create_predicate("${header.missing} > 5").unwrap();
566        let ex = exchange_with_body("test");
567        assert!(!pred.matches(&ex).unwrap());
568    }
569
570    #[test]
571    fn test_null_lt_number_is_false() {
572        let lang = SimpleLanguage;
573        let pred = lang.create_predicate("${header.missing} < 10").unwrap();
574        let ex = exchange_with_body("test");
575        assert!(!pred.matches(&ex).unwrap());
576    }
577
578    #[test]
579    fn test_null_gte_number_is_false() {
580        let lang = SimpleLanguage;
581        let pred = lang.create_predicate("${header.missing} >= 0").unwrap();
582        let ex = exchange_with_body("test");
583        assert!(!pred.matches(&ex).unwrap());
584    }
585
586    #[test]
587    fn test_null_lte_number_is_false() {
588        let lang = SimpleLanguage;
589        let pred = lang.create_predicate("${header.missing} <= 100").unwrap();
590        let ex = exchange_with_body("test");
591        assert!(!pred.matches(&ex).unwrap());
592    }
593
594    #[test]
595    fn test_number_gt_null_is_false() {
596        let lang = SimpleLanguage;
597        let pred = lang.create_predicate("5 > ${header.missing}").unwrap();
598        let ex = exchange_with_body("test");
599        assert!(!pred.matches(&ex).unwrap());
600    }
601
602    #[test]
603    fn test_true_or_false() {
604        let lang = SimpleLanguage;
605        let pred = lang.create_predicate("true || false").unwrap();
606        let ex = exchange_with_body("test");
607        assert!(pred.matches(&ex).unwrap());
608    }
609
610    #[test]
611    fn test_false_and_true() {
612        let lang = SimpleLanguage;
613        let pred = lang.create_predicate("false && true").unwrap();
614        let ex = exchange_with_body("test");
615        assert!(!pred.matches(&ex).unwrap());
616    }
617
618    #[test]
619    fn test_contains_null_left_is_false() {
620        let lang = SimpleLanguage;
621        let pred = lang
622            .create_predicate("${header.missing} contains 'x'")
623            .unwrap();
624        let ex = exchange_with_body("test");
625        assert!(!pred.matches(&ex).unwrap());
626    }
627
628    #[test]
629    fn test_contains_null_right_is_false() {
630        let lang = SimpleLanguage;
631        let pred = lang.create_predicate("${body} contains null").unwrap();
632        let ex = exchange_with_body("test");
633        assert!(!pred.matches(&ex).unwrap());
634    }
635
636    #[test]
637    fn test_contains_without_spaces() {
638        let lang = SimpleLanguage;
639        let pred = lang.create_predicate("${body}contains'hello'").unwrap();
640        let ex = exchange_with_body("say hello world");
641        assert!(pred.matches(&ex).unwrap());
642    }
643
644    #[test]
645    fn test_non_finite_number_parse_error() {
646        let lang = SimpleLanguage;
647        // Very long digit string that overflows f64 to infinity
648        let result = lang.create_expression(
649            "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
650        );
651        assert!(result.is_err(), "non-finite number should be a parse error");
652    }
653
654    #[test]
655    fn test_interpolation_with_empty_body_still_produces_text() {
656        let lang = SimpleLanguage;
657        let expr = lang.create_expression("Got ${body}").unwrap();
658        let ex = Exchange::new(Message::default());
659        let val = expr.evaluate(&ex).unwrap();
660        assert_eq!(val, Value::String("Got ".to_string()));
661    }
662
663    #[test]
664    fn test_interpolation_with_empty_body_and_trailing_text() {
665        let lang = SimpleLanguage;
666        let expr = lang.create_expression("${body} tail").unwrap();
667        let ex = Exchange::new(Message::default());
668        let val = expr.evaluate(&ex).unwrap();
669        assert_eq!(val, Value::String(" tail".to_string()));
670    }
671}
672
673#[cfg(test)]
674mod body_field_parser_tests {
675    use crate::parser::{Expr, PathSegment, parse};
676
677    #[test]
678    fn parse_body_field_simple_key() {
679        let expr = parse("${body.name}").unwrap();
680        assert_eq!(
681            expr,
682            Expr::BodyField(vec![PathSegment::Key("name".to_string())])
683        );
684    }
685
686    #[test]
687    fn parse_body_field_nested() {
688        let expr = parse("${body.user.city}").unwrap();
689        assert_eq!(
690            expr,
691            Expr::BodyField(vec![
692                PathSegment::Key("user".to_string()),
693                PathSegment::Key("city".to_string()),
694            ])
695        );
696    }
697
698    #[test]
699    fn parse_body_field_array_index() {
700        let expr = parse("${body.items.0}").unwrap();
701        assert_eq!(
702            expr,
703            Expr::BodyField(vec![
704                PathSegment::Key("items".to_string()),
705                PathSegment::Index(0),
706            ])
707        );
708    }
709
710    #[test]
711    fn parse_body_field_array_nested() {
712        let expr = parse("${body.users.0.name}").unwrap();
713        assert_eq!(
714            expr,
715            Expr::BodyField(vec![
716                PathSegment::Key("users".to_string()),
717                PathSegment::Index(0),
718                PathSegment::Key("name".to_string()),
719            ])
720        );
721    }
722
723    #[test]
724    fn parse_body_field_empty_segment_error() {
725        let result = parse("${body.}");
726        assert!(result.is_err());
727    }
728
729    #[test]
730    fn parse_body_field_exact_still_works() {
731        // Regression: ${body} must still produce Expr::Body, not BodyField
732        let expr = parse("${body}").unwrap();
733        assert_eq!(expr, Expr::Body);
734    }
735
736    #[test]
737    fn parse_body_field_double_dots_error() {
738        // ${body..name} has an empty segment between the two dots
739        let result = parse("${body..name}");
740        assert!(result.is_err());
741    }
742
743    #[test]
744    fn parse_body_field_index_only() {
745        // ${body.0} — single index segment (e.g. body is a JSON array)
746        let expr = parse("${body.0}").unwrap();
747        assert_eq!(expr, Expr::BodyField(vec![PathSegment::Index(0)]));
748    }
749
750    #[test]
751    fn parse_body_field_leading_zero_is_key() {
752        // ${body.01} — leading zero means it's a string key, not an array index
753        let expr = parse("${body.01}").unwrap();
754        assert_eq!(
755            expr,
756            Expr::BodyField(vec![PathSegment::Key("01".to_string())])
757        );
758    }
759}
760
761#[cfg(test)]
762mod body_field_eval_tests {
763    use crate::SimpleLanguage;
764    use camel_language_api::Language;
765    use camel_language_api::{Body, Exchange, Value};
766    use serde_json::json;
767
768    fn eval(expr_str: &str, body: Body) -> Value {
769        let mut ex = Exchange::default();
770        ex.input.body = body;
771        let lang = SimpleLanguage;
772        lang.create_expression(expr_str)
773            .unwrap()
774            .evaluate(&ex)
775            .unwrap()
776    }
777
778    #[test]
779    fn body_field_simple_key() {
780        let result = eval("${body.name}", Body::Json(json!({"name": "Alice"})));
781        assert_eq!(result, json!("Alice"));
782    }
783
784    #[test]
785    fn body_field_number_value() {
786        let result = eval("${body.age}", Body::Json(json!({"age": 30})));
787        assert_eq!(result, json!(30));
788    }
789
790    #[test]
791    fn body_field_bool_value() {
792        let result = eval("${body.active}", Body::Json(json!({"active": true})));
793        assert_eq!(result, json!(true));
794    }
795
796    #[test]
797    fn body_field_nested() {
798        let result = eval(
799            "${body.user.city}",
800            Body::Json(json!({"user": {"city": "Madrid"}})),
801        );
802        assert_eq!(result, json!("Madrid"));
803    }
804
805    #[test]
806    fn body_field_array_index() {
807        let result = eval("${body.items.0}", Body::Json(json!({"items": ["a", "b"]})));
808        assert_eq!(result, json!("a"));
809    }
810
811    #[test]
812    fn body_field_array_nested() {
813        let result = eval(
814            "${body.users.0.name}",
815            Body::Json(json!({"users": [{"name": "Bob"}]})),
816        );
817        assert_eq!(result, json!("Bob"));
818    }
819
820    #[test]
821    fn body_field_missing_key_returns_null() {
822        let result = eval("${body.missing}", Body::Json(json!({"name": "Alice"})));
823        assert_eq!(result, Value::Null);
824    }
825
826    #[test]
827    fn body_field_missing_nested_returns_null() {
828        let result = eval("${body.a.b.c}", Body::Json(json!({"a": {"x": 1}})));
829        assert_eq!(result, Value::Null);
830    }
831
832    #[test]
833    fn body_field_out_of_bounds_index_returns_null() {
834        let result = eval("${body.items.5}", Body::Json(json!({"items": ["a"]})));
835        assert_eq!(result, Value::Null);
836    }
837
838    #[test]
839    fn body_field_non_json_body_returns_null() {
840        let result = eval(
841            "${body.name}",
842            Body::Text(r#"{"name":"Alice"}"#.to_string()),
843        );
844        assert_eq!(result, Value::Null);
845    }
846
847    #[test]
848    fn body_field_empty_body_returns_null() {
849        let result = eval("${body.name}", Body::Empty);
850        assert_eq!(result, Value::Null);
851    }
852
853    #[test]
854    fn body_field_in_interpolation() {
855        let result = eval("Hello ${body.name}!", Body::Json(json!({"name": "Alice"})));
856        assert_eq!(result, json!("Hello Alice!"));
857    }
858
859    #[test]
860    fn body_field_in_predicate_true() {
861        let lang = SimpleLanguage;
862        let mut ex = Exchange::default();
863        ex.input.body = Body::Json(json!({"status": "active"}));
864        let result = lang
865            .create_expression("${body.status} == 'active'")
866            .unwrap()
867            .evaluate(&ex)
868            .unwrap();
869        assert_eq!(result, Value::Bool(true));
870    }
871
872    #[test]
873    fn body_field_in_predicate_false() {
874        let lang = SimpleLanguage;
875        let mut ex = Exchange::default();
876        ex.input.body = Body::Json(json!({"status": "inactive"}));
877        let result = lang
878            .create_expression("${body.status} == 'active'")
879            .unwrap()
880            .evaluate(&ex)
881            .unwrap();
882        assert_eq!(result, Value::Bool(false));
883    }
884
885    #[test]
886    fn body_field_bytes_body_returns_null() {
887        // Note: Using Body::Text here instead of Body::Bytes since bytes crate
888        // is not available in test context. Both should behave the same for JSON field access.
889        let result = eval(
890            "${body.name}",
891            Body::Text(r#"{"name":"Alice"}"#.to_string()),
892        );
893        assert_eq!(result, Value::Null);
894    }
895
896    #[test]
897    fn body_field_json_null_value_returns_null() {
898        // key exists but its value is JSON null → returns Value::Null
899        let result = eval(
900            "${body.name}",
901            Body::Json(serde_json::json!({"name": null})),
902        );
903        assert_eq!(result, Value::Null);
904    }
905
906    #[test]
907    fn body_field_numeric_predicate() {
908        // JSON number 42.0 compares equal to the parsed number 42 from the
909        // Simple Language expression, because both resolve to the same f64.
910        let lang = SimpleLanguage;
911        let mut ex = Exchange::default();
912        ex.input.body = Body::Json(json!({"score": 42.0}));
913        let result = lang
914            .create_expression("${body.score} == 42")
915            .unwrap()
916            .evaluate(&ex)
917            .unwrap();
918        assert_eq!(result, Value::Bool(true));
919    }
920
921    #[test]
922    fn body_bytes_utf8_returns_string() {
923        // Body::Bytes with valid UTF-8 content should be readable via ${body}
924        let lang = SimpleLanguage;
925        let mut ex = Exchange::default();
926        ex.input.body = Body::from(b"hello from bytes".to_vec());
927        let val = lang
928            .create_expression("${body}")
929            .unwrap()
930            .evaluate(&ex)
931            .unwrap();
932        assert_eq!(val, Value::String("hello from bytes".to_string()));
933    }
934
935    #[test]
936    fn body_json_returns_serialized_string() {
937        // Body::Json should be serialized to a JSON string when accessed via ${body}
938        let lang = SimpleLanguage;
939        let mut ex = Exchange::default();
940        ex.input.body = Body::Json(json!({"msg": "world"}));
941        let val = lang
942            .create_expression("${body}")
943            .unwrap()
944            .evaluate(&ex)
945            .unwrap();
946        // The result should be the JSON serialization, not empty
947        let s = match val {
948            Value::String(s) => s,
949            other => panic!("expected String, got {other:?}"),
950        };
951        assert!(!s.is_empty(), "${{body}} on Body::Json should not be empty");
952        let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
953        assert_eq!(parsed["msg"], "world");
954    }
955
956    #[test]
957    fn body_bytes_in_interpolation() {
958        // Body::Bytes should work in interpolated expressions like "Received: ${body}"
959        let lang = SimpleLanguage;
960        let mut ex = Exchange::default();
961        ex.input.body = Body::from(b"ping".to_vec());
962        let val = lang
963            .create_expression("Received: ${body}")
964            .unwrap()
965            .evaluate(&ex)
966            .unwrap();
967        assert_eq!(val, Value::String("Received: ping".to_string()));
968    }
969}