use crate::cap::{Auth, Cap};
use crate::error::{BotError, DispatchCertainty};
use crate::verb;
pub struct Pipeline {
steps: Vec<Box<dyn PipelineStep>>,
caps: Vec<Cap>,
}
pub trait PipelineStep {
fn required_caps(&self) -> &[Cap];
fn run_any<'a>(
&'a self,
call: (Auth, &'a dyn std::any::Any),
) -> crate::BoxFuture<'a, Result<Box<dyn std::any::Any>, BotError>>;
}
impl<A> PipelineStep for A
where
A: verb::Execute + 'static,
A::Input: 'static,
A::Output: 'static,
{
fn required_caps(&self) -> &[Cap] {
verb::Execute::required_caps(self)
}
fn run_any<'a>(
&'a self,
call: (Auth, &'a dyn std::any::Any),
) -> crate::BoxFuture<'a, Result<Box<dyn std::any::Any>, BotError>> {
Box::pin(async move {
let (auth, input) = call;
match input.downcast_ref::<A::Input>() {
Some(typed) => {
let value = self.execute_action((auth, typed)).await?;
let boxed: Box<dyn std::any::Any> = Box::new(value);
Ok(boxed)
}
None => Err(BotError::DomainError {
domain: verb::Execute::domain_id(self).into(),
certainty: DispatchCertainty::Refused,
cause: "type mismatch in pipeline step input".into(),
}),
}
})
}
}
impl Pipeline {
#[must_use]
pub fn new() -> Self {
Self {
steps: Vec::new(),
caps: Vec::new(),
}
}
#[must_use]
pub fn step(mut self, step: impl PipelineStep + 'static) -> Self {
self.caps.extend(step.required_caps().iter().cloned());
self.steps.push(Box::new(step));
self
}
}
impl Default for Pipeline {
fn default() -> Self {
Self::new()
}
}
struct StepCount(usize);
impl core::fmt::Debug for StepCount {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} step(s)", self.0)
}
}
impl core::fmt::Debug for Pipeline {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Pipeline")
.field("steps", &StepCount(self.steps.len()))
.field("caps", &self.caps)
.finish()
}
}
#[non_exhaustive]
pub struct PipelineOutput(pub Box<dyn std::any::Any>);
impl core::fmt::Debug for PipelineOutput {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("PipelineOutput(..)")
}
}
impl verb::Execute for Pipeline {
type Input = ();
type Output = PipelineOutput;
fn required_caps(&self) -> &[Cap] {
&self.caps
}
async fn execute_action(&self, call: (Auth, &())) -> Result<PipelineOutput, BotError> {
let (auth, _) = call;
auth.check(verb::Execute::required_caps(self))?;
let mut current: Box<dyn std::any::Any> = Box::new(());
for step in &self.steps {
let next = step.run_any((auth.clone(), current.as_ref())).await?;
current = next;
}
Ok(PipelineOutput(current))
}
fn domain_id(&self) -> &str {
"flow::pipeline"
}
}