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        let lang = SimpleLanguage;
142        let result = lang.create_expression("@@invalid@@");
143        assert!(result.is_err());
144    }
145
146    #[test]
147    fn test_empty_header_key_is_parse_error() {
148        let lang = SimpleLanguage;
149        let result = lang.create_expression("${header.}");
150        let err = result.err().expect("should be a parse error");
151        let err = format!("{err}");
152        assert!(
153            err.contains("empty"),
154            "error should mention empty key, got: {err}"
155        );
156    }
157
158    #[test]
159    fn test_empty_exchange_property_key_is_parse_error() {
160        let lang = SimpleLanguage;
161        let result = lang.create_expression("${exchangeProperty.}");
162        let err = result.err().expect("should be a parse error");
163        let err = format!("{err}");
164        assert!(
165            err.contains("empty"),
166            "error should mention empty key, got: {err}"
167        );
168    }
169
170    #[test]
171    fn test_missing_header_returns_null() {
172        let lang = SimpleLanguage;
173        let expr = lang.create_expression("${header.nonexistent}").unwrap();
174        let ex = exchange_with_body("anything");
175        let val = expr.evaluate(&ex).unwrap();
176        assert_eq!(val, Value::Null);
177    }
178
179    #[test]
180    fn test_exchange_property_expression() {
181        let lang = SimpleLanguage;
182        let expr = lang
183            .create_expression("${exchangeProperty.myProp}")
184            .unwrap();
185        let mut ex = exchange_with_body("test");
186        ex.set_property("myProp".to_string(), Value::String("propVal".to_string()));
187        let val = expr.evaluate(&ex).unwrap();
188        assert_eq!(val, Value::String("propVal".to_string()));
189    }
190
191    #[test]
192    fn test_missing_property_returns_null() {
193        let lang = SimpleLanguage;
194        let expr = lang
195            .create_expression("${exchangeProperty.missing}")
196            .unwrap();
197        let ex = exchange_with_body("test");
198        let val = expr.evaluate(&ex).unwrap();
199        assert_eq!(val, Value::Null);
200    }
201
202    #[test]
203    fn test_string_literal_expression() {
204        let lang = SimpleLanguage;
205        let expr = lang.create_expression("'hello'").unwrap();
206        let ex = exchange_with_body("test");
207        let val = expr.evaluate(&ex).unwrap();
208        assert_eq!(val, Value::String("hello".to_string()));
209    }
210
211    #[test]
212    fn test_null_expression() {
213        let lang = SimpleLanguage;
214        let expr = lang.create_expression("null").unwrap();
215        let ex = exchange_with_body("test");
216        let val = expr.evaluate(&ex).unwrap();
217        assert_eq!(val, Value::Null);
218    }
219
220    #[test]
221    fn test_predicate_null_is_false() {
222        let lang = SimpleLanguage;
223        let pred = lang.create_predicate("${header.missing}").unwrap();
224        let ex = exchange_with_body("test");
225        assert!(!pred.matches(&ex).unwrap());
226    }
227
228    #[test]
229    fn test_predicate_non_null_is_true() {
230        let lang = SimpleLanguage;
231        let pred = lang.create_predicate("${header.type}").unwrap();
232        let ex = exchange_with_header("type", "order");
233        assert!(pred.matches(&ex).unwrap());
234    }
235
236    #[test]
237    fn test_contains_not_found() {
238        let lang = SimpleLanguage;
239        let pred = lang.create_predicate("${body} contains 'xyz'").unwrap();
240        let ex = exchange_with_body("hello world");
241        assert!(!pred.matches(&ex).unwrap());
242    }
243
244    #[test]
245    fn test_less_than_or_equal() {
246        let lang = SimpleLanguage;
247        let pred = lang.create_predicate("${header.age} <= 18").unwrap();
248        let mut ex = Exchange::new(Message::default());
249        ex.input.set_header("age", Value::Number(18.into()));
250        assert!(pred.matches(&ex).unwrap());
251    }
252
253    #[test]
254    fn test_operator_inside_string_literal_not_split() {
255        // The string literal 'a>=b' contains '>=' but must NOT be parsed as a
256        // BinOp split — the whole thing is a StringLit atom.
257        let lang = SimpleLanguage;
258        let result = lang.create_expression("'a>=b'");
259        let val = result
260            .unwrap()
261            .evaluate(&Exchange::new(Message::default()))
262            .unwrap();
263        assert_eq!(
264            val,
265            Value::String("a>=b".to_string()),
266            "string literal 'a>=b' should be parsed as a plain string, not split on >="
267        );
268    }
269
270    #[test]
271    fn test_header_eq_string_literal_containing_operator() {
272        // ${header.x} == 'a>=b' — the operator inside the RHS string must not
273        // cause the parser to split the LHS at the wrong position.
274        let lang = SimpleLanguage;
275        let pred = lang.create_predicate("${header.x} == 'a>=b'").unwrap();
276        let ex = exchange_with_header("x", "a>=b");
277        assert!(
278            pred.matches(&ex).unwrap(),
279            "predicate should match when header equals 'a>=b'"
280        );
281    }
282}