Skip to main content

camel_language_simple/
lib.rs

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    // --- Edge case tests ---
129
130    #[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        // A pure `${...}` token that doesn't match any known form is a parse error.
142        // For example `${unknown}` is not a valid Simple expression.
143        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    // --- Mixed interpolation tests ---
256
257    #[test]
258    fn test_interpolated_text_with_header() {
259        // "Exchange #${header.CamelTimerCounter}" → "Exchange #42"
260        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        // "Got ${body}" → "Got hello"
272        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        // "Transformed: ${body} (source=${header.source})" → real values
282        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        // Missing header in interpolated string yields empty string for that slot
299        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        // Plain text with no ${...} — treated as a literal (no interpolation needed,
311        // but must still work without error)
312        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        // An unclosed `${` has no matching `}` — the remainder is treated as
322        // plain literal text rather than causing a parse error.
323        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        // The string literal 'a>=b' contains '>=' but must NOT be parsed as a
333        // BinOp split — the whole thing is a StringLit atom.
334        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        // ${header.x} == 'a>=b' — the operator inside the RHS string must not
350        // cause the parser to split the LHS at the wrong position.
351        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        // Regression: ${body} must still produce Expr::Body, not BodyField
420        let expr = parse("${body}").unwrap();
421        assert_eq!(expr, Expr::Body);
422    }
423
424    #[test]
425    fn parse_body_field_double_dots_error() {
426        // ${body..name} has an empty segment between the two dots
427        let result = parse("${body..name}");
428        assert!(result.is_err());
429    }
430
431    #[test]
432    fn parse_body_field_index_only() {
433        // ${body.0} — single index segment (e.g. body is a JSON array)
434        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        // ${body.01} — leading zero means it's a string key, not an array index
441        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        // Note: Using Body::Text here instead of Body::Bytes since bytes crate
578        // is not available in test context. Both should behave the same for JSON field access.
579        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        // key exists but its value is JSON null → returns Value::Null
589        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        // JSON number 42.0 compares equal to the parsed number 42 from the
599        // Simple Language expression, because both resolve to the same f64.
600        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}