1mod 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 #[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 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 #[tokio::test]
329 async fn test_interpolated_text_with_header() {
330 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 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 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 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 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 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 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 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 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 #[tokio::test]
721 async fn test_string_header_eq_number_literal() {
722 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 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 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 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 #[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 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 let result = parse("${body..name}");
870 assert!(result.is_err());
871 }
872
873 #[tokio::test]
874 async fn parse_body_field_index_only() {
875 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 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 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 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 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 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 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 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 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 #[tokio::test]
1229 async fn body_field_xml_attr_key() {
1230 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 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 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 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 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}