Skip to main content

apalis_workflow/sequential/and_then/
mod.rs

1use std::{marker::PhantomData, task::Context};
2
3use apalis_core::{
4    backend::{Backend, BackendConfig, WireFormatBackend, codec::Codec},
5    error::BoxDynError,
6    task::task_fn::{TaskFn, task_fn},
7    task::{Task, task_id::GenerateId},
8};
9use futures_util::{
10    FutureExt, Sink,
11    future::{BoxFuture, ready},
12};
13use serde::Serialize;
14use tower::{Service, ServiceBuilder, layer::layer_fn};
15
16use crate::{
17    SteppedService,
18    sequential::context::StepContext,
19    sequential::router::{GoTo, StepResponse, WorkflowRouter},
20    sequential::service::handle_step_result,
21    sequential::step::{Layer, Stack, Step},
22    sequential::workflow::SteppedFlow,
23};
24
25/// A layer that represents an `and_then` step in the workflow.
26#[derive(Clone, Debug)]
27pub struct AndThen<F> {
28    then_fn: F,
29}
30
31impl<F> AndThen<F> {
32    /// Creates a new `AndThen` layer with the provided function.
33    pub fn new(then_fn: F) -> Self {
34        Self { then_fn }
35    }
36}
37
38/// The step implementation for the `AndThen` layer.
39#[derive(Clone, Debug)]
40pub struct AndThenStep<F, S> {
41    then_fn: F,
42    step: S,
43}
44
45impl<S, F> Layer<S> for AndThen<F>
46where
47    F: Clone,
48{
49    type Step = AndThenStep<F, S>;
50
51    fn layer(&self, step: S) -> Self::Step {
52        AndThenStep {
53            then_fn: self.then_fn.clone(),
54            step,
55        }
56    }
57}
58
59impl<F, Input, S, B, CodecError, Err> Step<Input, B> for AndThenStep<F, S>
60where
61    B: Backend<Error = Err>
62        + WireFormatBackend
63        + BackendConfig
64        + Sink<Task<B::Compact>, Error = Err>
65        + Send
66        + Sync
67        + Unpin
68        + Clone
69        + 'static,
70    F: Service<Task<Input>, Error = BoxDynError> + Send + Sync + 'static + Clone,
71    S: Step<F::Response, B>,
72    Input: Send + Sync + 'static,
73    F::Future: Send + 'static,
74    F::Error: Into<BoxDynError> + Send + 'static,
75    B::Codec: Codec<F::Response, Error = CodecError, Compact = B::Compact>
76        + Codec<Input, Error = CodecError, Compact = B::Compact>
77        + Codec<S::Response, Error = CodecError, Compact = B::Compact>
78        + Send
79        + Sync
80        + Clone
81        + 'static,
82    CodecError: std::error::Error + Send + Sync + 'static,
83    B::Id: GenerateId + Send + Sync + 'static,
84    S::Response: Send + 'static,
85    B::Compact: Send + 'static,
86    F::Response: Send + Serialize + 'static,
87    Err: std::error::Error + Send + Sync + 'static,
88{
89    type Response = F::Response;
90    type Error = F::Error;
91    fn register(&mut self, ctx: &mut WorkflowRouter<B>) -> Result<(), BoxDynError> {
92        let svc = ServiceBuilder::new()
93            .layer(layer_fn(|s| AndThenService {
94                service: s,
95                _marker: PhantomData::<fn(B, Input) -> ()>,
96            }))
97            .map_response(|res: F::Response| GoTo::Next(res))
98            .service(self.then_fn.clone());
99        let svc = SteppedService::<B::Compact>::new(svc);
100        let count = ctx.steps.len();
101        ctx.steps.insert(count, svc);
102        self.step.register(ctx)
103    }
104}
105
106/// The service implementation for the `AndThen` step.
107#[derive(Debug)]
108pub struct AndThenService<Svc, Backend, Cur> {
109    service: Svc,
110    _marker: PhantomData<fn(Backend, Cur) -> ()>,
111}
112
113impl<Svc: Clone, Backend, Cur> Clone for AndThenService<Svc, Backend, Cur> {
114    fn clone(&self) -> Self {
115        Self {
116            service: self.service.clone(),
117            _marker: PhantomData,
118        }
119    }
120}
121
122impl<Svc, Backend, Cur> AndThenService<Svc, Backend, Cur> {
123    /// Creates a new `AndThenService` with the provided service.
124    pub fn new(service: Svc) -> Self {
125        Self {
126            service,
127            _marker: PhantomData,
128        }
129    }
130}
131
132impl<S, B, Cur, Res, CodecErr, Err> Service<Task<B::Compact>> for AndThenService<S, B, Cur>
133where
134    S: Service<Task<Cur>, Response = GoTo<Res>>,
135    S::Future: Send + 'static,
136    B: Backend<Error = Err>
137        + WireFormatBackend
138        + Sink<Task<B::Compact>, Error = Err>
139        + BackendConfig
140        + Clone
141        + Send
142        + Unpin
143        + Sync
144        + 'static,
145    B::Codec: Codec<Cur, Compact = B::Compact, Error = CodecErr>
146        + Codec<Res, Compact = B::Compact, Error = CodecErr>
147        + Send
148        + Clone
149        + Sync,
150    S::Error: Into<BoxDynError> + Send + 'static,
151    CodecErr: Into<BoxDynError> + Send + 'static,
152    Cur: Send + 'static,
153    B::Id: GenerateId + Send + Sync + 'static,
154    Res: Send + Serialize + 'static,
155    B::Compact: Send + 'static,
156    Err: std::error::Error + Send + Sync + 'static,
157{
158    type Response = GoTo<StepResponse>;
159    type Error = BoxDynError;
160    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
161
162    fn poll_ready(&mut self, cx: &mut Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
163        self.service.poll_ready(cx).map_err(|e| e.into())
164    }
165
166    fn call(&mut self, request: Task<B::Compact>) -> Self::Future {
167        let mut ctx = request.data().get::<StepContext<B>>().cloned().unwrap();
168        let codec = ctx.backend.codec();
169        let compacted = request.try_map_args(|t| B::Codec::decode(codec, &t));
170        match compacted {
171            Ok(task) => {
172                let fut = self.service.call(task);
173                async move {
174                    let res = fut.await.map_err(|e| e.into())?;
175                    Ok(handle_step_result(&mut ctx, res).await?)
176                }
177                .boxed()
178            }
179            Err(e) => ready(Err(e.into())).boxed(),
180        }
181    }
182}
183
184impl<Start, Cur, B, L> SteppedFlow<Start, Cur, B, L>
185where
186    B: Backend,
187{
188    /// Adds a transformation step to the workflow that processes the output of the previous step.
189    ///
190    /// The `and_then` method allows you to chain operations by providing a function that
191    /// takes the result of the current workflow step and transforms it into the input
192    /// for the next step. This enables building complex processing pipelines with
193    /// type-safe transformations between steps.
194    /// # Example
195    /// ```rust,ignore
196    /// workflow
197    ///     .and_then(extract)
198    ///     .and_then(transform)
199    ///     .and_then(load);
200    /// ```
201    #[allow(clippy::type_complexity)]
202    pub fn and_then<F, O, FnArgs>(
203        self,
204        and_then: F,
205    ) -> SteppedFlow<Start, O, B, Stack<AndThen<TaskFn<F, Cur, FnArgs>>, L>>
206    where
207        TaskFn<F, Cur, FnArgs>: Service<Task<Cur>, Response = O>,
208    {
209        self.add_step(AndThen {
210            then_fn: task_fn(and_then),
211        })
212    }
213}