camel_processor/
validate.rs1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use camel_api::{CamelError, Exchange, FilterPredicate};
6use tower::Service;
7
8#[derive(Clone)]
13pub struct ValidateService {
14 predicate: FilterPredicate,
15 expression_source: String,
16}
17
18impl ValidateService {
19 pub fn new(
21 predicate: impl Fn(&Exchange) -> bool + Send + Sync + 'static,
22 expression_source: impl Into<String>,
23 ) -> Self {
24 Self {
25 predicate: FilterPredicate::new(predicate),
26 expression_source: expression_source.into(),
27 }
28 }
29
30 pub fn from_predicate(
32 predicate: FilterPredicate,
33 expression_source: impl Into<String>,
34 ) -> Self {
35 Self {
36 predicate,
37 expression_source: expression_source.into(),
38 }
39 }
40}
41
42impl Service<Exchange> for ValidateService {
43 type Response = Exchange;
44 type Error = CamelError;
45 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
46
47 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
48 Poll::Ready(Ok(()))
49 }
50
51 fn call(&mut self, exchange: Exchange) -> Self::Future {
52 if (self.predicate)(&exchange) {
53 Box::pin(async move { Ok(exchange) })
54 } else {
55 let source = self.expression_source.clone();
56 Box::pin(async move {
57 Err(CamelError::ValidationError(format!(
58 "validate('{source}'): predicate returned false",
59 )))
60 })
61 }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use camel_api::Message;
69
70 #[tokio::test]
74 async fn test_validate_passing_predicate_returns_ok() {
75 let mut svc = ValidateService::new(|_ex: &Exchange| true, "true predicate");
76 let ex = Exchange::new(Message::new("hello"));
77 let result = svc.call(ex).await;
78 assert!(result.is_ok());
79 assert_eq!(result.unwrap().input.body.as_text(), Some("hello"));
80 }
81
82 #[tokio::test]
84 async fn test_validate_failing_predicate_returns_err() {
85 let mut svc = ValidateService::new(|_ex: &Exchange| false, "false predicate");
86 let ex = Exchange::new(Message::new("hello"));
87 let result = svc.call(ex).await;
88 assert!(result.is_err());
89 match result.unwrap_err() {
90 CamelError::ValidationError(msg) => {
91 assert!(
92 msg.contains("false predicate"),
93 "error message should contain expression source, got: {msg}"
94 );
95 }
96 other => panic!("expected ValidationError, got: {other:?}"),
97 }
98 }
99
100 #[tokio::test]
102 async fn test_validate_predicate_evaluates_body() {
103 let mut svc = ValidateService::new(
104 |ex: &Exchange| ex.input.body.as_text().is_some_and(|s| s.len() > 3),
105 "body length > 3",
106 );
107 let short = Exchange::new(Message::new("ab"));
108 let long = Exchange::new(Message::new("abcdef"));
109
110 assert!(svc.call(short).await.is_err());
111 assert!(svc.call(long).await.is_ok());
112 }
113
114 #[tokio::test]
116 async fn test_validate_clone_is_independent() {
117 let svc = ValidateService::new(|_ex: &Exchange| true, "true predicate");
118 let mut cloned = svc.clone();
119 let ex = Exchange::new(Message::new("hi"));
120 let result = cloned.call(ex).await;
121 assert!(result.is_ok());
122 }
123
124 #[tokio::test]
126 async fn test_validate_poll_ready() {
127 let mut svc = ValidateService::new(|_ex: &Exchange| true, "true predicate");
128 let poll = svc.poll_ready(&mut Context::from_waker(futures::task::noop_waker_ref()));
129 assert!(poll.is_ready());
130 match poll {
132 std::task::Poll::Ready(Ok(())) => {}
133 other => panic!("expected Ready(Ok(())), got: {other:?}"),
134 }
135 }
136}