use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tower::{Layer, Service};
use crate::error::{BoxError, CamelError};
use crate::exchange::Exchange;
use crate::outcome_segment::OutcomeSegment;
use crate::pipeline_outcome::PipelineOutcome;
use crate::processor::BoxProcessor;
pub trait ProcessorLayer: Send + Sync + 'static {
fn wrap(&self, inner: BoxProcessor) -> Result<BoxProcessor, CamelError>;
}
#[derive(Clone)]
pub struct CamelErrorAdapter<S> {
inner: S,
}
impl<S> CamelErrorAdapter<S> {
pub fn new(inner: S) -> Self {
Self { inner }
}
}
impl<S> Service<Exchange> for CamelErrorAdapter<S>
where
S: Service<Exchange, Response = Exchange> + Clone + Send + Sync + 'static,
S::Error: Into<BoxError>,
S::Future: Send,
{
type Response = Exchange;
type Error = CamelError;
type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner
.poll_ready(cx)
.map(|r| r.map_err(|e| CamelError::from(e.into())))
}
fn call(&mut self, exchange: Exchange) -> Self::Future {
let fut = self.inner.call(exchange);
Box::pin(async move { fut.await.map_err(|e| CamelError::from(e.into())) })
}
}
impl<L> ProcessorLayer for L
where
L: Layer<BoxProcessor> + Send + Sync + 'static,
L::Service: Service<Exchange, Response = Exchange> + Clone + Send + Sync + 'static,
<L::Service as Service<Exchange>>::Error: Into<BoxError>,
<L::Service as Service<Exchange>>::Future: Send,
{
fn wrap(&self, inner: BoxProcessor) -> Result<BoxProcessor, CamelError> {
Ok(BoxProcessor::new(CamelErrorAdapter::new(self.layer(inner))))
}
}
#[derive(Clone)]
pub struct LayerFactory(Arc<dyn ProcessorLayer>);
impl LayerFactory {
pub fn new(layer: impl ProcessorLayer + 'static) -> Self {
Self(Arc::new(layer))
}
pub fn wrap(&self, inner: BoxProcessor) -> Result<BoxProcessor, CamelError> {
self.0.wrap(inner)
}
}
impl std::fmt::Debug for LayerFactory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("LayerFactory(...)")
}
}
impl PartialEq for LayerFactory {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
#[derive(Clone)]
pub struct SegmentProcessor {
inner: OutcomeSegment,
}
impl SegmentProcessor {
pub fn new(inner: OutcomeSegment) -> Self {
Self { inner }
}
}
impl Service<Exchange> for SegmentProcessor {
type Response = Exchange;
type Error = CamelError;
type Future = Pin<Box<dyn Future<Output = Result<Exchange, CamelError>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, exchange: Exchange) -> Self::Future {
let mut segment = self.inner.clone();
Box::pin(async move {
match segment.run(exchange).await {
PipelineOutcome::Stopped(mut ex) => {
ex.set_property(crate::exchange::CAMEL_STOP, serde_json::Value::Bool(true));
Ok(ex)
}
outcome => outcome.into_tower_result(),
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, Instant};
use tokio::time::sleep;
use tower::ServiceExt;
use tower::timeout::TimeoutLayer;
use crate::message::Message;
use crate::outcome_pipeline::OutcomePipeline;
use crate::outcome_segment::OutcomeSegment;
use crate::pipeline_outcome::PipelineOutcome;
use crate::processor::ProcessorFn;
#[derive(Clone, Copy)]
enum StubOutcome {
Completed,
Stopped,
Failed,
}
#[derive(Clone)]
struct StubPipeline {
outcome: StubOutcome,
}
impl StubPipeline {
fn new(outcome: StubOutcome) -> Self {
Self { outcome }
}
}
impl OutcomePipeline for StubPipeline {
fn clone_box(&self) -> Box<dyn OutcomePipeline> {
Box::new(self.clone())
}
fn run<'a>(
&'a mut self,
exchange: Exchange,
) -> Pin<Box<dyn Future<Output = PipelineOutcome> + Send + 'a>> {
let outcome = self.outcome;
Box::pin(async move {
match outcome {
StubOutcome::Completed => PipelineOutcome::Completed(exchange),
StubOutcome::Stopped => PipelineOutcome::Stopped(exchange),
StubOutcome::Failed => {
PipelineOutcome::Failed(CamelError::ProcessorError("x".to_string()))
}
}
})
}
}
fn sleeping_processor(delay: Duration) -> BoxProcessor {
BoxProcessor::new(ProcessorFn::new(move |ex: Exchange| async move {
sleep(delay).await;
Ok(ex)
}))
}
fn identity_processor() -> BoxProcessor {
BoxProcessor::new(ProcessorFn::new(|ex: Exchange| async move { Ok(ex) }))
}
fn source_chain_reaches_elapsed(err: &CamelError) -> bool {
let mut current = std::error::Error::source(err);
for _ in 0..3 {
let Some(node) = current else { return false };
if node
.downcast_ref::<tower::timeout::error::Elapsed>()
.is_some()
{
return true;
}
current = std::error::Error::source(node);
}
false
}
#[tokio::test]
async fn layer_factory_wraps_timeout_layer() {
let factory = LayerFactory::new(TimeoutLayer::new(Duration::from_millis(50)));
let wrapped = factory
.wrap(sleeping_processor(Duration::from_secs(5)))
.expect("timeout layer must satisfy the ProcessorLayer bounds");
let started = Instant::now();
let result = tokio::time::timeout(
Duration::from_secs(1),
wrapped.oneshot(Exchange::new(Message::new("x"))),
)
.await
.expect("the 50ms timeout must return well inside the 1s guard");
let err = result.expect_err("the sleeping producer must be timed out");
assert!(
source_chain_reaches_elapsed(&err),
"source chain must reach tower::timeout::error::Elapsed: {err:?}"
);
assert!(
err.to_string().contains("middleware timeout elapsed"),
"unexpected error classification: {err}"
);
assert!(
started.elapsed() < Duration::from_secs(1),
"timeout fired too late: {:?}",
started.elapsed()
);
}
#[tokio::test]
async fn layer_factory_is_clone_and_debug_redacted() {
let factory = LayerFactory::new(TimeoutLayer::new(Duration::from_millis(50)));
assert_eq!(format!("{factory:?}"), "LayerFactory(...)");
let clone = factory.clone();
let wrapped = clone
.wrap(identity_processor())
.expect("the clone must wrap the same layer");
wrapped
.oneshot(Exchange::new(Message::new("clone")))
.await
.expect("a fast call passes through the cloned factory");
}
#[test]
fn layer_factory_partial_eq_ptr_identity() {
let original = LayerFactory::new(TimeoutLayer::new(Duration::from_millis(50)));
let cloned = original.clone();
let different = LayerFactory::new(TimeoutLayer::new(Duration::from_millis(50)));
assert_eq!(original, cloned, "a clone shares the same Arc");
assert_ne!(
original, different,
"distinct layer values are distinct factories"
);
}
#[tokio::test]
async fn segment_processor_remarks_stopped_as_camel_stop_true() {
let mut exchange = Exchange::new(Message::new("stopped"));
exchange.set_property(crate::CAMEL_STOP, serde_json::Value::Bool(false));
let processor = SegmentProcessor::new(OutcomeSegment::new(Box::new(StubPipeline::new(
StubOutcome::Stopped,
))));
let response = processor
.oneshot(exchange)
.await
.expect("a stopped segment outcome is Ok at the Tower boundary");
assert!(
crate::is_camel_stop(&response),
"SegmentProcessor must force CamelStop=true on the Stopped arm"
);
}
#[tokio::test]
async fn segment_processor_passes_completed_and_failed() {
let completed = SegmentProcessor::new(OutcomeSegment::new(Box::new(StubPipeline::new(
StubOutcome::Completed,
))));
let response = completed
.oneshot(Exchange::new(Message::new("done")))
.await
.expect("Completed maps to Ok");
assert_eq!(response.input.body.as_text(), Some("done"));
let failed = SegmentProcessor::new(OutcomeSegment::new(Box::new(StubPipeline::new(
StubOutcome::Failed,
))));
let err = failed
.oneshot(Exchange::new(Message::new("boom")))
.await
.expect_err("Failed maps to Err");
assert!(
matches!(err, CamelError::ProcessorError(ref msg) if msg == "x"),
"unexpected error: {err:?}"
);
}
}