camel_processor/
script_mutator.rs1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4use std::task::{Context, Poll};
5
6use tower::Service;
7
8use camel_api::CamelError;
9use camel_api::exchange::Exchange;
10use camel_language_api::{EvalMeta, MutatingExpression, to_expression_failed};
11
12#[derive(Clone)]
19pub struct ScriptMutator {
20 expression: Arc<dyn MutatingExpression>,
21 meta: EvalMeta,
22}
23
24impl ScriptMutator {
25 pub fn new(expression: Box<dyn MutatingExpression>) -> Self {
29 Self::with_meta(
30 expression,
31 EvalMeta {
32 language: "unknown".to_string(),
33 route_id: String::new(),
34 step_id: String::new(),
35 verb: String::new(),
36 target: None,
37 },
38 )
39 }
40
41 pub fn with_meta(expression: Box<dyn MutatingExpression>, meta: EvalMeta) -> Self {
44 Self {
45 expression: expression.into(),
46 meta,
47 }
48 }
49}
50
51impl Service<Exchange> for ScriptMutator {
52 type Response = Exchange;
53 type Error = CamelError;
54 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
55
56 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
57 Poll::Ready(Ok(()))
58 }
59
60 fn call(&mut self, mut exchange: Exchange) -> Self::Future {
61 let expression = self.expression.clone();
62 let meta = self.meta.clone();
63 Box::pin(async move {
64 let result = expression.evaluate(&mut exchange).await;
65 result
66 .map(|_| exchange)
67 .map_err(|e| to_expression_failed(e, &meta))
68 })
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use camel_api::{CamelError, Exchange, ExpressionErrorClass, Message, Value};
75 use camel_language_api::{EvalMeta, LanguageError};
76 use tower::ServiceExt;
77
78 use super::*;
79
80 struct EvalFailureMutatingExpression;
82
83 #[async_trait::async_trait]
84 impl MutatingExpression for EvalFailureMutatingExpression {
85 async fn evaluate(&self, _exchange: &mut Exchange) -> Result<Value, LanguageError> {
86 Err(LanguageError::EvalFailure {
87 class: ExpressionErrorClass::Runtime,
88 position: None,
89 detail: None,
90 })
91 }
92 }
93
94 struct TestMutatingExpression;
96
97 struct ParseErrorMutatingExpression;
98 struct NotSupportedMutatingExpression;
99 struct UnknownVariableMutatingExpression;
100
101 #[async_trait::async_trait]
102 impl MutatingExpression for TestMutatingExpression {
103 async fn evaluate(&self, exchange: &mut Exchange) -> Result<Value, LanguageError> {
104 exchange
105 .input
106 .headers
107 .insert("mutated".into(), Value::Bool(true));
108 Ok(Value::Null)
109 }
110 }
111
112 #[async_trait::async_trait]
113 impl MutatingExpression for ParseErrorMutatingExpression {
114 async fn evaluate(&self, _exchange: &mut Exchange) -> Result<Value, LanguageError> {
115 Err(LanguageError::ParseError {
116 expr: "x".to_string(),
117 reason: "bad".to_string(),
118 })
119 }
120 }
121
122 #[async_trait::async_trait]
123 impl MutatingExpression for NotSupportedMutatingExpression {
124 async fn evaluate(&self, _exchange: &mut Exchange) -> Result<Value, LanguageError> {
125 Err(LanguageError::NotSupported {
126 feature: "f".to_string(),
127 language: "l".to_string(),
128 })
129 }
130 }
131
132 #[async_trait::async_trait]
133 impl MutatingExpression for UnknownVariableMutatingExpression {
134 async fn evaluate(&self, _exchange: &mut Exchange) -> Result<Value, LanguageError> {
135 Err(LanguageError::UnknownVariable("foo".to_string()))
136 }
137 }
138
139 #[tokio::test]
140 async fn test_script_mutator_modifies_exchange() {
141 let exchange = Exchange::new(Message::new("test"));
142
143 let mutator = ScriptMutator::new(Box::new(TestMutatingExpression));
144
145 let result = mutator.oneshot(exchange).await.unwrap();
146 assert_eq!(result.input.header("mutated"), Some(&Value::Bool(true)));
147 }
148
149 #[tokio::test]
150 async fn test_script_mutator_preserves_body() {
151 let exchange = Exchange::new(Message::new("original body"));
152
153 let mutator = ScriptMutator::new(Box::new(TestMutatingExpression));
154
155 let result = mutator.oneshot(exchange).await.unwrap();
156 assert_eq!(result.input.body.as_text(), Some("original body"));
157 }
158
159 #[tokio::test]
160 async fn test_script_mutator_is_clone() {
161 let mutator = ScriptMutator::new(Box::new(TestMutatingExpression));
162 let _cloned = mutator.clone();
163 }
164
165 #[tokio::test]
166 async fn script_mutator_error_is_expression_failed() {
167 let meta = EvalMeta {
168 language: "rhai".to_string(),
169 route_id: "r1".to_string(),
170 step_id: "script#0".to_string(),
171 verb: "script".to_string(),
172 target: None,
173 };
174 let mutator = ScriptMutator::with_meta(Box::new(EvalFailureMutatingExpression), meta);
175
176 let result = mutator.oneshot(Exchange::new(Message::new("test"))).await;
177
178 assert!(
179 matches!(
180 result,
181 Err(CamelError::ExpressionFailed {
182 ref language,
183 ref verb,
184 class: ExpressionErrorClass::Runtime,
185 ..
186 }) if language == "rhai" && verb == "script"
187 ),
188 "mutating-eval failure must map to ExpressionFailed with the bound meta"
189 );
190 }
191
192 #[tokio::test]
193 async fn test_script_mutator_maps_parse_error() {
194 let exchange = Exchange::new(Message::new("test"));
195 let mutator = ScriptMutator::new(Box::new(ParseErrorMutatingExpression));
196 let result = mutator.oneshot(exchange).await;
197 assert!(matches!(
198 result,
199 Err(CamelError::ExpressionFailed {
200 class: ExpressionErrorClass::Parse,
201 ..
202 })
203 ));
204 }
205
206 #[tokio::test]
207 async fn test_script_mutator_maps_not_supported_error() {
208 let exchange = Exchange::new(Message::new("test"));
209 let mutator = ScriptMutator::new(Box::new(NotSupportedMutatingExpression));
210 let result = mutator.oneshot(exchange).await;
211 assert!(matches!(
214 result,
215 Err(CamelError::ExpressionFailed {
216 ref language,
217 class: ExpressionErrorClass::Runtime,
218 ..
219 }) if language == "unknown"
220 ));
221 }
222
223 #[tokio::test]
224 async fn test_script_mutator_maps_other_language_error() {
225 let exchange = Exchange::new(Message::new("test"));
226 let mutator = ScriptMutator::new(Box::new(UnknownVariableMutatingExpression));
227 let result = mutator.oneshot(exchange).await;
228 assert!(matches!(
229 result,
230 Err(CamelError::ExpressionFailed {
231 class: ExpressionErrorClass::Runtime,
232 ..
233 })
234 ));
235 }
236}