Skip to main content

camel_language_simple/
lib.rs

1mod evaluator;
2mod parser;
3
4use camel_language_api::{Exchange, Expression, Language, LanguageError, Predicate, Value};
5
6pub struct SimpleLanguage;
7
8struct SimpleExpression(parser::Expr);
9struct SimplePredicate(parser::Expr);
10
11impl Expression for SimpleExpression {
12    fn evaluate(&self, exchange: &Exchange) -> Result<Value, LanguageError> {
13        evaluator::evaluate(&self.0, exchange)
14    }
15}
16
17impl Predicate for SimplePredicate {
18    fn matches(&self, exchange: &Exchange) -> Result<bool, LanguageError> {
19        let val = evaluator::evaluate(&self.0, exchange)?;
20        Ok(match &val {
21            Value::Bool(b) => *b,
22            Value::Null => false,
23            _ => true,
24        })
25    }
26}
27
28impl Language for SimpleLanguage {
29    fn name(&self) -> &'static str {
30        "simple"
31    }
32
33    fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
34        let ast = parser::parse(script)?;
35        Ok(Box::new(SimpleExpression(ast)))
36    }
37
38    fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
39        let ast = parser::parse(script)?;
40        Ok(Box::new(SimplePredicate(ast)))
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::SimpleLanguage;
47    use camel_language_api::Language;
48    use camel_language_api::{Exchange, Message, Value};
49
50    fn exchange_with_header(key: &str, val: &str) -> Exchange {
51        let mut msg = Message::default();
52        msg.set_header(key, Value::String(val.to_string()));
53        Exchange::new(msg)
54    }
55
56    fn exchange_with_body(body: &str) -> Exchange {
57        Exchange::new(Message::new(body))
58    }
59
60    #[test]
61    fn test_header_equals_string() {
62        let lang = SimpleLanguage;
63        let pred = lang.create_predicate("${header.type} == 'order'").unwrap();
64        let ex = exchange_with_header("type", "order");
65        assert!(pred.matches(&ex).unwrap());
66    }
67
68    #[test]
69    fn test_header_not_equals() {
70        let lang = SimpleLanguage;
71        let pred = lang.create_predicate("${header.type} != 'order'").unwrap();
72        let ex = exchange_with_header("type", "invoice");
73        assert!(pred.matches(&ex).unwrap());
74    }
75
76    #[test]
77    fn test_body_contains() {
78        let lang = SimpleLanguage;
79        let pred = lang.create_predicate("${body} contains 'hello'").unwrap();
80        let ex = exchange_with_body("say hello world");
81        assert!(pred.matches(&ex).unwrap());
82    }
83
84    #[test]
85    fn test_header_null_check() {
86        let lang = SimpleLanguage;
87        let pred = lang.create_predicate("${header.missing} == null").unwrap();
88        let ex = exchange_with_body("anything");
89        assert!(pred.matches(&ex).unwrap());
90    }
91
92    #[test]
93    fn test_header_not_null() {
94        let lang = SimpleLanguage;
95        let pred = lang.create_predicate("${header.type} != null").unwrap();
96        let ex = exchange_with_header("type", "order");
97        assert!(pred.matches(&ex).unwrap());
98    }
99
100    #[test]
101    fn test_expression_header_value() {
102        let lang = SimpleLanguage;
103        let expr = lang.create_expression("${header.type}").unwrap();
104        let ex = exchange_with_header("type", "order");
105        let val = expr.evaluate(&ex).unwrap();
106        assert_eq!(val, Value::String("order".to_string()));
107    }
108
109    #[test]
110    fn test_expression_body() {
111        let lang = SimpleLanguage;
112        let expr = lang.create_expression("${body}").unwrap();
113        let ex = exchange_with_body("hello");
114        let val = expr.evaluate(&ex).unwrap();
115        assert_eq!(val, Value::String("hello".to_string()));
116    }
117
118    #[test]
119    fn test_numeric_comparison() {
120        let lang = SimpleLanguage;
121        let pred = lang.create_predicate("${header.age} > 18").unwrap();
122        let mut ex = Exchange::new(Message::default());
123        ex.input.set_header("age", Value::Number(25.into()));
124        assert!(pred.matches(&ex).unwrap());
125    }
126
127    // --- Edge case tests ---
128
129    #[test]
130    fn test_empty_body() {
131        let lang = SimpleLanguage;
132        let expr = lang.create_expression("${body}").unwrap();
133        let ex = Exchange::new(Message::default());
134        let val = expr.evaluate(&ex).unwrap();
135        assert_eq!(val, Value::String("".to_string()));
136    }
137
138    #[test]
139    fn test_parse_error_unrecognized_token() {
140        // A pure `${...}` token that doesn't match any known form is a parse error.
141        // For example `${unknown}` is not a valid Simple expression.
142        let lang = SimpleLanguage;
143        let result = lang.create_expression("${unknown}");
144        assert!(result.is_err(), "unknown token should be a parse error");
145    }
146
147    #[test]
148    fn test_empty_header_key_is_parse_error() {
149        let lang = SimpleLanguage;
150        let result = lang.create_expression("${header.}");
151        let err = result.err().expect("should be a parse error");
152        let err = format!("{err}");
153        assert!(
154            err.contains("empty"),
155            "error should mention empty key, got: {err}"
156        );
157    }
158
159    #[test]
160    fn test_empty_exchange_property_key_is_parse_error() {
161        let lang = SimpleLanguage;
162        let result = lang.create_expression("${exchangeProperty.}");
163        let err = result.err().expect("should be a parse error");
164        let err = format!("{err}");
165        assert!(
166            err.contains("empty"),
167            "error should mention empty key, got: {err}"
168        );
169    }
170
171    #[test]
172    fn test_missing_header_returns_null() {
173        let lang = SimpleLanguage;
174        let expr = lang.create_expression("${header.nonexistent}").unwrap();
175        let ex = exchange_with_body("anything");
176        let val = expr.evaluate(&ex).unwrap();
177        assert_eq!(val, Value::Null);
178    }
179
180    #[test]
181    fn test_exchange_property_expression() {
182        let lang = SimpleLanguage;
183        let expr = lang
184            .create_expression("${exchangeProperty.myProp}")
185            .unwrap();
186        let mut ex = exchange_with_body("test");
187        ex.set_property("myProp".to_string(), Value::String("propVal".to_string()));
188        let val = expr.evaluate(&ex).unwrap();
189        assert_eq!(val, Value::String("propVal".to_string()));
190    }
191
192    #[test]
193    fn test_missing_property_returns_null() {
194        let lang = SimpleLanguage;
195        let expr = lang
196            .create_expression("${exchangeProperty.missing}")
197            .unwrap();
198        let ex = exchange_with_body("test");
199        let val = expr.evaluate(&ex).unwrap();
200        assert_eq!(val, Value::Null);
201    }
202
203    #[test]
204    fn test_string_literal_expression() {
205        let lang = SimpleLanguage;
206        let expr = lang.create_expression("'hello'").unwrap();
207        let ex = exchange_with_body("test");
208        let val = expr.evaluate(&ex).unwrap();
209        assert_eq!(val, Value::String("hello".to_string()));
210    }
211
212    #[test]
213    fn test_null_expression() {
214        let lang = SimpleLanguage;
215        let expr = lang.create_expression("null").unwrap();
216        let ex = exchange_with_body("test");
217        let val = expr.evaluate(&ex).unwrap();
218        assert_eq!(val, Value::Null);
219    }
220
221    #[test]
222    fn test_predicate_null_is_false() {
223        let lang = SimpleLanguage;
224        let pred = lang.create_predicate("${header.missing}").unwrap();
225        let ex = exchange_with_body("test");
226        assert!(!pred.matches(&ex).unwrap());
227    }
228
229    #[test]
230    fn test_predicate_non_null_is_true() {
231        let lang = SimpleLanguage;
232        let pred = lang.create_predicate("${header.type}").unwrap();
233        let ex = exchange_with_header("type", "order");
234        assert!(pred.matches(&ex).unwrap());
235    }
236
237    #[test]
238    fn test_contains_not_found() {
239        let lang = SimpleLanguage;
240        let pred = lang.create_predicate("${body} contains 'xyz'").unwrap();
241        let ex = exchange_with_body("hello world");
242        assert!(!pred.matches(&ex).unwrap());
243    }
244
245    #[test]
246    fn test_less_than_or_equal() {
247        let lang = SimpleLanguage;
248        let pred = lang.create_predicate("${header.age} <= 18").unwrap();
249        let mut ex = Exchange::new(Message::default());
250        ex.input.set_header("age", Value::Number(18.into()));
251        assert!(pred.matches(&ex).unwrap());
252    }
253
254    // --- Mixed interpolation tests ---
255
256    #[test]
257    fn test_interpolated_text_with_header() {
258        // "Exchange #${header.CamelTimerCounter}" → "Exchange #42"
259        let lang = SimpleLanguage;
260        let expr = lang
261            .create_expression("Exchange #${header.CamelTimerCounter}")
262            .unwrap();
263        let ex = exchange_with_header("CamelTimerCounter", "42");
264        let val = expr.evaluate(&ex).unwrap();
265        assert_eq!(val, Value::String("Exchange #42".to_string()));
266    }
267
268    #[test]
269    fn test_interpolated_text_with_body() {
270        // "Got ${body}" → "Got hello"
271        let lang = SimpleLanguage;
272        let expr = lang.create_expression("Got ${body}").unwrap();
273        let ex = exchange_with_body("hello");
274        let val = expr.evaluate(&ex).unwrap();
275        assert_eq!(val, Value::String("Got hello".to_string()));
276    }
277
278    #[test]
279    fn test_interpolated_multiple_expressions() {
280        // "Transformed: ${body} (source=${header.source})" → real values
281        let lang = SimpleLanguage;
282        let expr = lang
283            .create_expression("Transformed: ${body} (source=${header.source})")
284            .unwrap();
285        let mut msg = Message::new("data");
286        msg.set_header("source", Value::String("kafka".to_string()));
287        let ex = Exchange::new(msg);
288        let val = expr.evaluate(&ex).unwrap();
289        assert_eq!(
290            val,
291            Value::String("Transformed: data (source=kafka)".to_string())
292        );
293    }
294
295    #[test]
296    fn test_interpolated_missing_header_becomes_empty() {
297        // Missing header in interpolated string yields empty string for that slot
298        let lang = SimpleLanguage;
299        let expr = lang
300            .create_expression("prefix-${header.missing}-suffix")
301            .unwrap();
302        let ex = exchange_with_body("x");
303        let val = expr.evaluate(&ex).unwrap();
304        assert_eq!(val, Value::String("prefix--suffix".to_string()));
305    }
306
307    #[test]
308    fn test_interpolated_text_only_no_expressions() {
309        // Plain text with no ${...} — treated as a literal (no interpolation needed,
310        // but must still work without error)
311        let lang = SimpleLanguage;
312        let expr = lang.create_expression("Hello World").unwrap();
313        let ex = exchange_with_body("x");
314        let val = expr.evaluate(&ex).unwrap();
315        assert_eq!(val, Value::String("Hello World".to_string()));
316    }
317
318    #[test]
319    fn test_interpolated_unclosed_brace_treated_as_literal() {
320        // An unclosed `${` has no matching `}` — the remainder is treated as
321        // plain literal text rather than causing a parse error.
322        let lang = SimpleLanguage;
323        let expr = lang.create_expression("Got ${body").unwrap();
324        let ex = exchange_with_body("hello");
325        let val = expr.evaluate(&ex).unwrap();
326        assert_eq!(val, Value::String("Got ${body".to_string()));
327    }
328
329    #[test]
330    fn test_operator_inside_string_literal_not_split() {
331        // The string literal 'a>=b' contains '>=' but must NOT be parsed as a
332        // BinOp split — the whole thing is a StringLit atom.
333        let lang = SimpleLanguage;
334        let result = lang.create_expression("'a>=b'");
335        let val = result
336            .unwrap()
337            .evaluate(&Exchange::new(Message::default()))
338            .unwrap();
339        assert_eq!(
340            val,
341            Value::String("a>=b".to_string()),
342            "string literal 'a>=b' should be parsed as a plain string, not split on >="
343        );
344    }
345
346    #[test]
347    fn test_header_eq_string_literal_containing_operator() {
348        // ${header.x} == 'a>=b' — the operator inside the RHS string must not
349        // cause the parser to split the LHS at the wrong position.
350        let lang = SimpleLanguage;
351        let pred = lang.create_predicate("${header.x} == 'a>=b'").unwrap();
352        let ex = exchange_with_header("x", "a>=b");
353        assert!(
354            pred.matches(&ex).unwrap(),
355            "predicate should match when header equals 'a>=b'"
356        );
357    }
358}
359
360#[cfg(test)]
361mod body_field_parser_tests {
362    use crate::parser::{Expr, PathSegment, parse};
363
364    #[test]
365    fn parse_body_field_simple_key() {
366        let expr = parse("${body.name}").unwrap();
367        assert_eq!(
368            expr,
369            Expr::BodyField(vec![PathSegment::Key("name".to_string())])
370        );
371    }
372
373    #[test]
374    fn parse_body_field_nested() {
375        let expr = parse("${body.user.city}").unwrap();
376        assert_eq!(
377            expr,
378            Expr::BodyField(vec![
379                PathSegment::Key("user".to_string()),
380                PathSegment::Key("city".to_string()),
381            ])
382        );
383    }
384
385    #[test]
386    fn parse_body_field_array_index() {
387        let expr = parse("${body.items.0}").unwrap();
388        assert_eq!(
389            expr,
390            Expr::BodyField(vec![
391                PathSegment::Key("items".to_string()),
392                PathSegment::Index(0),
393            ])
394        );
395    }
396
397    #[test]
398    fn parse_body_field_array_nested() {
399        let expr = parse("${body.users.0.name}").unwrap();
400        assert_eq!(
401            expr,
402            Expr::BodyField(vec![
403                PathSegment::Key("users".to_string()),
404                PathSegment::Index(0),
405                PathSegment::Key("name".to_string()),
406            ])
407        );
408    }
409
410    #[test]
411    fn parse_body_field_empty_segment_error() {
412        let result = parse("${body.}");
413        assert!(result.is_err());
414    }
415
416    #[test]
417    fn parse_body_field_exact_still_works() {
418        // Regression: ${body} must still produce Expr::Body, not BodyField
419        let expr = parse("${body}").unwrap();
420        assert_eq!(expr, Expr::Body);
421    }
422
423    #[test]
424    fn parse_body_field_double_dots_error() {
425        // ${body..name} has an empty segment between the two dots
426        let result = parse("${body..name}");
427        assert!(result.is_err());
428    }
429
430    #[test]
431    fn parse_body_field_index_only() {
432        // ${body.0} — single index segment (e.g. body is a JSON array)
433        let expr = parse("${body.0}").unwrap();
434        assert_eq!(expr, Expr::BodyField(vec![PathSegment::Index(0)]));
435    }
436
437    #[test]
438    fn parse_body_field_leading_zero_is_key() {
439        // ${body.01} — leading zero means it's a string key, not an array index
440        let expr = parse("${body.01}").unwrap();
441        assert_eq!(
442            expr,
443            Expr::BodyField(vec![PathSegment::Key("01".to_string())])
444        );
445    }
446}
447
448#[cfg(test)]
449mod body_field_eval_tests {
450    use crate::SimpleLanguage;
451    use camel_language_api::Language;
452    use camel_language_api::{Body, Exchange, Value};
453    use serde_json::json;
454
455    fn eval(expr_str: &str, body: Body) -> Value {
456        let mut ex = Exchange::default();
457        ex.input.body = body;
458        let lang = SimpleLanguage;
459        lang.create_expression(expr_str)
460            .unwrap()
461            .evaluate(&ex)
462            .unwrap()
463    }
464
465    #[test]
466    fn body_field_simple_key() {
467        let result = eval("${body.name}", Body::Json(json!({"name": "Alice"})));
468        assert_eq!(result, json!("Alice"));
469    }
470
471    #[test]
472    fn body_field_number_value() {
473        let result = eval("${body.age}", Body::Json(json!({"age": 30})));
474        assert_eq!(result, json!(30));
475    }
476
477    #[test]
478    fn body_field_bool_value() {
479        let result = eval("${body.active}", Body::Json(json!({"active": true})));
480        assert_eq!(result, json!(true));
481    }
482
483    #[test]
484    fn body_field_nested() {
485        let result = eval(
486            "${body.user.city}",
487            Body::Json(json!({"user": {"city": "Madrid"}})),
488        );
489        assert_eq!(result, json!("Madrid"));
490    }
491
492    #[test]
493    fn body_field_array_index() {
494        let result = eval("${body.items.0}", Body::Json(json!({"items": ["a", "b"]})));
495        assert_eq!(result, json!("a"));
496    }
497
498    #[test]
499    fn body_field_array_nested() {
500        let result = eval(
501            "${body.users.0.name}",
502            Body::Json(json!({"users": [{"name": "Bob"}]})),
503        );
504        assert_eq!(result, json!("Bob"));
505    }
506
507    #[test]
508    fn body_field_missing_key_returns_null() {
509        let result = eval("${body.missing}", Body::Json(json!({"name": "Alice"})));
510        assert_eq!(result, Value::Null);
511    }
512
513    #[test]
514    fn body_field_missing_nested_returns_null() {
515        let result = eval("${body.a.b.c}", Body::Json(json!({"a": {"x": 1}})));
516        assert_eq!(result, Value::Null);
517    }
518
519    #[test]
520    fn body_field_out_of_bounds_index_returns_null() {
521        let result = eval("${body.items.5}", Body::Json(json!({"items": ["a"]})));
522        assert_eq!(result, Value::Null);
523    }
524
525    #[test]
526    fn body_field_non_json_body_returns_null() {
527        let result = eval(
528            "${body.name}",
529            Body::Text(r#"{"name":"Alice"}"#.to_string()),
530        );
531        assert_eq!(result, Value::Null);
532    }
533
534    #[test]
535    fn body_field_empty_body_returns_null() {
536        let result = eval("${body.name}", Body::Empty);
537        assert_eq!(result, Value::Null);
538    }
539
540    #[test]
541    fn body_field_in_interpolation() {
542        let result = eval("Hello ${body.name}!", Body::Json(json!({"name": "Alice"})));
543        assert_eq!(result, json!("Hello Alice!"));
544    }
545
546    #[test]
547    fn body_field_in_predicate_true() {
548        let lang = SimpleLanguage;
549        let mut ex = Exchange::default();
550        ex.input.body = Body::Json(json!({"status": "active"}));
551        let result = lang
552            .create_expression("${body.status} == 'active'")
553            .unwrap()
554            .evaluate(&ex)
555            .unwrap();
556        assert_eq!(result, Value::Bool(true));
557    }
558
559    #[test]
560    fn body_field_in_predicate_false() {
561        let lang = SimpleLanguage;
562        let mut ex = Exchange::default();
563        ex.input.body = Body::Json(json!({"status": "inactive"}));
564        let result = lang
565            .create_expression("${body.status} == 'active'")
566            .unwrap()
567            .evaluate(&ex)
568            .unwrap();
569        assert_eq!(result, Value::Bool(false));
570    }
571
572    #[test]
573    fn body_field_bytes_body_returns_null() {
574        // Note: Using Body::Text here instead of Body::Bytes since bytes crate
575        // is not available in test context. Both should behave the same for JSON field access.
576        let result = eval(
577            "${body.name}",
578            Body::Text(r#"{"name":"Alice"}"#.to_string()),
579        );
580        assert_eq!(result, Value::Null);
581    }
582
583    #[test]
584    fn body_field_json_null_value_returns_null() {
585        // key exists but its value is JSON null → returns Value::Null
586        let result = eval(
587            "${body.name}",
588            Body::Json(serde_json::json!({"name": null})),
589        );
590        assert_eq!(result, Value::Null);
591    }
592
593    #[test]
594    fn body_field_numeric_predicate() {
595        // JSON number 42.0 compares equal to the parsed number 42 from the
596        // Simple Language expression, because both resolve to the same f64.
597        let lang = SimpleLanguage;
598        let mut ex = Exchange::default();
599        ex.input.body = Body::Json(json!({"score": 42.0}));
600        let result = lang
601            .create_expression("${body.score} == 42")
602            .unwrap()
603            .evaluate(&ex)
604            .unwrap();
605        assert_eq!(result, Value::Bool(true));
606    }
607
608    #[test]
609    fn body_bytes_utf8_returns_string() {
610        // Body::Bytes with valid UTF-8 content should be readable via ${body}
611        let lang = SimpleLanguage;
612        let mut ex = Exchange::default();
613        ex.input.body = Body::from(b"hello from bytes".to_vec());
614        let val = lang
615            .create_expression("${body}")
616            .unwrap()
617            .evaluate(&ex)
618            .unwrap();
619        assert_eq!(val, Value::String("hello from bytes".to_string()));
620    }
621
622    #[test]
623    fn body_json_returns_serialized_string() {
624        // Body::Json should be serialized to a JSON string when accessed via ${body}
625        let lang = SimpleLanguage;
626        let mut ex = Exchange::default();
627        ex.input.body = Body::Json(json!({"msg": "world"}));
628        let val = lang
629            .create_expression("${body}")
630            .unwrap()
631            .evaluate(&ex)
632            .unwrap();
633        // The result should be the JSON serialization, not empty
634        let s = match val {
635            Value::String(s) => s,
636            other => panic!("expected String, got {other:?}"),
637        };
638        assert!(!s.is_empty(), "${{body}} on Body::Json should not be empty");
639        let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
640        assert_eq!(parsed["msg"], "world");
641    }
642
643    #[test]
644    fn body_bytes_in_interpolation() {
645        // Body::Bytes should work in interpolated expressions like "Received: ${body}"
646        let lang = SimpleLanguage;
647        let mut ex = Exchange::default();
648        ex.input.body = Body::from(b"ping".to_vec());
649        let val = lang
650            .create_expression("Received: ${body}")
651            .unwrap()
652            .evaluate(&ex)
653            .unwrap();
654        assert_eq!(val, Value::String("Received: ping".to_string()));
655    }
656}