camel_processor/
choice.rs1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use tower::Service;
6
7use camel_api::{BoxProcessor, CamelError, Exchange, FilterPredicate};
8
9pub struct WhenClause {
11 pub predicate: FilterPredicate,
12 pub pipeline: BoxProcessor,
13}
14
15impl Clone for WhenClause {
16 fn clone(&self) -> Self {
17 Self {
18 predicate: self.predicate.clone(),
19 pipeline: self.pipeline.clone(),
20 }
21 }
22}
23
24#[derive(Clone)]
30pub struct ChoiceService {
31 whens: Vec<WhenClause>,
32 otherwise: Option<BoxProcessor>,
33}
34
35impl ChoiceService {
36 pub fn new(whens: Vec<WhenClause>, otherwise: Option<BoxProcessor>) -> Self {
41 Self { whens, otherwise }
42 }
43}
44
45impl Service<Exchange> for ChoiceService {
46 type Response = Exchange;
47 type Error = CamelError;
48 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
49
50 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
51 Poll::Ready(Ok(()))
52 }
53
54 fn call(&mut self, exchange: Exchange) -> Self::Future {
55 for when in &mut self.whens {
56 if (when.predicate)(&exchange) {
57 let fut = when.pipeline.call(exchange);
58 return Box::pin(fut);
59 }
60 }
61 if let Some(otherwise) = &mut self.otherwise {
62 let fut = otherwise.call(exchange);
63 return Box::pin(fut);
64 }
65 Box::pin(async move { Ok(exchange) })
66 }
67}
68
69pub struct WhenClauseSegment {
73 pub predicate: camel_api::FilterPredicate,
74 pub body: camel_api::OutcomeSegment,
75}
76
77impl Clone for WhenClauseSegment {
78 fn clone(&self) -> Self {
79 Self {
80 predicate: self.predicate.clone(),
81 body: self.body.clone(),
82 }
83 }
84}
85
86pub struct ChoiceSegment {
98 pub clauses: Vec<WhenClauseSegment>,
99 pub otherwise: Option<camel_api::OutcomeSegment>,
100}
101
102impl Clone for ChoiceSegment {
103 fn clone(&self) -> Self {
104 Self {
105 clauses: self
106 .clauses
107 .iter()
108 .map(|c| WhenClauseSegment {
109 predicate: c.predicate.clone(),
110 body: c.body.clone(),
111 })
112 .collect(),
113 otherwise: self.otherwise.clone(),
114 }
115 }
116}
117
118impl camel_api::OutcomePipeline for ChoiceSegment {
119 fn clone_box(&self) -> Box<dyn camel_api::OutcomePipeline> {
120 Box::new(self.clone())
121 }
122
123 fn run<'a>(
124 &'a mut self,
125 exchange: camel_api::Exchange,
126 ) -> Pin<Box<dyn Future<Output = camel_api::PipelineOutcome> + Send + 'a>> {
127 Box::pin(async move {
128 for clause in self.clauses.iter_mut() {
129 if (clause.predicate)(&exchange) {
130 return clause.body.run(exchange).await;
131 }
132 }
133 if let Some(otherwise) = self.otherwise.as_mut() {
134 otherwise.run(exchange).await
135 } else {
136 camel_api::PipelineOutcome::Completed(exchange)
137 }
138 })
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use camel_api::{Body, BoxProcessorExt, Message, Value};
146 use tower::ServiceExt;
147
148 fn append_body(suffix: &'static str) -> BoxProcessor {
149 BoxProcessor::from_fn(move |mut ex: Exchange| {
150 Box::pin(async move {
151 if let Body::Text(s) = &ex.input.body {
152 ex.input.body = Body::Text(format!("{s}{suffix}"));
153 }
154 Ok(ex)
155 })
156 })
157 }
158
159 fn failing() -> BoxProcessor {
160 BoxProcessor::from_fn(|_ex| {
161 Box::pin(async { Err(CamelError::ProcessorError("boom".into())) })
162 })
163 }
164
165 fn pred_header(name: &'static str) -> FilterPredicate {
166 FilterPredicate::new(move |ex: &Exchange| ex.input.header(name).is_some())
167 }
168
169 #[tokio::test]
171 async fn test_choice_first_when_matches() {
172 let whens = vec![
173 WhenClause {
174 predicate: pred_header("a"),
175 pipeline: append_body("-A"),
176 },
177 WhenClause {
178 predicate: pred_header("b"),
179 pipeline: append_body("-B"),
180 },
181 ];
182 let mut svc = ChoiceService::new(whens, None);
183 let mut ex = Exchange::new(Message::new("x"));
184 ex.input.set_header("a", Value::Bool(true));
185 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
186 assert_eq!(result.input.body.as_text(), Some("x-A"));
187 }
188
189 #[tokio::test]
191 async fn test_choice_second_when_matches() {
192 let whens = vec![
193 WhenClause {
194 predicate: pred_header("a"),
195 pipeline: append_body("-A"),
196 },
197 WhenClause {
198 predicate: pred_header("b"),
199 pipeline: append_body("-B"),
200 },
201 ];
202 let mut svc = ChoiceService::new(whens, None);
203 let mut ex = Exchange::new(Message::new("x"));
204 ex.input.set_header("b", Value::Bool(true));
205 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
206 assert_eq!(result.input.body.as_text(), Some("x-B"));
207 }
208
209 #[tokio::test]
211 async fn test_choice_short_circuits_at_first_match() {
212 let whens = vec![
213 WhenClause {
214 predicate: pred_header("a"),
215 pipeline: append_body("-A"),
216 },
217 WhenClause {
218 predicate: pred_header("b"),
219 pipeline: append_body("-B"),
220 },
221 ];
222 let mut svc = ChoiceService::new(whens, None);
223 let mut ex = Exchange::new(Message::new("x"));
224 ex.input.set_header("a", Value::Bool(true));
225 ex.input.set_header("b", Value::Bool(true));
226 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
227 assert_eq!(result.input.body.as_text(), Some("x-A"));
228 }
229
230 #[tokio::test]
232 async fn test_choice_otherwise_fires_when_no_when_matches() {
233 let whens = vec![WhenClause {
234 predicate: pred_header("a"),
235 pipeline: append_body("-A"),
236 }];
237 let mut svc = ChoiceService::new(whens, Some(append_body("-else")));
238 let ex = Exchange::new(Message::new("x"));
239 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
240 assert_eq!(result.input.body.as_text(), Some("x-else"));
241 }
242
243 #[tokio::test]
245 async fn test_choice_no_match_no_otherwise_passthrough() {
246 let whens = vec![WhenClause {
247 predicate: pred_header("a"),
248 pipeline: append_body("-A"),
249 }];
250 let mut svc = ChoiceService::new(whens, None);
251 let ex = Exchange::new(Message::new("untouched"));
252 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
253 assert_eq!(result.input.body.as_text(), Some("untouched"));
254 }
255
256 #[tokio::test]
258 async fn test_choice_error_in_when_propagates() {
259 let whens = vec![WhenClause {
260 predicate: pred_header("a"),
261 pipeline: failing(),
262 }];
263 let mut svc = ChoiceService::new(whens, None);
264 let mut ex = Exchange::new(Message::new("x"));
265 ex.input.set_header("a", Value::Bool(true));
266 let result = svc.ready().await.unwrap().call(ex).await;
267 assert!(result.is_err());
268 assert!(result.unwrap_err().to_string().contains("boom"));
269 }
270
271 #[tokio::test]
273 async fn test_choice_error_in_otherwise_propagates() {
274 let mut svc = ChoiceService::new(vec![], Some(failing()));
275 let ex = Exchange::new(Message::new("x"));
276 let result = svc.ready().await.unwrap().call(ex).await;
277 assert!(result.is_err());
278 }
279}