1use std::future::Future;
7use std::pin::Pin;
8use std::sync::Arc;
9use std::task::{Context, Poll};
10
11use tokio_util::sync::CancellationToken;
12use tower::Service;
13
14use camel_api::metrics::MetricsCollector;
15use camel_api::{
16 BoxProcessor, CamelError, Exchange, IdentityProcessor, Message, NoOpMetrics,
17 ORIGINAL_MESSAGE_EXTENSION, PipelineOutcome,
18};
19
20use camel_api::error_handler::{BoundaryKind, RetryOutcome, StepDisposition};
21use camel_processor::{
22 CircuitBreakerDecision, CircuitBreakerGate, RouteErrorHandler, invoke_processor,
23};
24use tracing::Instrument;
25
26use crate::lifecycle::adapters::body_coercing::wrap_if_needed;
27use crate::lifecycle::adapters::step_compilers::CompiledStep;
28use crate::shared::observability::adapters::TracingProcessor;
29use crate::shared::observability::domain::DetailLevel;
30
31pub(crate) use super::outcome_composition::{
34 BodyCoercingSegment, BoxProcessorSegment, StopSegment, compose_outcome_segment,
35};
36
37tokio::task_local! {
45 pub(crate) static CANCEL_TOKEN: CancellationToken;
46}
47
48#[derive(Clone)]
50pub struct PipelineRuntimeCtx {
51 pub metrics: Arc<dyn MetricsCollector>,
52 pub route_id: Arc<str>,
53}
54
55impl PipelineRuntimeCtx {
56 pub fn compile_time() -> Self {
60 Self {
61 metrics: Arc::new(NoOpMetrics),
62 route_id: Arc::from(""),
63 }
64 }
65}
66
67#[derive(Clone)]
75struct SharedSnapshot(Arc<[CompiledStep]>);
76
77#[allow(dead_code)]
83const _: () = {
84 fn assert_send<T: Send>() {}
85 fn assert_sync<T: Sync>() {}
86 fn _check() {
87 assert_send::<CompiledStep>();
88 assert_sync::<CompiledStep>();
89 }
90};
91
92pub fn compose_pipeline(processors: Vec<CompiledStep>, ctx: PipelineRuntimeCtx) -> BoxProcessor {
97 if processors.is_empty() {
98 return BoxProcessor::new(IdentityProcessor);
99 }
100 BoxProcessor::new(SequentialPipeline {
101 steps: SharedSnapshot(processors.into()),
102 handler: None,
103 ctx,
104 })
105}
106
107pub fn compose_pipeline_with_handler(
113 processors: Vec<CompiledStep>,
114 handler: Option<Arc<dyn RouteErrorHandler>>,
115 ctx: PipelineRuntimeCtx,
116) -> BoxProcessor {
117 if processors.is_empty() {
118 return BoxProcessor::new(IdentityProcessor);
119 }
120 BoxProcessor::new(SequentialPipeline {
121 steps: SharedSnapshot(processors.into()),
122 handler,
123 ctx,
124 })
125}
126
127pub fn compose_traced_pipeline(
132 processors: Vec<CompiledStep>,
133 route_id: &str,
134 trace_enabled: bool,
135 detail_level: DetailLevel,
136 metrics: Option<Arc<dyn MetricsCollector>>,
137 handler: Option<Arc<dyn RouteErrorHandler>>,
138 ctx: PipelineRuntimeCtx,
139) -> BoxProcessor {
140 if !trace_enabled {
141 return compose_pipeline_with_handler(processors, handler, ctx);
142 }
143
144 if processors.is_empty() {
145 return BoxProcessor::new(IdentityProcessor);
146 }
147
148 let wrapped: Vec<CompiledStep> = processors
149 .into_iter()
150 .enumerate()
151 .map(|(idx, step)| {
152 let (p, c, lc) = match step {
153 CompiledStep::Process {
154 processor,
155 body_contract,
156 lifecycle,
157 } => (processor, body_contract, lifecycle),
158 CompiledStep::Stop => return CompiledStep::Stop,
159 CompiledStep::Segment { .. } => return step,
160 };
161 let traced = BoxProcessor::new(TracingProcessor::new(
162 p,
163 route_id.to_string(),
164 idx,
165 detail_level.clone(),
166 metrics.clone(),
167 ));
168 CompiledStep::Process {
169 processor: traced,
170 body_contract: c,
171 lifecycle: lc,
172 }
173 })
174 .collect();
175
176 BoxProcessor::new(TracedPipeline {
177 steps: SharedSnapshot(wrapped.into()),
178 handler,
179 ctx,
180 })
181}
182
183pub fn compose_pipeline_with_contracts(
189 processors: Vec<CompiledStep>,
190 handler: Option<Arc<dyn RouteErrorHandler>>,
191 ctx: PipelineRuntimeCtx,
192) -> BoxProcessor {
193 let wrapped: Vec<CompiledStep> = processors
194 .into_iter()
195 .map(|step| match step {
196 CompiledStep::Process {
197 processor,
198 body_contract,
199 lifecycle,
200 } => {
201 let coerced = wrap_if_needed(processor, body_contract);
202 CompiledStep::Process {
203 processor: coerced,
204 body_contract: None,
205 lifecycle,
206 }
207 }
208 CompiledStep::Stop => CompiledStep::Stop,
209 CompiledStep::Segment { .. } => step,
210 })
211 .collect();
212 compose_pipeline_with_handler(wrapped, handler, ctx)
213}
214
215pub(crate) fn compose_traced_pipeline_with_contracts(
220 processors: Vec<CompiledStep>,
221 route_id: &str,
222 trace_enabled: bool,
223 detail_level: DetailLevel,
224 metrics: Option<Arc<dyn MetricsCollector>>,
225 handler: Option<Arc<dyn RouteErrorHandler>>,
226 ctx: PipelineRuntimeCtx,
227) -> BoxProcessor {
228 if !trace_enabled {
229 return compose_pipeline_with_contracts(processors, handler, ctx);
230 }
231
232 if processors.is_empty() {
233 return BoxProcessor::new(IdentityProcessor);
234 }
235
236 let wrapped: Vec<CompiledStep> = processors
237 .into_iter()
238 .enumerate()
239 .map(|(idx, step)| match step {
240 CompiledStep::Process {
241 processor,
242 body_contract,
243 lifecycle,
244 } => {
245 let coerced = wrap_if_needed(processor, body_contract);
246 let traced = BoxProcessor::new(TracingProcessor::new(
247 coerced,
248 route_id.to_string(),
249 idx,
250 detail_level.clone(),
251 metrics.clone(),
252 ));
253 CompiledStep::Process {
254 processor: traced,
255 body_contract: None,
256 lifecycle,
257 }
258 }
259 CompiledStep::Stop => CompiledStep::Stop,
260 CompiledStep::Segment { .. } => step,
261 })
262 .collect();
263
264 BoxProcessor::new(TracedPipeline {
265 steps: SharedSnapshot(wrapped.into()),
266 handler,
267 ctx,
268 })
269}
270
271#[derive(Clone)]
277struct SequentialPipeline {
278 steps: SharedSnapshot,
279 handler: Option<Arc<dyn RouteErrorHandler>>,
280 ctx: PipelineRuntimeCtx,
281}
282
283impl Service<Exchange> for SequentialPipeline {
284 type Response = Exchange;
285 type Error = CamelError;
286 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
287
288 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
289 match self.steps.0.first() {
290 Some(CompiledStep::Process { processor, .. }) => {
291 let mut proc = processor.clone();
292 match proc.poll_ready(cx) {
293 Poll::Pending => Poll::Pending,
294 Poll::Ready(Err(_)) if self.handler.is_some() => Poll::Ready(Ok(())),
295 Poll::Ready(other) => Poll::Ready(other),
296 }
297 }
298 Some(CompiledStep::Stop) => Poll::Ready(Ok(())),
299 Some(CompiledStep::Segment { .. }) => Poll::Ready(Ok(())),
300 None => Poll::Ready(Ok(())),
301 }
302 }
303
304 fn call(&mut self, exchange: Exchange) -> Self::Future {
309 let steps = self.steps.clone();
313 let handler = self.handler.clone();
314 let ctx = self.ctx.clone();
315 Box::pin(async move {
316 run_steps(steps, exchange, handler, false, &ctx)
317 .await
318 .into_tower_result()
319 })
320 }
321}
322
323#[derive(Clone)]
325struct TracedPipeline {
326 steps: SharedSnapshot,
327 handler: Option<Arc<dyn RouteErrorHandler>>,
328 ctx: PipelineRuntimeCtx,
329}
330
331impl Service<Exchange> for TracedPipeline {
332 type Response = Exchange;
333 type Error = CamelError;
334 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
335
336 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
337 match self.steps.0.first() {
338 Some(CompiledStep::Process { processor, .. }) => {
339 let mut proc = processor.clone();
340 match proc.poll_ready(cx) {
341 Poll::Pending => Poll::Pending,
342 Poll::Ready(Err(_)) if self.handler.is_some() => Poll::Ready(Ok(())),
343 Poll::Ready(other) => Poll::Ready(other),
344 }
345 }
346 Some(CompiledStep::Stop) => Poll::Ready(Ok(())),
347 Some(CompiledStep::Segment { .. }) => Poll::Ready(Ok(())),
348 None => Poll::Ready(Ok(())),
349 }
350 }
351
352 fn call(&mut self, exchange: Exchange) -> Self::Future {
355 let steps = self.steps.clone();
356 let handler = self.handler.clone();
357 let ctx = self.ctx.clone();
358 Box::pin(async move {
359 run_steps(steps, exchange, handler, true, &ctx)
360 .await
361 .into_tower_result()
362 })
363 }
364}
365
366async fn run_steps(
387 steps: SharedSnapshot,
388 exchange: Exchange,
389 handler: Option<Arc<dyn RouteErrorHandler>>,
390 trace: bool,
391 ctx: &PipelineRuntimeCtx,
392) -> PipelineOutcome {
393 use camel_api::error_handler::RetryableStep;
394 let mut ex = exchange;
395 let len = steps.0.len();
403 for i in 0..len {
404 let cancelled = CANCEL_TOKEN.try_with(|t| t.is_cancelled()).unwrap_or(false);
407 if cancelled {
408 return PipelineOutcome::Failed(CamelError::ConsumerStopping);
409 }
410 let mut retryable: OwnedRetryable = match &steps.0[i] {
415 CompiledStep::Stop => return PipelineOutcome::Stopped(ex),
416 CompiledStep::Process { processor, .. } => OwnedRetryable::Processor(processor.clone()),
417 CompiledStep::Segment { segment, .. } => OwnedRetryable::Segment(segment.clone()),
418 };
419
420 let original = handler.as_ref().map(|_| ex.clone());
421 let outcome = if trace {
422 invoke_with_span(&mut retryable, ex, i).await
423 } else {
424 retryable.invoke(ex).await
425 };
426
427 match outcome {
428 PipelineOutcome::Completed(next) => {
429 if camel_api::is_camel_stop(&next) {
430 return PipelineOutcome::Stopped(next);
431 }
432 ex = next;
433 }
434 PipelineOutcome::Stopped(stopped_ex) => {
435 return PipelineOutcome::Stopped(stopped_ex);
436 }
437 PipelineOutcome::Failed(err) => {
438 let (Some(handler), Some(original)) = (handler.as_ref(), original) else {
439 return PipelineOutcome::Failed(err);
440 };
441 let policy = handler.match_policy(&err);
442 match handler
445 .retry_step(policy, &mut retryable, original, err)
446 .await
447 {
448 RetryOutcome::Recovered(exchange) => {
449 ctx.metrics.record_counter(
450 "pipeline_disposition",
451 1.0,
452 &[("disposition", "recovered"), ("route_id", &ctx.route_id)],
453 );
454 ex = exchange;
455 }
456 RetryOutcome::Stopped(stopped_ex) => {
457 ctx.metrics.record_counter(
458 "pipeline_disposition",
459 1.0,
460 &[("disposition", "stopped"), ("route_id", &ctx.route_id)],
461 );
462 return PipelineOutcome::Stopped(stopped_ex);
463 }
464 RetryOutcome::Exhausted {
465 exchange,
466 error,
467 policy,
468 } => {
469 let disposition = if trace {
470 handler
471 .handle_step(policy, exchange, error)
472 .instrument(tracing::debug_span!("error_handler", step_index = i))
473 .await
474 } else {
475 handler.handle_step(policy, exchange, error).await
476 };
477 match disposition {
478 Ok(StepDisposition::Propagate(e)) => {
479 ctx.metrics.record_counter(
480 "pipeline_disposition",
481 1.0,
482 &[("disposition", "propagated"), ("route_id", &ctx.route_id)],
483 );
484 return PipelineOutcome::Failed(e);
485 }
486 Ok(StepDisposition::Handled(done)) => {
487 ctx.metrics.record_counter(
488 "pipeline_disposition",
489 1.0,
490 &[("disposition", "handled"), ("route_id", &ctx.route_id)],
491 );
492 return PipelineOutcome::Completed(done);
493 }
494 Ok(StepDisposition::Continued(next)) => {
495 ctx.metrics.record_counter(
496 "pipeline_disposition",
497 1.0,
498 &[("disposition", "continued"), ("route_id", &ctx.route_id)],
499 );
500 ex = next;
501 }
502 Err(e) => {
503 ctx.metrics.record_counter(
504 "pipeline_disposition",
505 1.0,
506 &[
507 ("disposition", "handler_error"),
508 ("route_id", &ctx.route_id),
509 ],
510 );
511 return PipelineOutcome::Failed(e);
512 }
513 _ => {
515 return PipelineOutcome::Failed(CamelError::ProcessorError(
516 "unknown step disposition".to_string(),
517 ));
518 }
519 }
520 }
521 _ => {
523 return PipelineOutcome::Failed(CamelError::ProcessorError(
524 "unknown retry outcome".to_string(),
525 ));
526 }
527 }
528 }
529 }
530 }
531 PipelineOutcome::Completed(ex)
532}
533
534enum OwnedRetryable {
544 Processor(camel_api::BoxProcessor),
545 Segment(camel_api::OutcomeSegment),
546}
547
548impl camel_api::error_handler::RetryableStep for OwnedRetryable {
549 fn invoke<'a>(
550 &'a mut self,
551 exchange: Exchange,
552 ) -> Pin<Box<dyn Future<Output = PipelineOutcome> + Send + 'a>> {
553 match self {
554 OwnedRetryable::Processor(p) => p.invoke(exchange),
555 OwnedRetryable::Segment(s) => s.invoke(exchange),
556 }
557 }
558}
559
560async fn invoke_with_span(
561 retryable: &mut dyn camel_api::error_handler::RetryableStep,
562 exchange: Exchange,
563 idx: usize,
564) -> PipelineOutcome {
565 retryable
566 .invoke(exchange)
567 .instrument(tracing::debug_span!("pipeline_step", index = idx))
568 .await
569}
570
571#[derive(Clone)]
578pub struct RouteChannelService {
579 handler: Arc<dyn RouteErrorHandler>,
580 security: Option<BoxProcessor>,
581 cb_gate: Option<CircuitBreakerGate>,
582 pipeline: BoxProcessor,
583 use_original_message: bool,
586}
587
588impl RouteChannelService {
589 pub fn new(
590 handler: Arc<dyn RouteErrorHandler>,
591 security: Option<BoxProcessor>,
592 cb_gate: Option<CircuitBreakerGate>,
593 pipeline: BoxProcessor,
594 use_original_message: bool,
595 ) -> Self {
596 Self {
597 handler,
598 security,
599 cb_gate,
600 pipeline,
601 use_original_message,
602 }
603 }
604}
605
606impl Service<Exchange> for RouteChannelService {
607 type Response = Exchange;
608 type Error = CamelError;
609 type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
610
611 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), CamelError>> {
612 if let Some(ref mut sec) = self.security {
614 match sec.clone().poll_ready(cx) {
615 Poll::Pending => return Poll::Pending,
616 Poll::Ready(Err(_)) | Poll::Ready(Ok(())) => {}
617 }
618 }
619 match self.pipeline.clone().poll_ready(cx) {
621 Poll::Pending => return Poll::Pending,
622 Poll::Ready(Err(_)) | Poll::Ready(Ok(())) => {}
623 }
624 Poll::Ready(Ok(()))
625 }
626
627 fn call(&mut self, exchange: Exchange) -> Self::Future {
628 let handler = self.handler.clone();
629 let security = self.security.clone();
630 let cb_gate = self.cb_gate.clone();
631 let mut pipeline = self.pipeline.clone();
632 let use_original_message = self.use_original_message;
633
634 Box::pin(async move {
635 let mut ex = exchange;
636
637 if use_original_message {
641 let original: Arc<Message> = Arc::new(ex.input.clone());
642 ex.set_extension(ORIGINAL_MESSAGE_EXTENSION, original);
643 }
644
645 if let Some(mut sec) = security {
647 let original = ex.clone();
648 match invoke_processor(&mut sec, ex).await {
649 Ok(next) => ex = next,
650 Err(err) => {
651 return handler
652 .handle_boundary(BoundaryKind::Security, original, err)
653 .await;
654 }
655 }
656 }
657
658 if let Some(ref cb) = cb_gate {
660 match cb.before_call() {
661 CircuitBreakerDecision::Allow => { }
662 CircuitBreakerDecision::Fallback(mut fb) => {
663 let original = ex.clone();
666 match invoke_processor(&mut fb, ex).await {
667 Ok(result) => return Ok(result),
668 Err(err) => {
669 return handler
670 .handle_boundary(BoundaryKind::CircuitBreaker, original, err)
671 .await;
672 }
673 }
674 }
675 CircuitBreakerDecision::Reject(err) => {
676 let original = ex.clone();
677 return handler
678 .handle_boundary(BoundaryKind::CircuitBreaker, original, err)
679 .await;
680 }
681 }
682 }
683
684 let result = invoke_processor(&mut pipeline, ex).await;
686
687 if let Some(ref cb) = cb_gate {
689 cb.after_result(&result);
690 }
691
692 result
694 })
695 }
696}
697
698#[cfg(test)]
699#[path = "route_compiler_tests.rs"]
700mod tests;