1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use tower::Service;
6use tower::ServiceExt;
7
8use camel_api::{BoxProcessor, CamelError, Exchange, PredicateSource};
9
10pub struct WhenClause {
13 pub predicate: PredicateSource,
14 pub pipeline: BoxProcessor,
15}
16
17impl Clone for WhenClause {
18 fn clone(&self) -> Self {
19 Self {
20 predicate: self.predicate.clone(),
21 pipeline: self.pipeline.clone(),
22 }
23 }
24}
25
26#[derive(Clone)]
34pub struct ChoiceService {
35 whens: Vec<WhenClause>,
36 otherwise: Option<BoxProcessor>,
37}
38
39impl ChoiceService {
40 pub fn new(whens: Vec<WhenClause>, otherwise: Option<BoxProcessor>) -> Self {
45 Self { whens, otherwise }
46 }
47}
48
49impl Service<Exchange> for ChoiceService {
50 type Response = Exchange;
51 type Error = CamelError;
52 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
53
54 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
55 Poll::Ready(Ok(()))
56 }
57
58 fn call(&mut self, exchange: Exchange) -> Self::Future {
59 let whens = self.whens.clone();
62 let otherwise = self.otherwise.clone();
63 Box::pin(async move {
64 let mut whens = whens;
65 for when in whens.iter_mut() {
66 if when.predicate.matches(&exchange).await? {
67 return when.pipeline.ready().await?.call(exchange).await;
68 }
69 }
70 if let Some(mut otherwise) = otherwise {
71 otherwise.ready().await?.call(exchange).await
72 } else {
73 Ok(exchange)
74 }
75 })
76 }
77}
78
79pub struct WhenClauseSegment {
83 pub predicate: camel_api::PredicateSource,
84 pub body: camel_api::OutcomeSegment,
85}
86
87impl Clone for WhenClauseSegment {
88 fn clone(&self) -> Self {
89 Self {
90 predicate: self.predicate.clone(),
91 body: self.body.clone(),
92 }
93 }
94}
95
96pub struct ChoiceSegment {
109 pub clauses: Vec<WhenClauseSegment>,
110 pub otherwise: Option<camel_api::OutcomeSegment>,
111}
112
113impl Clone for ChoiceSegment {
114 fn clone(&self) -> Self {
115 Self {
116 clauses: self
117 .clauses
118 .iter()
119 .map(|c| WhenClauseSegment {
120 predicate: c.predicate.clone(),
121 body: c.body.clone(),
122 })
123 .collect(),
124 otherwise: self.otherwise.clone(),
125 }
126 }
127}
128
129impl camel_api::OutcomePipeline for ChoiceSegment {
130 fn clone_box(&self) -> Box<dyn camel_api::OutcomePipeline> {
131 Box::new(self.clone())
132 }
133
134 fn run<'a>(
135 &'a mut self,
136 exchange: camel_api::Exchange,
137 ) -> Pin<Box<dyn Future<Output = camel_api::PipelineOutcome> + Send + 'a>> {
138 Box::pin(async move {
139 for clause in self.clauses.iter_mut() {
140 match clause.predicate.matches(&exchange).await {
141 Ok(true) => return clause.body.run(exchange).await,
142 Ok(false) => continue,
143 Err(err) => return camel_api::PipelineOutcome::Failed(err),
144 }
145 }
146 if let Some(otherwise) = self.otherwise.as_mut() {
147 otherwise.run(exchange).await
148 } else {
149 camel_api::PipelineOutcome::Completed(exchange)
150 }
151 })
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use camel_api::{Body, BoxProcessorExt, Message, Value};
159 use tower::ServiceExt;
160
161 fn append_body(suffix: &'static str) -> BoxProcessor {
162 BoxProcessor::from_fn(move |mut ex: Exchange| {
163 Box::pin(async move {
164 if let Body::Text(s) = &ex.input.body {
165 ex.input.body = Body::Text(format!("{s}{suffix}"));
166 }
167 Ok(ex)
168 })
169 })
170 }
171
172 fn failing() -> BoxProcessor {
173 BoxProcessor::from_fn(|_ex| {
174 Box::pin(async { Err(CamelError::ProcessorError("boom".into())) })
175 })
176 }
177
178 fn pred_header(name: &'static str) -> PredicateSource {
179 PredicateSource::Sync(FilterPredicate::new(move |ex: &Exchange| {
180 ex.input.header(name).is_some()
181 }))
182 }
183
184 #[tokio::test]
186 async fn test_choice_first_when_matches() {
187 let whens = vec![
188 WhenClause {
189 predicate: pred_header("a"),
190 pipeline: append_body("-A"),
191 },
192 WhenClause {
193 predicate: pred_header("b"),
194 pipeline: append_body("-B"),
195 },
196 ];
197 let mut svc = ChoiceService::new(whens, None);
198 let mut ex = Exchange::new(Message::new("x"));
199 ex.input.set_header("a", Value::Bool(true));
200 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
201 assert_eq!(result.input.body.as_text(), Some("x-A"));
202 }
203
204 #[tokio::test]
206 async fn test_choice_second_when_matches() {
207 let whens = vec![
208 WhenClause {
209 predicate: pred_header("a"),
210 pipeline: append_body("-A"),
211 },
212 WhenClause {
213 predicate: pred_header("b"),
214 pipeline: append_body("-B"),
215 },
216 ];
217 let mut svc = ChoiceService::new(whens, None);
218 let mut ex = Exchange::new(Message::new("x"));
219 ex.input.set_header("b", Value::Bool(true));
220 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
221 assert_eq!(result.input.body.as_text(), Some("x-B"));
222 }
223
224 #[tokio::test]
226 async fn test_choice_short_circuits_at_first_match() {
227 let whens = vec![
228 WhenClause {
229 predicate: pred_header("a"),
230 pipeline: append_body("-A"),
231 },
232 WhenClause {
233 predicate: pred_header("b"),
234 pipeline: append_body("-B"),
235 },
236 ];
237 let mut svc = ChoiceService::new(whens, None);
238 let mut ex = Exchange::new(Message::new("x"));
239 ex.input.set_header("a", Value::Bool(true));
240 ex.input.set_header("b", Value::Bool(true));
241 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
242 assert_eq!(result.input.body.as_text(), Some("x-A"));
243 }
244
245 #[tokio::test]
247 async fn test_choice_otherwise_fires_when_no_when_matches() {
248 let whens = vec![WhenClause {
249 predicate: pred_header("a"),
250 pipeline: append_body("-A"),
251 }];
252 let mut svc = ChoiceService::new(whens, Some(append_body("-else")));
253 let ex = Exchange::new(Message::new("x"));
254 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
255 assert_eq!(result.input.body.as_text(), Some("x-else"));
256 }
257
258 #[tokio::test]
260 async fn test_choice_no_match_no_otherwise_passthrough() {
261 let whens = vec![WhenClause {
262 predicate: pred_header("a"),
263 pipeline: append_body("-A"),
264 }];
265 let mut svc = ChoiceService::new(whens, None);
266 let ex = Exchange::new(Message::new("untouched"));
267 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
268 assert_eq!(result.input.body.as_text(), Some("untouched"));
269 }
270
271 #[tokio::test]
273 async fn test_choice_error_in_when_propagates() {
274 let whens = vec![WhenClause {
275 predicate: pred_header("a"),
276 pipeline: failing(),
277 }];
278 let mut svc = ChoiceService::new(whens, None);
279 let mut ex = Exchange::new(Message::new("x"));
280 ex.input.set_header("a", Value::Bool(true));
281 let result = svc.ready().await.unwrap().call(ex).await;
282 assert!(result.is_err());
283 assert!(result.unwrap_err().to_string().contains("boom"));
284 }
285
286 #[tokio::test]
288 async fn test_choice_error_in_otherwise_propagates() {
289 let mut svc = ChoiceService::new(vec![], Some(failing()));
290 let ex = Exchange::new(Message::new("x"));
291 let result = svc.ready().await.unwrap().call(ex).await;
292 assert!(result.is_err());
293 }
294
295 use std::sync::Arc;
298 use std::sync::atomic::{AtomicU32, Ordering};
299
300 use camel_api::FilterPredicate;
301 use camel_api::outcome_pipeline::OutcomePipeline as _;
302 use camel_api::{ExpressionErrorClass, PipelineOutcome, PredicateSource};
303
304 fn expression_failed() -> CamelError {
305 CamelError::ExpressionFailed {
306 language: "rhai".to_string(),
307 route_id: "r1".to_string(),
308 step_id: "step#0".to_string(),
309 verb: "choice".to_string(),
310 class: ExpressionErrorClass::Runtime,
311 position: None,
312 conversion: None,
313 cause: None,
314 }
315 }
316
317 fn async_err_predicate(err: CamelError) -> PredicateSource {
318 PredicateSource::Async(Arc::new(move |_: &Exchange| {
319 let err = err.clone();
320 Box::pin(async move { Err(err) }) as camel_api::BoxBoolFuture
321 }))
322 }
323
324 struct RecordRun(Arc<AtomicU32>);
325
326 impl camel_api::OutcomePipeline for RecordRun {
327 fn clone_box(&self) -> Box<dyn camel_api::OutcomePipeline> {
328 Box::new(RecordRun(Arc::clone(&self.0)))
329 }
330 fn run<'a>(
331 &'a mut self,
332 exchange: Exchange,
333 ) -> Pin<Box<dyn Future<Output = PipelineOutcome> + Send + 'a>> {
334 let c = Arc::clone(&self.0);
335 Box::pin(async move {
336 c.fetch_add(1, Ordering::SeqCst);
337 PipelineOutcome::Completed(exchange)
338 })
339 }
340 }
341
342 #[tokio::test]
343 async fn choice_when_predicate_error_fails_step() {
344 let body_calls = Arc::new(AtomicU32::new(0));
345 let otherwise_calls = Arc::new(AtomicU32::new(0));
346 let mut seg = ChoiceSegment {
347 clauses: vec![WhenClauseSegment {
348 predicate: async_err_predicate(expression_failed()),
349 body: camel_api::OutcomeSegment::new(Box::new(RecordRun(Arc::clone(&body_calls)))),
350 }],
351 otherwise: Some(camel_api::OutcomeSegment::new(Box::new(RecordRun(
352 Arc::clone(&otherwise_calls),
353 )))),
354 };
355 let outcome = seg.run(Exchange::default()).await;
356 match outcome {
357 PipelineOutcome::Failed(err) => {
358 assert!(
359 matches!(err, CamelError::ExpressionFailed { .. }),
360 "expected ExpressionFailed, got {err:?}"
361 );
362 }
363 other => panic!("expected PipelineOutcome::Failed, got {other:?}"),
364 }
365 assert_eq!(
366 body_calls.load(Ordering::SeqCst),
367 0,
368 "when body must not run when its predicate errors"
369 );
370 assert_eq!(
371 otherwise_calls.load(Ordering::SeqCst),
372 0,
373 "otherwise must never execute when a when-predicate errors"
374 );
375 }
376}