use std::future::Future;
use async_trait::async_trait;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ControlFlow {
Continue,
Break,
}
#[async_trait]
pub trait Command<S>: Send {
type Output: Send + Sync + 'static;
type Error: Send + Sync + 'static;
async fn execute(self, services: S) -> Result<Self::Output, Self::Error>;
}
pub trait OnComplete<T>: Send {
fn run(self, value: T);
}
pub struct ExecutionBuilder<S> {
tasks: Vec<std::pin::Pin<Box<dyn Future<Output = ControlFlow> + Send>>>,
services: S,
rt: tokio::runtime::Handle,
}
impl<S> ExecutionBuilder<S>
where
S: Clone + Send + Sync + 'static,
{
pub fn new<C, F>(rt: tokio::runtime::Handle, services: S, cmd: C, on_complete: F) -> Self
where
C: Command<S> + 'static,
F: OnComplete<Result<C::Output, C::Error>> + Send + 'static,
{
let services_clone = services.clone();
let fut = async move {
let result = cmd.execute(services_clone).await;
let flow = if result.is_ok() {
ControlFlow::Continue
} else {
ControlFlow::Break
};
on_complete.run(result);
flow
};
Self {
tasks: vec![Box::pin(fut)],
services,
rt,
}
}
#[must_use = "the builder must be consumed with `.go()` to spawn execution"]
pub fn then<C, F>(mut self, cmd: C, on_complete: F) -> Self
where
C: Command<S> + 'static,
F: OnComplete<Result<C::Output, C::Error>> + Send + 'static,
{
let services = self.services.clone();
let fut = async move {
let result = cmd.execute(services).await;
let flow = if result.is_ok() {
ControlFlow::Continue
} else {
ControlFlow::Break
};
on_complete.run(result);
flow
};
self.tasks.push(Box::pin(fut));
self
}
pub fn go(self) -> tokio::task::JoinHandle<ControlFlow> {
self.rt.spawn(async move {
for task in self.tasks {
if task.await == ControlFlow::Break {
return ControlFlow::Break;
}
}
ControlFlow::Continue
})
}
}