camel_processor/
set_body.rs1use std::future::Future;
2use std::pin::Pin;
3use std::task::{Context, Poll};
4
5use tower::Service;
6
7use camel_api::body::Body;
8use camel_api::{CamelError, Exchange, Value, ValueSource};
9
10fn value_to_body(value: Value) -> Body {
15 match value {
16 Value::Null => Body::Empty,
17 Value::String(s) => Body::Text(s),
18 other => Body::Json(other),
19 }
20}
21
22#[derive(Clone)]
27pub struct SetBody<P> {
28 inner: P,
29 source: ValueSource,
30}
31
32impl<P> SetBody<P> {
33 pub fn new(inner: P, source: impl Into<ValueSource>) -> Self {
34 Self {
35 inner,
36 source: source.into(),
37 }
38 }
39}
40
41#[derive(Clone)]
43pub struct SetBodyLayer {
44 source: ValueSource,
45}
46
47impl SetBodyLayer {
48 pub fn new(source: impl Into<ValueSource>) -> Self {
49 Self {
50 source: source.into(),
51 }
52 }
53}
54
55impl<S> tower::Layer<S> for SetBodyLayer {
56 type Service = SetBody<S>;
57
58 fn layer(&self, inner: S) -> Self::Service {
59 SetBody {
60 inner,
61 source: self.source.clone(),
62 }
63 }
64}
65
66impl<P> Service<Exchange> for SetBody<P>
67where
68 P: Service<Exchange, Response = Exchange, Error = CamelError> + Clone + Send + 'static,
69 P::Future: Send,
70{
71 type Response = Exchange;
72 type Error = CamelError;
73 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
74
75 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
76 self.inner.poll_ready(cx)
77 }
78
79 fn call(&mut self, mut exchange: Exchange) -> Self::Future {
80 let source = self.source.clone();
81 let clone = self.inner.clone();
84 let mut inner = std::mem::replace(&mut self.inner, clone);
85 Box::pin(async move {
86 let value = source.evaluate(&exchange).await?;
87 exchange.input.body = value_to_body(value);
88 inner.call(exchange).await
89 })
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use std::sync::Arc;
96 use std::sync::atomic::{AtomicUsize, Ordering};
97
98 use camel_api::{
99 BoxValueFuture, CamelError, Exchange, IdentityProcessor, Message, Value, ValueSource,
100 };
101 use tower::ServiceExt;
102
103 use super::*;
104
105 fn sync_source<F>(f: F) -> ValueSource
106 where
107 F: Fn(&Exchange) -> Value + Send + Sync + 'static,
108 {
109 ValueSource::Sync(Arc::new(f))
110 }
111
112 fn failing_source() -> ValueSource {
113 ValueSource::Async(Arc::new(|_: &Exchange| {
114 Box::pin(async { Err(CamelError::ProcessorError("expr boom".into())) })
115 as BoxValueFuture
116 }))
117 }
118
119 #[derive(Clone)]
121 struct CountingInner {
122 called: Arc<AtomicUsize>,
123 }
124
125 impl Service<Exchange> for CountingInner {
126 type Response = Exchange;
127 type Error = CamelError;
128 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
129
130 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
131 Poll::Ready(Ok(()))
132 }
133
134 fn call(&mut self, exchange: Exchange) -> Self::Future {
135 self.called.fetch_add(1, Ordering::SeqCst);
136 Box::pin(async { Ok(exchange) })
137 }
138 }
139
140 #[tokio::test]
141 async fn set_body_dynamic_error_keeps_body() {
142 let called = Arc::new(AtomicUsize::new(0));
143 let svc = SetBody::new(
144 CountingInner {
145 called: Arc::clone(&called),
146 },
147 failing_source(),
148 );
149
150 let result = svc.oneshot(Exchange::new(Message::new("original"))).await;
154
155 assert!(result.is_err(), "failed evaluation must fail the step");
156 assert_eq!(
157 called.load(Ordering::SeqCst),
158 0,
159 "inner must NOT run when evaluation fails"
160 );
161 }
162
163 #[tokio::test]
164 async fn test_set_body_static_replaces_body() {
165 let exchange = Exchange::new(Message::new("original"));
166 let svc = SetBody::new(
167 IdentityProcessor,
168 sync_source(|_ex: &Exchange| Value::String("replaced".into())),
169 );
170 let result = svc.oneshot(exchange).await.unwrap();
171 assert_eq!(result.input.body.as_text(), Some("replaced"));
172 }
173
174 #[tokio::test]
175 async fn test_set_body_dynamic_reads_exchange() {
176 let mut msg = Message::new("hello");
177 msg.set_header("suffix", camel_api::Value::String("!".into()));
178 let exchange = Exchange::new(msg);
179
180 let svc = SetBody::new(
181 IdentityProcessor,
182 sync_source(|ex: &Exchange| {
183 let base = ex.input.body.as_text().unwrap_or("");
184 let suffix = ex
185 .input
186 .header("suffix")
187 .and_then(|v| v.as_str())
188 .unwrap_or("");
189 Value::String(format!("{}{}", base, suffix))
190 }),
191 );
192
193 let result = svc.oneshot(exchange).await.unwrap();
194 assert_eq!(result.input.body.as_text(), Some("hello!"));
195 }
196
197 #[tokio::test]
198 async fn test_set_body_preserves_headers() {
199 let mut msg = Message::default();
200 msg.set_header("keep", camel_api::Value::Bool(true));
201 let exchange = Exchange::new(msg);
202
203 let svc = SetBody::new(
204 IdentityProcessor,
205 sync_source(|_ex: &Exchange| Value::String("new".into())),
206 );
207 let result = svc.oneshot(exchange).await.unwrap();
208 assert_eq!(
209 result.input.header("keep"),
210 Some(&camel_api::Value::Bool(true))
211 );
212 assert_eq!(result.input.body.as_text(), Some("new"));
213 }
214
215 #[tokio::test]
216 async fn test_set_body_layer_composes() {
217 use tower::ServiceBuilder;
218
219 let svc = ServiceBuilder::new()
220 .layer(SetBodyLayer::new(sync_source(|_ex: &Exchange| {
221 Value::String("layered".into())
222 })))
223 .service(IdentityProcessor);
224
225 let exchange = Exchange::new(Message::default());
226 let result = svc.oneshot(exchange).await.unwrap();
227 assert_eq!(result.input.body.as_text(), Some("layered"));
228 }
229}