camel_language_js/
language.rs1use std::sync::Arc;
4
5use camel_language_api::{Expression, Language, LanguageError, MutatingExpression, Predicate};
6
7use crate::{
8 engine::JsEngine,
9 engines::boa::BoaEngine,
10 expression::{JsExpression, JsMutatingExpression, JsPredicate, validate_to_parse_error},
11};
12
13#[derive(Clone)]
33pub struct JsLanguage {
34 engine: Arc<dyn JsEngine>,
35}
36
37impl JsLanguage {
38 pub fn new() -> Self {
40 Self {
41 engine: Arc::new(BoaEngine::new()),
42 }
43 }
44
45 pub fn with_engine<E: JsEngine>(engine: E) -> Self {
49 Self {
50 engine: Arc::new(engine),
51 }
52 }
53}
54
55impl Default for JsLanguage {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl Language for JsLanguage {
62 fn name(&self) -> &'static str {
63 "js"
64 }
65
66 fn create_expression(&self, script: &str) -> Result<Box<dyn Expression>, LanguageError> {
67 validate_to_parse_error(&self.engine, script)?;
68 Ok(Box::new(JsExpression::new(
69 script.to_string(),
70 Arc::clone(&self.engine),
71 )))
72 }
73
74 fn create_predicate(&self, script: &str) -> Result<Box<dyn Predicate>, LanguageError> {
75 validate_to_parse_error(&self.engine, script)?;
76 Ok(Box::new(JsPredicate::new(
77 script.to_string(),
78 Arc::clone(&self.engine),
79 )))
80 }
81
82 fn create_mutating_expression(
83 &self,
84 script: &str,
85 ) -> Result<Box<dyn MutatingExpression>, LanguageError> {
86 validate_to_parse_error(&self.engine, script)?;
87 Ok(Box::new(JsMutatingExpression::new(
88 script.to_string(),
89 Arc::clone(&self.engine),
90 )))
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use camel_language_api::{Body, Exchange, Message};
98 use serde_json::json;
99
100 fn make_exchange() -> Exchange {
101 let mut msg = Message::default();
102 msg.headers.insert("env".to_string(), json!("prod"));
103 msg.body = Body::Text("payload".to_string());
104 let mut ex = Exchange::new(msg);
105 ex.properties.insert("trace".to_string(), json!("on"));
106 ex
107 }
108
109 #[test]
110 fn test_language_name() {
111 assert_eq!(JsLanguage::new().name(), "js");
112 }
113
114 #[test]
115 fn test_create_expression_valid() {
116 let lang = JsLanguage::new();
117 let result = lang.create_expression("1 + 1");
118 assert!(result.is_ok());
119 }
120
121 #[test]
122 fn test_create_expression_invalid_syntax() {
123 let lang = JsLanguage::new();
124 let result = lang.create_expression("let x = {{{");
125 assert!(result.is_err());
126 assert!(matches!(result, Err(LanguageError::ParseError { .. })));
128 }
129
130 #[test]
131 fn test_create_predicate_valid() {
132 let lang = JsLanguage::new();
133 assert!(lang.create_predicate("true").is_ok());
134 }
135
136 #[test]
137 fn test_create_predicate_invalid() {
138 let lang = JsLanguage::new();
139 assert!(matches!(
140 lang.create_predicate("let !!!"),
141 Err(LanguageError::ParseError { .. })
142 ));
143 }
144
145 #[test]
146 fn test_create_mutating_expression_invalid_syntax() {
147 let lang = JsLanguage::new();
148 let result = lang.create_mutating_expression("let !!!");
149 assert!(result.is_err());
150 assert!(matches!(result, Err(LanguageError::ParseError { .. })));
151 }
152
153 #[test]
154 fn test_create_mutating_expression_valid() {
155 let lang = JsLanguage::new();
156 assert!(
157 lang.create_mutating_expression("camel.headers.set('k','v')")
158 .is_ok()
159 );
160 }
161
162 #[test]
163 fn test_expression_evaluate() {
164 let lang = JsLanguage::new();
165 let expr = lang.create_expression("camel.headers.get('env')").unwrap();
166 let ex = make_exchange();
167 let val = expr.evaluate(&ex).unwrap();
168 assert_eq!(val.as_str().unwrap(), "prod");
169 }
170
171 #[test]
172 fn test_predicate_matches() {
173 let lang = JsLanguage::new();
174 let pred = lang
175 .create_predicate("camel.headers.get('env') === 'prod'")
176 .unwrap();
177 let ex = make_exchange();
178 assert!(pred.matches(&ex).unwrap());
179 }
180
181 #[test]
182 fn test_mutating_expression_propagates() {
183 let lang = JsLanguage::new();
184 let expr = lang
185 .create_mutating_expression(
186 "camel.headers.set('added', 'yes'); camel.body = 'new'; 'done'",
187 )
188 .unwrap();
189 let mut ex = make_exchange();
190 let result = expr.evaluate(&mut ex).unwrap();
191 assert_eq!(result.as_str().unwrap(), "done");
192 assert_eq!(
193 ex.input.headers.get("added").unwrap().as_str().unwrap(),
194 "yes"
195 );
196 assert_eq!(ex.input.body.as_text().unwrap(), "new");
197 }
198
199 #[test]
200 fn test_default_creates_js_language() {
201 let lang = JsLanguage::default();
202 assert_eq!(lang.name(), "js");
203 }
204
205 #[test]
206 fn test_clone_works() {
207 let lang = JsLanguage::new();
208 let lang2 = lang.clone();
209 assert_eq!(lang2.name(), "js");
210 let ex = make_exchange();
212 let expr = lang2.create_expression("42").unwrap();
213 let val = expr.evaluate(&ex).unwrap();
214 assert_eq!(val.as_i64().unwrap(), 42);
215 }
216}