ironflow_engine/handler.rs
1//! [`WorkflowHandler`] trait — dynamic workflows with context chaining.
2//!
3//! Implement this trait to define workflows where steps can reference
4//! outputs from previous steps. The handler receives a [`WorkflowContext`]
5//! that provides step execution methods with automatic persistence.
6//!
7//! # Examples
8//!
9//! ```no_run
10//! use ironflow_engine::handler::WorkflowHandler;
11//! use ironflow_engine::context::WorkflowContext;
12//! use ironflow_engine::config::{ShellConfig, AgentStepConfig};
13//! use ironflow_engine::error::EngineError;
14//! use std::future::Future;
15//! use std::pin::Pin;
16//!
17//! struct DeployWorkflow;
18//!
19//! impl WorkflowHandler for DeployWorkflow {
20//! fn name(&self) -> &str {
21//! "deploy"
22//! }
23//!
24//! fn execute<'a>(
25//! &'a self,
26//! ctx: &'a mut WorkflowContext,
27//! ) -> Pin<Box<dyn Future<Output = Result<(), EngineError>> + Send + 'a>> {
28//! Box::pin(async move {
29//! let build = ctx.shell("build", ShellConfig::new("cargo build --release")).await?;
30//! let tests = ctx.shell("test", ShellConfig::new("cargo test")).await?;
31//!
32//! let review = ctx.agent("review", AgentStepConfig::new(
33//! &format!("Build:\n{}\nTests:\n{}\nReview.",
34//! build.output["stdout"], tests.output["stdout"])
35//! )).await?;
36//!
37//! if review.output["value"].as_str().unwrap_or("").contains("LGTM") {
38//! ctx.shell("deploy", ShellConfig::new("./deploy.sh")).await?;
39//! }
40//!
41//! Ok(())
42//! })
43//! }
44//! }
45//! ```
46
47use std::future::Future;
48use std::pin::Pin;
49
50use serde::Serialize;
51
52use crate::context::WorkflowContext;
53use crate::error::EngineError;
54
55/// Boxed future returned by [`WorkflowHandler::execute`].
56pub type HandlerFuture<'a> = Pin<Box<dyn Future<Output = Result<(), EngineError>> + Send + 'a>>;
57
58/// Metadata about a workflow, returned by [`WorkflowHandler::describe`].
59///
60/// Contains a human-readable description and optional Rust source code
61/// for display in the dashboard.
62#[derive(Debug, Clone, Serialize)]
63pub struct WorkflowInfo {
64 /// Human-readable description of what the workflow does.
65 pub description: String,
66 /// Optional Rust source code of the handler (for UI display).
67 pub source_code: Option<String>,
68 /// Names of sub-workflows invoked by this handler.
69 #[serde(default, skip_serializing_if = "Vec::is_empty")]
70 pub sub_workflows: Vec<String>,
71}
72
73/// A dynamic workflow handler with context-aware step chaining.
74///
75/// Implement this trait to define workflows where each step can use
76/// the output of previous steps. Register handlers with
77/// [`Engine::register`](crate::engine::Engine::register) and execute
78/// them by name.
79///
80/// # Why `Pin<Box<dyn Future>>` instead of `async fn`?
81///
82/// The handler must be object-safe (`dyn WorkflowHandler`) to allow
83/// registering different handler types in the engine's registry.
84pub trait WorkflowHandler: Send + Sync {
85 /// The workflow name used for registration and lookup.
86 fn name(&self) -> &str;
87
88 /// Return metadata about this workflow (description, source code).
89 ///
90 /// Override this to provide a description and source code for the
91 /// dashboard UI. The default returns an empty description with no source.
92 fn describe(&self) -> WorkflowInfo {
93 WorkflowInfo {
94 description: String::new(),
95 source_code: None,
96 sub_workflows: Vec::new(),
97 }
98 }
99
100 /// Execute the workflow with the given context.
101 ///
102 /// The context provides [`shell`](WorkflowContext::shell),
103 /// [`http`](WorkflowContext::http), and [`agent`](WorkflowContext::agent)
104 /// methods that automatically persist each step.
105 ///
106 /// # Errors
107 ///
108 /// Return [`EngineError`] if any step fails. The engine will mark
109 /// the run as `Failed` and record the error.
110 fn execute<'a>(&'a self, ctx: &'a mut WorkflowContext) -> HandlerFuture<'a>;
111}