use crate::types::StepProgress;
use rskit_errors::{AppError, AppResult};
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
pub type ChainProgressFn = Arc<dyn Fn(StepProgress) + Send + Sync>;
pub(crate) type CleanupAction =
Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = AppResult<()>> + Send + 'static>> + Send>;
pub(crate) struct ChainState<O> {
pub(crate) output: O,
pub(crate) cleanups: Vec<CleanupAction>,
}
#[derive(Clone)]
pub(crate) struct ChainContext {
pub(crate) progress: Option<ChainProgressFn>,
pub(crate) cancel: CancellationToken,
}
pub(crate) type ChainRunner<I, O> = Arc<
dyn Fn(I, ChainContext) -> Pin<Box<dyn Future<Output = AppResult<ChainState<O>>> + Send>>
+ Send
+ Sync,
>;
pub(crate) async fn run_cleanups(mut cleanups: Vec<CleanupAction>) -> AppResult<()> {
let mut cleanup_error: Option<AppError> = None;
while let Some(cleanup) = cleanups.pop() {
if let Err(error) = cleanup().await {
cleanup_error = Some(match cleanup_error {
Some(existing) => {
existing.context(format!("additional chain cleanup failed: {error}"))
}
None => error.context("chain cleanup failed"),
});
}
}
cleanup_error.map_or(Ok(()), Err)
}
pub struct Chain<I, O> {
step_count: usize,
runner: ChainRunner<I, O>,
_types: PhantomData<fn(I) -> O>,
}
impl<I, O> Chain<I, O>
where
I: Send + 'static,
O: Send + 'static,
{
pub(crate) fn new(step_count: usize, runner: ChainRunner<I, O>) -> Self {
Self {
step_count,
runner,
_types: PhantomData,
}
}
pub async fn execute(
&self,
input: I,
progress: Option<ChainProgressFn>,
cancel: CancellationToken,
) -> AppResult<O> {
let state = (self.runner)(input, ChainContext { progress, cancel }).await?;
Ok(state.output)
}
#[must_use]
pub fn len(&self) -> usize {
self.step_count
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.step_count == 0
}
}