camel_processor/
filter.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
9#[derive(Clone)]
14pub struct FilterService {
15 predicate: FilterPredicate,
16 sub_pipeline: BoxProcessor,
17}
18
19impl FilterService {
20 pub fn new(
22 predicate: impl Fn(&Exchange) -> bool + Send + Sync + 'static,
23 sub_pipeline: BoxProcessor,
24 ) -> Self {
25 Self {
26 predicate: FilterPredicate::new(predicate),
27 sub_pipeline,
28 }
29 }
30
31 pub fn from_predicate(predicate: FilterPredicate, sub_pipeline: BoxProcessor) -> Self {
33 Self {
34 predicate,
35 sub_pipeline,
36 }
37 }
38}
39
40impl Service<Exchange> for FilterService {
41 type Response = Exchange;
42 type Error = CamelError;
43 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
44
45 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
46 self.sub_pipeline.poll_ready(cx)
47 }
48
49 fn call(&mut self, exchange: Exchange) -> Self::Future {
50 if (self.predicate)(&exchange) {
51 let fut = self.sub_pipeline.call(exchange);
52 Box::pin(fut)
53 } else {
54 Box::pin(async move { Ok(exchange) })
55 }
56 }
57}
58
59pub struct FilterSegment {
73 pub predicate: camel_api::FilterPredicate,
74 pub body: camel_api::OutcomeSegment,
75}
76
77impl Clone for FilterSegment {
78 fn clone(&self) -> Self {
79 Self {
80 predicate: self.predicate.clone(),
81 body: self.body.clone(),
82 }
83 }
84}
85
86impl camel_api::OutcomePipeline for FilterSegment {
87 fn clone_box(&self) -> Box<dyn camel_api::OutcomePipeline> {
88 Box::new(self.clone())
89 }
90
91 fn run<'a>(
92 &'a mut self,
93 exchange: camel_api::Exchange,
94 ) -> Pin<Box<dyn Future<Output = camel_api::PipelineOutcome> + Send + 'a>> {
95 Box::pin(async move {
96 if (self.predicate)(&exchange) {
97 self.body.run(exchange).await
98 } else {
99 camel_api::PipelineOutcome::Completed(exchange)
100 }
101 })
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use camel_api::{Body, BoxProcessorExt, Message, Value};
109 use tower::ServiceExt;
110
111 fn passthrough() -> BoxProcessor {
112 BoxProcessor::from_fn(|ex| Box::pin(async move { Ok(ex) }))
113 }
114
115 fn uppercase_body() -> BoxProcessor {
116 BoxProcessor::from_fn(|mut ex: Exchange| {
117 Box::pin(async move {
118 if let Body::Text(s) = &ex.input.body {
119 ex.input.body = Body::Text(s.to_uppercase());
120 }
121 Ok(ex)
122 })
123 })
124 }
125
126 fn failing() -> BoxProcessor {
127 BoxProcessor::from_fn(|_ex| {
128 Box::pin(async { Err(CamelError::ProcessorError("boom".into())) })
129 })
130 }
131
132 #[tokio::test]
134 async fn test_filter_passes_matching_exchange() {
135 let mut svc = FilterService::new(
136 |ex: &Exchange| ex.input.header("active").is_some(),
137 uppercase_body(),
138 );
139 let mut ex = Exchange::new(Message::new("hello"));
140 ex.input.set_header("active", Value::Bool(true));
141 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
142 assert_eq!(result.input.body.as_text(), Some("HELLO"));
143 }
144
145 #[tokio::test]
147 async fn test_filter_blocks_non_matching_exchange() {
148 let mut svc = FilterService::new(
149 |ex: &Exchange| ex.input.header("active").is_some(),
150 uppercase_body(),
151 );
152 let ex = Exchange::new(Message::new("hello"));
153 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
154 assert_eq!(result.input.body.as_text(), Some("hello"));
156 }
157
158 #[tokio::test]
160 async fn test_filter_sub_pipeline_transforms_body() {
161 let mut svc = FilterService::new(|_: &Exchange| true, uppercase_body());
162 let ex = Exchange::new(Message::new("world"));
163 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
164 assert_eq!(result.input.body.as_text(), Some("WORLD"));
165 }
166
167 #[tokio::test]
169 async fn test_filter_sub_pipeline_error_propagates() {
170 let mut svc = FilterService::new(|_: &Exchange| true, failing());
171 let ex = Exchange::new(Message::new("x"));
172 let result = svc.ready().await.unwrap().call(ex).await;
173 assert!(result.is_err());
174 assert!(result.unwrap_err().to_string().contains("boom"));
175 }
176
177 #[tokio::test]
179 async fn test_filter_predicate_receives_original_exchange() {
180 let mut svc = FilterService::new(
181 |ex: &Exchange| ex.input.body.as_text() == Some("check"),
182 uppercase_body(),
183 );
184 let ex = Exchange::new(Message::new("check"));
185 let result = svc.ready().await.unwrap().call(ex).await.unwrap();
186 assert_eq!(result.input.body.as_text(), Some("CHECK"));
187 }
188
189 #[tokio::test]
191 async fn test_filter_clone_is_independent() {
192 let svc = FilterService::new(|_: &Exchange| true, passthrough());
193 let mut clone = svc.clone();
194 let ex = Exchange::new(Message::new("hi"));
195 let result = clone.ready().await.unwrap().call(ex).await.unwrap();
196 assert_eq!(result.input.body.as_text(), Some("hi"));
197 }
198}