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 #[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::String("".to_string()));
136 }
137
138 #[test]
139 fn test_parse_error_unrecognized_token() {
140 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 #[test]
284 fn test_interpolated_text_with_header() {
285 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 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 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 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 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 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 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 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
387#[cfg(test)]
388mod body_field_parser_tests {
389 use crate::parser::{Expr, PathSegment, parse};
390
391 #[test]
392 fn parse_body_field_simple_key() {
393 let expr = parse("${body.name}").unwrap();
394 assert_eq!(
395 expr,
396 Expr::BodyField(vec![PathSegment::Key("name".to_string())])
397 );
398 }
399
400 #[test]
401 fn parse_body_field_nested() {
402 let expr = parse("${body.user.city}").unwrap();
403 assert_eq!(
404 expr,
405 Expr::BodyField(vec![
406 PathSegment::Key("user".to_string()),
407 PathSegment::Key("city".to_string()),
408 ])
409 );
410 }
411
412 #[test]
413 fn parse_body_field_array_index() {
414 let expr = parse("${body.items.0}").unwrap();
415 assert_eq!(
416 expr,
417 Expr::BodyField(vec![
418 PathSegment::Key("items".to_string()),
419 PathSegment::Index(0),
420 ])
421 );
422 }
423
424 #[test]
425 fn parse_body_field_array_nested() {
426 let expr = parse("${body.users.0.name}").unwrap();
427 assert_eq!(
428 expr,
429 Expr::BodyField(vec![
430 PathSegment::Key("users".to_string()),
431 PathSegment::Index(0),
432 PathSegment::Key("name".to_string()),
433 ])
434 );
435 }
436
437 #[test]
438 fn parse_body_field_empty_segment_error() {
439 let result = parse("${body.}");
440 assert!(result.is_err());
441 }
442
443 #[test]
444 fn parse_body_field_exact_still_works() {
445 let expr = parse("${body}").unwrap();
447 assert_eq!(expr, Expr::Body);
448 }
449
450 #[test]
451 fn parse_body_field_double_dots_error() {
452 let result = parse("${body..name}");
454 assert!(result.is_err());
455 }
456
457 #[test]
458 fn parse_body_field_index_only() {
459 let expr = parse("${body.0}").unwrap();
461 assert_eq!(expr, Expr::BodyField(vec![PathSegment::Index(0)]));
462 }
463
464 #[test]
465 fn parse_body_field_leading_zero_is_key() {
466 let expr = parse("${body.01}").unwrap();
468 assert_eq!(
469 expr,
470 Expr::BodyField(vec![PathSegment::Key("01".to_string())])
471 );
472 }
473}
474
475#[cfg(test)]
476mod body_field_eval_tests {
477 use crate::SimpleLanguage;
478 use camel_language_api::Language;
479 use camel_language_api::{Body, Exchange, Value};
480 use serde_json::json;
481
482 fn eval(expr_str: &str, body: Body) -> Value {
483 let mut ex = Exchange::default();
484 ex.input.body = body;
485 let lang = SimpleLanguage;
486 lang.create_expression(expr_str)
487 .unwrap()
488 .evaluate(&ex)
489 .unwrap()
490 }
491
492 #[test]
493 fn body_field_simple_key() {
494 let result = eval("${body.name}", Body::Json(json!({"name": "Alice"})));
495 assert_eq!(result, json!("Alice"));
496 }
497
498 #[test]
499 fn body_field_number_value() {
500 let result = eval("${body.age}", Body::Json(json!({"age": 30})));
501 assert_eq!(result, json!(30));
502 }
503
504 #[test]
505 fn body_field_bool_value() {
506 let result = eval("${body.active}", Body::Json(json!({"active": true})));
507 assert_eq!(result, json!(true));
508 }
509
510 #[test]
511 fn body_field_nested() {
512 let result = eval(
513 "${body.user.city}",
514 Body::Json(json!({"user": {"city": "Madrid"}})),
515 );
516 assert_eq!(result, json!("Madrid"));
517 }
518
519 #[test]
520 fn body_field_array_index() {
521 let result = eval("${body.items.0}", Body::Json(json!({"items": ["a", "b"]})));
522 assert_eq!(result, json!("a"));
523 }
524
525 #[test]
526 fn body_field_array_nested() {
527 let result = eval(
528 "${body.users.0.name}",
529 Body::Json(json!({"users": [{"name": "Bob"}]})),
530 );
531 assert_eq!(result, json!("Bob"));
532 }
533
534 #[test]
535 fn body_field_missing_key_returns_null() {
536 let result = eval("${body.missing}", Body::Json(json!({"name": "Alice"})));
537 assert_eq!(result, Value::Null);
538 }
539
540 #[test]
541 fn body_field_missing_nested_returns_null() {
542 let result = eval("${body.a.b.c}", Body::Json(json!({"a": {"x": 1}})));
543 assert_eq!(result, Value::Null);
544 }
545
546 #[test]
547 fn body_field_out_of_bounds_index_returns_null() {
548 let result = eval("${body.items.5}", Body::Json(json!({"items": ["a"]})));
549 assert_eq!(result, Value::Null);
550 }
551
552 #[test]
553 fn body_field_non_json_body_returns_null() {
554 let result = eval(
555 "${body.name}",
556 Body::Text(r#"{"name":"Alice"}"#.to_string()),
557 );
558 assert_eq!(result, Value::Null);
559 }
560
561 #[test]
562 fn body_field_empty_body_returns_null() {
563 let result = eval("${body.name}", Body::Empty);
564 assert_eq!(result, Value::Null);
565 }
566
567 #[test]
568 fn body_field_in_interpolation() {
569 let result = eval("Hello ${body.name}!", Body::Json(json!({"name": "Alice"})));
570 assert_eq!(result, json!("Hello Alice!"));
571 }
572
573 #[test]
574 fn body_field_in_predicate_true() {
575 let lang = SimpleLanguage;
576 let mut ex = Exchange::default();
577 ex.input.body = Body::Json(json!({"status": "active"}));
578 let result = lang
579 .create_expression("${body.status} == 'active'")
580 .unwrap()
581 .evaluate(&ex)
582 .unwrap();
583 assert_eq!(result, Value::Bool(true));
584 }
585
586 #[test]
587 fn body_field_in_predicate_false() {
588 let lang = SimpleLanguage;
589 let mut ex = Exchange::default();
590 ex.input.body = Body::Json(json!({"status": "inactive"}));
591 let result = lang
592 .create_expression("${body.status} == 'active'")
593 .unwrap()
594 .evaluate(&ex)
595 .unwrap();
596 assert_eq!(result, Value::Bool(false));
597 }
598
599 #[test]
600 fn body_field_bytes_body_returns_null() {
601 let result = eval(
604 "${body.name}",
605 Body::Text(r#"{"name":"Alice"}"#.to_string()),
606 );
607 assert_eq!(result, Value::Null);
608 }
609
610 #[test]
611 fn body_field_json_null_value_returns_null() {
612 let result = eval(
614 "${body.name}",
615 Body::Json(serde_json::json!({"name": null})),
616 );
617 assert_eq!(result, Value::Null);
618 }
619
620 #[test]
621 fn body_field_numeric_predicate() {
622 let lang = SimpleLanguage;
625 let mut ex = Exchange::default();
626 ex.input.body = Body::Json(json!({"score": 42.0}));
627 let result = lang
628 .create_expression("${body.score} == 42")
629 .unwrap()
630 .evaluate(&ex)
631 .unwrap();
632 assert_eq!(result, Value::Bool(true));
633 }
634
635 #[test]
636 fn body_bytes_utf8_returns_string() {
637 let lang = SimpleLanguage;
639 let mut ex = Exchange::default();
640 ex.input.body = Body::from(b"hello from bytes".to_vec());
641 let val = lang
642 .create_expression("${body}")
643 .unwrap()
644 .evaluate(&ex)
645 .unwrap();
646 assert_eq!(val, Value::String("hello from bytes".to_string()));
647 }
648
649 #[test]
650 fn body_json_returns_serialized_string() {
651 let lang = SimpleLanguage;
653 let mut ex = Exchange::default();
654 ex.input.body = Body::Json(json!({"msg": "world"}));
655 let val = lang
656 .create_expression("${body}")
657 .unwrap()
658 .evaluate(&ex)
659 .unwrap();
660 let s = match val {
662 Value::String(s) => s,
663 other => panic!("expected String, got {other:?}"),
664 };
665 assert!(!s.is_empty(), "${{body}} on Body::Json should not be empty");
666 let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
667 assert_eq!(parsed["msg"], "world");
668 }
669
670 #[test]
671 fn body_bytes_in_interpolation() {
672 let lang = SimpleLanguage;
674 let mut ex = Exchange::default();
675 ex.input.body = Body::from(b"ping".to_vec());
676 let val = lang
677 .create_expression("Received: ${body}")
678 .unwrap()
679 .evaluate(&ex)
680 .unwrap();
681 assert_eq!(val, Value::String("Received: ping".to_string()));
682 }
683}