1mod evaluator;
2mod parser;
3
4use camel_api::exchange::Exchange;
5use camel_language_api::{Expression, Language, LanguageError, Predicate};
6
7pub struct SimpleLanguage;
8
9struct SimpleExpression(parser::Expr);
10struct SimplePredicate(parser::Expr);
11
12impl Expression for SimpleExpression {
13 fn evaluate(&self, exchange: &Exchange) -> Result<camel_api::Value, LanguageError> {
14 evaluator::evaluate(&self.0, exchange)
15 }
16}
17
18impl Predicate for SimplePredicate {
19 fn matches(&self, exchange: &Exchange) -> Result<bool, LanguageError> {
20 let val = evaluator::evaluate(&self.0, exchange)?;
21 Ok(match &val {
22 camel_api::Value::Bool(b) => *b,
23 camel_api::Value::Null => false,
24 _ => true,
25 })
26 }
27}
28
29impl Language for SimpleLanguage {
30 fn name(&self) -> &'static str {
31 "simple"
32 }
33
34 fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
35 let ast = parser::parse(script)?;
36 Ok(Box::new(SimpleExpression(ast)))
37 }
38
39 fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
40 let ast = parser::parse(script)?;
41 Ok(Box::new(SimplePredicate(ast)))
42 }
43}
44
45#[cfg(test)]
46mod tests {
47 use super::SimpleLanguage;
48 use camel_api::{Value, exchange::Exchange, message::Message};
49 use camel_language_api::Language;
50
51 fn exchange_with_header(key: &str, val: &str) -> Exchange {
52 let mut msg = Message::default();
53 msg.set_header(key, Value::String(val.to_string()));
54 Exchange::new(msg)
55 }
56
57 fn exchange_with_body(body: &str) -> Exchange {
58 Exchange::new(Message::new(body))
59 }
60
61 #[test]
62 fn test_header_equals_string() {
63 let lang = SimpleLanguage;
64 let pred = lang.create_predicate("${header.type} == 'order'").unwrap();
65 let ex = exchange_with_header("type", "order");
66 assert!(pred.matches(&ex).unwrap());
67 }
68
69 #[test]
70 fn test_header_not_equals() {
71 let lang = SimpleLanguage;
72 let pred = lang.create_predicate("${header.type} != 'order'").unwrap();
73 let ex = exchange_with_header("type", "invoice");
74 assert!(pred.matches(&ex).unwrap());
75 }
76
77 #[test]
78 fn test_body_contains() {
79 let lang = SimpleLanguage;
80 let pred = lang.create_predicate("${body} contains 'hello'").unwrap();
81 let ex = exchange_with_body("say hello world");
82 assert!(pred.matches(&ex).unwrap());
83 }
84
85 #[test]
86 fn test_header_null_check() {
87 let lang = SimpleLanguage;
88 let pred = lang.create_predicate("${header.missing} == null").unwrap();
89 let ex = exchange_with_body("anything");
90 assert!(pred.matches(&ex).unwrap());
91 }
92
93 #[test]
94 fn test_header_not_null() {
95 let lang = SimpleLanguage;
96 let pred = lang.create_predicate("${header.type} != null").unwrap();
97 let ex = exchange_with_header("type", "order");
98 assert!(pred.matches(&ex).unwrap());
99 }
100
101 #[test]
102 fn test_expression_header_value() {
103 let lang = SimpleLanguage;
104 let expr = lang.create_expression("${header.type}").unwrap();
105 let ex = exchange_with_header("type", "order");
106 let val = expr.evaluate(&ex).unwrap();
107 assert_eq!(val, Value::String("order".to_string()));
108 }
109
110 #[test]
111 fn test_expression_body() {
112 let lang = SimpleLanguage;
113 let expr = lang.create_expression("${body}").unwrap();
114 let ex = exchange_with_body("hello");
115 let val = expr.evaluate(&ex).unwrap();
116 assert_eq!(val, Value::String("hello".to_string()));
117 }
118
119 #[test]
120 fn test_numeric_comparison() {
121 let lang = SimpleLanguage;
122 let pred = lang.create_predicate("${header.age} > 18").unwrap();
123 let mut ex = Exchange::new(Message::default());
124 ex.input.set_header("age", Value::Number(25.into()));
125 assert!(pred.matches(&ex).unwrap());
126 }
127
128 #[test]
131 fn test_empty_body() {
132 let lang = SimpleLanguage;
133 let expr = lang.create_expression("${body}").unwrap();
134 let ex = Exchange::new(Message::default());
135 let val = expr.evaluate(&ex).unwrap();
136 assert_eq!(val, Value::String("".to_string()));
137 }
138
139 #[test]
140 fn test_parse_error_unrecognized_token() {
141 let lang = SimpleLanguage;
144 let result = lang.create_expression("${unknown}");
145 assert!(result.is_err(), "unknown token should be a parse error");
146 }
147
148 #[test]
149 fn test_empty_header_key_is_parse_error() {
150 let lang = SimpleLanguage;
151 let result = lang.create_expression("${header.}");
152 let err = result.err().expect("should be a parse error");
153 let err = format!("{err}");
154 assert!(
155 err.contains("empty"),
156 "error should mention empty key, got: {err}"
157 );
158 }
159
160 #[test]
161 fn test_empty_exchange_property_key_is_parse_error() {
162 let lang = SimpleLanguage;
163 let result = lang.create_expression("${exchangeProperty.}");
164 let err = result.err().expect("should be a parse error");
165 let err = format!("{err}");
166 assert!(
167 err.contains("empty"),
168 "error should mention empty key, got: {err}"
169 );
170 }
171
172 #[test]
173 fn test_missing_header_returns_null() {
174 let lang = SimpleLanguage;
175 let expr = lang.create_expression("${header.nonexistent}").unwrap();
176 let ex = exchange_with_body("anything");
177 let val = expr.evaluate(&ex).unwrap();
178 assert_eq!(val, Value::Null);
179 }
180
181 #[test]
182 fn test_exchange_property_expression() {
183 let lang = SimpleLanguage;
184 let expr = lang
185 .create_expression("${exchangeProperty.myProp}")
186 .unwrap();
187 let mut ex = exchange_with_body("test");
188 ex.set_property("myProp".to_string(), Value::String("propVal".to_string()));
189 let val = expr.evaluate(&ex).unwrap();
190 assert_eq!(val, Value::String("propVal".to_string()));
191 }
192
193 #[test]
194 fn test_missing_property_returns_null() {
195 let lang = SimpleLanguage;
196 let expr = lang
197 .create_expression("${exchangeProperty.missing}")
198 .unwrap();
199 let ex = exchange_with_body("test");
200 let val = expr.evaluate(&ex).unwrap();
201 assert_eq!(val, Value::Null);
202 }
203
204 #[test]
205 fn test_string_literal_expression() {
206 let lang = SimpleLanguage;
207 let expr = lang.create_expression("'hello'").unwrap();
208 let ex = exchange_with_body("test");
209 let val = expr.evaluate(&ex).unwrap();
210 assert_eq!(val, Value::String("hello".to_string()));
211 }
212
213 #[test]
214 fn test_null_expression() {
215 let lang = SimpleLanguage;
216 let expr = lang.create_expression("null").unwrap();
217 let ex = exchange_with_body("test");
218 let val = expr.evaluate(&ex).unwrap();
219 assert_eq!(val, Value::Null);
220 }
221
222 #[test]
223 fn test_predicate_null_is_false() {
224 let lang = SimpleLanguage;
225 let pred = lang.create_predicate("${header.missing}").unwrap();
226 let ex = exchange_with_body("test");
227 assert!(!pred.matches(&ex).unwrap());
228 }
229
230 #[test]
231 fn test_predicate_non_null_is_true() {
232 let lang = SimpleLanguage;
233 let pred = lang.create_predicate("${header.type}").unwrap();
234 let ex = exchange_with_header("type", "order");
235 assert!(pred.matches(&ex).unwrap());
236 }
237
238 #[test]
239 fn test_contains_not_found() {
240 let lang = SimpleLanguage;
241 let pred = lang.create_predicate("${body} contains 'xyz'").unwrap();
242 let ex = exchange_with_body("hello world");
243 assert!(!pred.matches(&ex).unwrap());
244 }
245
246 #[test]
247 fn test_less_than_or_equal() {
248 let lang = SimpleLanguage;
249 let pred = lang.create_predicate("${header.age} <= 18").unwrap();
250 let mut ex = Exchange::new(Message::default());
251 ex.input.set_header("age", Value::Number(18.into()));
252 assert!(pred.matches(&ex).unwrap());
253 }
254
255 #[test]
258 fn test_interpolated_text_with_header() {
259 let lang = SimpleLanguage;
261 let expr = lang
262 .create_expression("Exchange #${header.CamelTimerCounter}")
263 .unwrap();
264 let ex = exchange_with_header("CamelTimerCounter", "42");
265 let val = expr.evaluate(&ex).unwrap();
266 assert_eq!(val, Value::String("Exchange #42".to_string()));
267 }
268
269 #[test]
270 fn test_interpolated_text_with_body() {
271 let lang = SimpleLanguage;
273 let expr = lang.create_expression("Got ${body}").unwrap();
274 let ex = exchange_with_body("hello");
275 let val = expr.evaluate(&ex).unwrap();
276 assert_eq!(val, Value::String("Got hello".to_string()));
277 }
278
279 #[test]
280 fn test_interpolated_multiple_expressions() {
281 let lang = SimpleLanguage;
283 let expr = lang
284 .create_expression("Transformed: ${body} (source=${header.source})")
285 .unwrap();
286 let mut msg = camel_api::message::Message::new("data");
287 msg.set_header("source", Value::String("kafka".to_string()));
288 let ex = Exchange::new(msg);
289 let val = expr.evaluate(&ex).unwrap();
290 assert_eq!(
291 val,
292 Value::String("Transformed: data (source=kafka)".to_string())
293 );
294 }
295
296 #[test]
297 fn test_interpolated_missing_header_becomes_empty() {
298 let lang = SimpleLanguage;
300 let expr = lang
301 .create_expression("prefix-${header.missing}-suffix")
302 .unwrap();
303 let ex = exchange_with_body("x");
304 let val = expr.evaluate(&ex).unwrap();
305 assert_eq!(val, Value::String("prefix--suffix".to_string()));
306 }
307
308 #[test]
309 fn test_interpolated_text_only_no_expressions() {
310 let lang = SimpleLanguage;
313 let expr = lang.create_expression("Hello World").unwrap();
314 let ex = exchange_with_body("x");
315 let val = expr.evaluate(&ex).unwrap();
316 assert_eq!(val, Value::String("Hello World".to_string()));
317 }
318
319 #[test]
320 fn test_interpolated_unclosed_brace_treated_as_literal() {
321 let lang = SimpleLanguage;
324 let expr = lang.create_expression("Got ${body").unwrap();
325 let ex = exchange_with_body("hello");
326 let val = expr.evaluate(&ex).unwrap();
327 assert_eq!(val, Value::String("Got ${body".to_string()));
328 }
329
330 #[test]
331 fn test_operator_inside_string_literal_not_split() {
332 let lang = SimpleLanguage;
335 let result = lang.create_expression("'a>=b'");
336 let val = result
337 .unwrap()
338 .evaluate(&Exchange::new(Message::default()))
339 .unwrap();
340 assert_eq!(
341 val,
342 Value::String("a>=b".to_string()),
343 "string literal 'a>=b' should be parsed as a plain string, not split on >="
344 );
345 }
346
347 #[test]
348 fn test_header_eq_string_literal_containing_operator() {
349 let lang = SimpleLanguage;
352 let pred = lang.create_predicate("${header.x} == 'a>=b'").unwrap();
353 let ex = exchange_with_header("x", "a>=b");
354 assert!(
355 pred.matches(&ex).unwrap(),
356 "predicate should match when header equals 'a>=b'"
357 );
358 }
359}
360
361#[cfg(test)]
362mod body_field_parser_tests {
363 use crate::parser::{Expr, PathSegment, parse};
364
365 #[test]
366 fn parse_body_field_simple_key() {
367 let expr = parse("${body.name}").unwrap();
368 assert_eq!(
369 expr,
370 Expr::BodyField(vec![PathSegment::Key("name".to_string())])
371 );
372 }
373
374 #[test]
375 fn parse_body_field_nested() {
376 let expr = parse("${body.user.city}").unwrap();
377 assert_eq!(
378 expr,
379 Expr::BodyField(vec![
380 PathSegment::Key("user".to_string()),
381 PathSegment::Key("city".to_string()),
382 ])
383 );
384 }
385
386 #[test]
387 fn parse_body_field_array_index() {
388 let expr = parse("${body.items.0}").unwrap();
389 assert_eq!(
390 expr,
391 Expr::BodyField(vec![
392 PathSegment::Key("items".to_string()),
393 PathSegment::Index(0),
394 ])
395 );
396 }
397
398 #[test]
399 fn parse_body_field_array_nested() {
400 let expr = parse("${body.users.0.name}").unwrap();
401 assert_eq!(
402 expr,
403 Expr::BodyField(vec![
404 PathSegment::Key("users".to_string()),
405 PathSegment::Index(0),
406 PathSegment::Key("name".to_string()),
407 ])
408 );
409 }
410
411 #[test]
412 fn parse_body_field_empty_segment_error() {
413 let result = parse("${body.}");
414 assert!(result.is_err());
415 }
416
417 #[test]
418 fn parse_body_field_exact_still_works() {
419 let expr = parse("${body}").unwrap();
421 assert_eq!(expr, Expr::Body);
422 }
423
424 #[test]
425 fn parse_body_field_double_dots_error() {
426 let result = parse("${body..name}");
428 assert!(result.is_err());
429 }
430
431 #[test]
432 fn parse_body_field_index_only() {
433 let expr = parse("${body.0}").unwrap();
435 assert_eq!(expr, Expr::BodyField(vec![PathSegment::Index(0)]));
436 }
437
438 #[test]
439 fn parse_body_field_leading_zero_is_key() {
440 let expr = parse("${body.01}").unwrap();
442 assert_eq!(
443 expr,
444 Expr::BodyField(vec![PathSegment::Key("01".to_string())])
445 );
446 }
447}
448
449#[cfg(test)]
450mod body_field_eval_tests {
451 use crate::SimpleLanguage;
452 use camel_api::Value;
453 use camel_api::body::Body;
454 use camel_api::exchange::Exchange;
455 use camel_language_api::Language;
456 use serde_json::json;
457
458 fn eval(expr_str: &str, body: Body) -> Value {
459 let mut ex = Exchange::default();
460 ex.input.body = body;
461 let lang = SimpleLanguage;
462 lang.create_expression(expr_str)
463 .unwrap()
464 .evaluate(&ex)
465 .unwrap()
466 }
467
468 #[test]
469 fn body_field_simple_key() {
470 let result = eval("${body.name}", Body::Json(json!({"name": "Alice"})));
471 assert_eq!(result, json!("Alice"));
472 }
473
474 #[test]
475 fn body_field_number_value() {
476 let result = eval("${body.age}", Body::Json(json!({"age": 30})));
477 assert_eq!(result, json!(30));
478 }
479
480 #[test]
481 fn body_field_bool_value() {
482 let result = eval("${body.active}", Body::Json(json!({"active": true})));
483 assert_eq!(result, json!(true));
484 }
485
486 #[test]
487 fn body_field_nested() {
488 let result = eval(
489 "${body.user.city}",
490 Body::Json(json!({"user": {"city": "Madrid"}})),
491 );
492 assert_eq!(result, json!("Madrid"));
493 }
494
495 #[test]
496 fn body_field_array_index() {
497 let result = eval("${body.items.0}", Body::Json(json!({"items": ["a", "b"]})));
498 assert_eq!(result, json!("a"));
499 }
500
501 #[test]
502 fn body_field_array_nested() {
503 let result = eval(
504 "${body.users.0.name}",
505 Body::Json(json!({"users": [{"name": "Bob"}]})),
506 );
507 assert_eq!(result, json!("Bob"));
508 }
509
510 #[test]
511 fn body_field_missing_key_returns_null() {
512 let result = eval("${body.missing}", Body::Json(json!({"name": "Alice"})));
513 assert_eq!(result, Value::Null);
514 }
515
516 #[test]
517 fn body_field_missing_nested_returns_null() {
518 let result = eval("${body.a.b.c}", Body::Json(json!({"a": {"x": 1}})));
519 assert_eq!(result, Value::Null);
520 }
521
522 #[test]
523 fn body_field_out_of_bounds_index_returns_null() {
524 let result = eval("${body.items.5}", Body::Json(json!({"items": ["a"]})));
525 assert_eq!(result, Value::Null);
526 }
527
528 #[test]
529 fn body_field_non_json_body_returns_null() {
530 let result = eval(
531 "${body.name}",
532 Body::Text(r#"{"name":"Alice"}"#.to_string()),
533 );
534 assert_eq!(result, Value::Null);
535 }
536
537 #[test]
538 fn body_field_empty_body_returns_null() {
539 let result = eval("${body.name}", Body::Empty);
540 assert_eq!(result, Value::Null);
541 }
542
543 #[test]
544 fn body_field_in_interpolation() {
545 let result = eval("Hello ${body.name}!", Body::Json(json!({"name": "Alice"})));
546 assert_eq!(result, json!("Hello Alice!"));
547 }
548
549 #[test]
550 fn body_field_in_predicate_true() {
551 let lang = SimpleLanguage;
552 let mut ex = Exchange::default();
553 ex.input.body = Body::Json(json!({"status": "active"}));
554 let result = lang
555 .create_expression("${body.status} == 'active'")
556 .unwrap()
557 .evaluate(&ex)
558 .unwrap();
559 assert_eq!(result, Value::Bool(true));
560 }
561
562 #[test]
563 fn body_field_in_predicate_false() {
564 let lang = SimpleLanguage;
565 let mut ex = Exchange::default();
566 ex.input.body = Body::Json(json!({"status": "inactive"}));
567 let result = lang
568 .create_expression("${body.status} == 'active'")
569 .unwrap()
570 .evaluate(&ex)
571 .unwrap();
572 assert_eq!(result, Value::Bool(false));
573 }
574
575 #[test]
576 fn body_field_bytes_body_returns_null() {
577 let result = eval(
580 "${body.name}",
581 Body::Text(r#"{"name":"Alice"}"#.to_string()),
582 );
583 assert_eq!(result, camel_api::Value::Null);
584 }
585
586 #[test]
587 fn body_field_json_null_value_returns_null() {
588 let result = eval(
590 "${body.name}",
591 Body::Json(serde_json::json!({"name": null})),
592 );
593 assert_eq!(result, camel_api::Value::Null);
594 }
595
596 #[test]
597 fn body_field_numeric_predicate() {
598 let lang = SimpleLanguage;
601 let mut ex = Exchange::default();
602 ex.input.body = Body::Json(json!({"score": 42.0}));
603 let result = lang
604 .create_expression("${body.score} == 42")
605 .unwrap()
606 .evaluate(&ex)
607 .unwrap();
608 assert_eq!(result, Value::Bool(true));
609 }
610}