camel_processor/
delayer.rs1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4use std::time::Duration;
5
6use tower::Service;
7
8use camel_api::{CamelError, DelayConfig, Exchange, Value};
9
10#[derive(Clone)]
11pub struct DelayerService {
12 config: DelayConfig,
13}
14
15impl DelayerService {
16 pub fn new(config: DelayConfig) -> Self {
17 Self { config }
18 }
19
20 fn effective_delay_ms(&self, exchange: &Exchange) -> u64 {
21 if let Some(ref header) = self.config.dynamic_header
22 && let Some(Value::Number(n)) = exchange.input.header(header)
23 && let Some(n) = n.as_f64()
24 && n >= 0.0
25 {
26 let cap = self.config.max_delay_ms as f64;
31 let clamped = n.min(cap);
32 return clamped as u64;
33 }
34 self.config.delay_ms
35 }
36}
37
38impl Service<Exchange> for DelayerService {
39 type Response = Exchange;
40 type Error = CamelError;
41 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
42
43 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
44 Poll::Ready(Ok(()))
45 }
46
47 fn call(&mut self, exchange: Exchange) -> Self::Future {
48 let delay_ms = self.effective_delay_ms(&exchange);
49 Box::pin(async move {
50 if delay_ms > 0 {
51 tokio::time::sleep(Duration::from_millis(delay_ms)).await;
52 }
53 Ok(exchange)
54 })
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use std::time::{Duration, Instant};
61
62 use camel_api::DelayConfig;
63 use camel_api::{Exchange, Message, Value};
64 use tower::{Service, ServiceExt};
65
66 use super::DelayerService;
67
68 #[tokio::test]
69 async fn test_delayer_fixed_delay() {
70 let config = DelayConfig::new(100);
71 let mut svc = DelayerService::new(config);
72
73 let start = Instant::now();
74 let ex = Exchange::new(Message::new("test"));
75 let result = svc.ready().await.unwrap().call(ex).await;
76 let elapsed = start.elapsed();
77
78 assert!(result.is_ok());
79 assert!(elapsed >= Duration::from_millis(90));
80 }
81
82 #[tokio::test]
83 async fn test_delayer_dynamic_from_header() {
84 let config = DelayConfig::new(5000).with_dynamic_header("CamelDelayMs");
85 let mut svc = DelayerService::new(config);
86
87 let mut ex = Exchange::new(Message::new("test"));
88 ex.input
89 .set_header("CamelDelayMs", Value::Number(50.into()));
90
91 let start = Instant::now();
92 let result = svc.ready().await.unwrap().call(ex).await;
93 let elapsed = start.elapsed();
94
95 assert!(result.is_ok());
96 assert!(elapsed >= Duration::from_millis(40));
97 assert!(elapsed < Duration::from_millis(200));
98 }
99
100 #[tokio::test]
101 async fn test_delayer_zero_delay() {
102 let config = DelayConfig::new(0);
103 let mut svc = DelayerService::new(config);
104
105 let start = Instant::now();
106 let ex = Exchange::new(Message::new("test"));
107 let result = svc.ready().await.unwrap().call(ex).await;
108 let elapsed = start.elapsed();
109
110 assert!(result.is_ok());
111 assert!(elapsed < Duration::from_millis(50));
112 }
113
114 #[tokio::test]
115 async fn test_delayer_zero_from_header() {
116 let config = DelayConfig::new(5000).with_dynamic_header("CamelDelayMs");
117 let mut svc = DelayerService::new(config);
118
119 let mut ex = Exchange::new(Message::new("test"));
120 ex.input.set_header("CamelDelayMs", Value::Number(0.into()));
121
122 let start = Instant::now();
123 let result = svc.ready().await.unwrap().call(ex).await;
124 let elapsed = start.elapsed();
125
126 assert!(result.is_ok());
127 assert!(elapsed < Duration::from_millis(50));
128 }
129
130 #[tokio::test]
131 async fn test_delayer_fallback_on_invalid_header() {
132 let config = DelayConfig::new(80).with_dynamic_header("CamelDelayMs");
133 let mut svc = DelayerService::new(config);
134
135 let mut ex = Exchange::new(Message::new("test"));
136 ex.input
137 .set_header("CamelDelayMs", Value::String("not a number".into()));
138
139 let start = Instant::now();
140 let result = svc.ready().await.unwrap().call(ex).await;
141 let elapsed = start.elapsed();
142
143 assert!(result.is_ok());
144 assert!(elapsed >= Duration::from_millis(70));
145 }
146
147 #[tokio::test]
148 async fn test_delayer_negative_header() {
149 let config = DelayConfig::new(80).with_dynamic_header("CamelDelayMs");
150 let mut svc = DelayerService::new(config);
151
152 let mut ex = Exchange::new(Message::new("test"));
153 ex.input
154 .set_header("CamelDelayMs", Value::Number((-100).into()));
155
156 let start = Instant::now();
157 let result = svc.ready().await.unwrap().call(ex).await;
158 let elapsed = start.elapsed();
159
160 assert!(result.is_ok());
161 assert!(elapsed >= Duration::from_millis(70));
162 }
163
164 #[tokio::test]
165 async fn test_delayer_missing_header() {
166 let config = DelayConfig::new(80).with_dynamic_header("CamelDelayMs");
167 let mut svc = DelayerService::new(config);
168
169 let start = Instant::now();
170 let ex = Exchange::new(Message::new("test"));
171 let result = svc.ready().await.unwrap().call(ex).await;
172 let elapsed = start.elapsed();
173
174 assert!(result.is_ok());
175 assert!(elapsed >= Duration::from_millis(70));
176 }
177
178 #[tokio::test]
186 async fn test_huge_header_value_clamps_to_max_delay_ms() {
187 let config = DelayConfig::new(80)
188 .with_dynamic_header("CamelDelayMs")
189 .with_max_delay_ms(50); let mut svc = DelayerService::new(config);
191
192 let mut ex = Exchange::new(Message::new("test"));
193 ex.input.set_header(
195 "CamelDelayMs",
196 Value::Number(serde_json::Number::from_f64(1e20).unwrap()),
197 );
198
199 let start = Instant::now();
200 let result = svc.ready().await.unwrap().call(ex).await;
201 let elapsed = start.elapsed();
202
203 assert!(result.is_ok(), "huge header value must clamp, not panic");
204 assert!(
205 elapsed < Duration::from_millis(500),
206 "should clamp to ~50ms, got {elapsed:?}"
207 );
208 assert!(
209 elapsed >= Duration::from_millis(40),
210 "should sleep at least ~50ms (the cap), got {elapsed:?}"
211 );
212 }
213
214 #[tokio::test]
216 async fn test_small_header_value_unaffected_by_clamp() {
217 let config = DelayConfig::new(80)
218 .with_dynamic_header("CamelDelayMs")
219 .with_max_delay_ms(3_600_000); let mut svc = DelayerService::new(config);
221
222 let mut ex = Exchange::new(Message::new("test"));
223 ex.input
224 .set_header("CamelDelayMs", Value::Number(20.into()));
225
226 let start = Instant::now();
227 let result = svc.ready().await.unwrap().call(ex).await;
228 let elapsed = start.elapsed();
229
230 assert!(result.is_ok());
231 assert!(
232 elapsed < Duration::from_millis(200),
233 "small value should be unaffected, got {elapsed:?}"
234 );
235 }
236}