1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use tower::Service;
6
7use camel_api::{CamelError, Exchange, Value};
8
9#[derive(Clone)]
12pub struct DynamicSetHeader<P, F> {
13 inner: P,
14 key: String,
15 expr: F,
16}
17
18impl<P, F> DynamicSetHeader<P, F>
19where
20 F: Fn(&Exchange) -> Value,
21{
22 pub fn new(inner: P, key: impl Into<String>, expr: F) -> Self {
23 Self {
24 inner,
25 key: key.into(),
26 expr,
27 }
28 }
29}
30
31#[derive(Clone)]
33pub struct DynamicSetHeaderLayer<F> {
34 key: String,
35 expr: F,
36}
37
38impl<F> DynamicSetHeaderLayer<F> {
39 pub fn new(key: impl Into<String>, expr: F) -> Self {
40 Self {
41 key: key.into(),
42 expr,
43 }
44 }
45}
46
47impl<S, F> tower::Layer<S> for DynamicSetHeaderLayer<F>
48where
49 F: Clone,
50{
51 type Service = DynamicSetHeader<S, F>;
52
53 fn layer(&self, inner: S) -> Self::Service {
54 DynamicSetHeader {
55 inner,
56 key: self.key.clone(),
57 expr: self.expr.clone(),
58 }
59 }
60}
61
62impl<P, F> Service<Exchange> for DynamicSetHeader<P, F>
63where
64 P: Service<Exchange, Response = Exchange, Error = CamelError> + Clone + Send + 'static,
65 P::Future: Send,
66 F: Fn(&Exchange) -> Value + Clone + Send + Sync + 'static,
67{
68 type Response = Exchange;
69 type Error = CamelError;
70 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
71
72 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
73 self.inner.poll_ready(cx)
74 }
75
76 fn call(&mut self, mut exchange: Exchange) -> Self::Future {
77 let value = (self.expr)(&exchange);
78 exchange.input.headers.insert(self.key.clone(), value);
79 let fut = self.inner.call(exchange);
80 Box::pin(fut)
81 }
82}
83
84#[derive(Clone)]
89pub struct DynamicSetHeaderIfAbsent<P, F> {
90 inner: P,
91 key: String,
92 expr: F,
93}
94
95impl<P, F> DynamicSetHeaderIfAbsent<P, F>
96where
97 F: Fn(&Exchange) -> Value,
98{
99 pub fn new(inner: P, key: impl Into<String>, expr: F) -> Self {
100 Self {
101 inner,
102 key: key.into(),
103 expr,
104 }
105 }
106}
107
108impl<P, F> Service<Exchange> for DynamicSetHeaderIfAbsent<P, F>
109where
110 P: Service<Exchange, Response = Exchange, Error = CamelError> + Clone + Send + 'static,
111 P::Future: Send,
112 F: Fn(&Exchange) -> Value + Clone + Send + Sync + 'static,
113{
114 type Response = Exchange;
115 type Error = CamelError;
116 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
117
118 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
119 self.inner.poll_ready(cx)
120 }
121
122 fn call(&mut self, mut exchange: Exchange) -> Self::Future {
123 if !exchange.input.headers.contains_key(&self.key) {
125 let value = (self.expr)(&exchange);
126 exchange.input.headers.insert(self.key.clone(), value);
127 }
128 let fut = self.inner.call(exchange);
129 Box::pin(fut)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use camel_api::{Exchange, IdentityProcessor, Message, Value};
136 use tower::ServiceExt;
137
138 use super::*;
139
140 #[tokio::test]
141 async fn test_dynamic_set_header_from_body() {
142 let exchange = Exchange::new(Message::new("world"));
143
144 let svc = DynamicSetHeader::new(IdentityProcessor, "greeting", |ex: &Exchange| {
145 Value::String(format!("hello {}", ex.input.body.as_text().unwrap_or("")))
146 });
147
148 let result = svc.oneshot(exchange).await.unwrap();
149 assert_eq!(
150 result.input.header("greeting"),
151 Some(&Value::String("hello world".into()))
152 );
153 }
154
155 #[tokio::test]
156 async fn test_dynamic_set_header_overwrites_existing() {
157 let mut msg = Message::new("new");
158 msg.set_header("key", Value::String("old".into()));
159 let exchange = Exchange::new(msg);
160
161 let svc = DynamicSetHeader::new(IdentityProcessor, "key", |ex: &Exchange| {
162 Value::String(ex.input.body.as_text().unwrap_or("").into())
163 });
164
165 let result = svc.oneshot(exchange).await.unwrap();
166 assert_eq!(
167 result.input.header("key"),
168 Some(&Value::String("new".into()))
169 );
170 }
171
172 #[tokio::test]
173 async fn test_dynamic_set_header_preserves_body() {
174 let exchange = Exchange::new(Message::new("body content"));
175
176 let svc = DynamicSetHeader::new(IdentityProcessor, "len", |ex: &Exchange| {
177 let len = ex.input.body.as_text().map(|t| t.len() as i64).unwrap_or(0);
178 Value::Number(len.into())
179 });
180
181 let result = svc.oneshot(exchange).await.unwrap();
182 assert_eq!(result.input.body.as_text(), Some("body content"));
183 assert_eq!(result.input.header("len"), Some(&Value::Number(12.into())));
184 }
185
186 #[tokio::test]
187 async fn test_dynamic_set_header_layer_composes() {
188 use tower::ServiceBuilder;
189
190 let svc = ServiceBuilder::new()
191 .layer(DynamicSetHeaderLayer::new("computed", |_ex: &Exchange| {
192 Value::Bool(true)
193 }))
194 .service(IdentityProcessor);
195
196 let exchange = Exchange::new(Message::default());
197 let result = svc.oneshot(exchange).await.unwrap();
198 assert_eq!(result.input.header("computed"), Some(&Value::Bool(true)));
199 }
200
201 #[tokio::test]
204 async fn test_dynamic_set_header_if_absent_adds_when_missing() {
205 let exchange = Exchange::new(Message::new("world"));
206
207 let svc = DynamicSetHeaderIfAbsent::new(IdentityProcessor, "greeting", |ex: &Exchange| {
208 Value::String(format!("hello {}", ex.input.body.as_text().unwrap_or("")))
209 });
210
211 let result = svc.oneshot(exchange).await.unwrap();
212 assert_eq!(
213 result.input.header("greeting"),
214 Some(&Value::String("hello world".into()))
215 );
216 }
217
218 #[tokio::test]
219 async fn test_dynamic_set_header_if_absent_preserves_existing() {
220 use std::sync::Arc;
221 use std::sync::atomic::{AtomicUsize, Ordering};
222
223 let mut msg = Message::new("computed");
224 msg.set_header("key", Value::String("original".into()));
225 let exchange = Exchange::new(msg);
226
227 let call_count = Arc::new(AtomicUsize::new(0));
230 let cc = call_count.clone();
231
232 let svc = DynamicSetHeaderIfAbsent::new(IdentityProcessor, "key", move |ex: &Exchange| {
233 cc.fetch_add(1, Ordering::SeqCst);
234 Value::String(ex.input.body.as_text().unwrap_or("").into())
235 });
236
237 let result = svc.oneshot(exchange).await.unwrap();
238 assert_eq!(
239 result.input.header("key"),
240 Some(&Value::String("original".into()))
241 );
242 assert_eq!(
243 call_count.load(Ordering::SeqCst),
244 0,
245 "expression must NOT be evaluated when header is present"
246 );
247 }
248
249 #[tokio::test]
250 async fn test_dynamic_set_header_if_absent_preserves_body() {
251 let exchange = Exchange::new(Message::new("body content"));
252
253 let svc = DynamicSetHeaderIfAbsent::new(IdentityProcessor, "len", |ex: &Exchange| {
254 let len = ex.input.body.as_text().map(|t| t.len() as i64).unwrap_or(0);
255 Value::Number(len.into())
256 });
257
258 let result = svc.oneshot(exchange).await.unwrap();
259 assert_eq!(result.input.body.as_text(), Some("body content"));
260 assert_eq!(result.input.header("len"), Some(&Value::Number(12.into())));
261 }
262}