pub trait WorkflowHandler: Send + Sync {
// Required methods
fn name(&self) -> &str;
fn execute<'a>(&'a self, ctx: &'a mut WorkflowContext) -> HandlerFuture<'a>;
// Provided methods
fn version(&self) -> Option<&str> { ... }
fn compatible_versions(&self) -> &[&str] { ... }
fn category(&self) -> Option<&str> { ... }
fn input_schema(&self) -> Option<Value> { ... }
fn default_labels(&self) -> HashMap<String, String> { ... }
fn schedule(&self) -> Option<&CronSchedule> { ... }
fn default_max_cost_usd(&self) -> Option<Decimal> { ... }
fn is_version_compatible(&self, run_version: Option<&str>) -> bool { ... }
fn describe(&self) -> WorkflowInfo { ... }
fn create_run<'a>(
&self,
creator: &'a dyn RunCreator,
opts: CreateRunOpts,
) -> RunCreatorFuture<'a> { ... }
}Expand description
A dynamic workflow handler with context-aware step chaining.
Implement this trait to define workflows where each step can use
the output of previous steps. Register handlers with
Engine::register and execute
them by name.
§Why Pin<Box<dyn Future>> instead of async fn?
The handler must be object-safe (dyn WorkflowHandler) to allow
registering different handler types in the engine’s registry.
Required Methods§
Sourcefn execute<'a>(&'a self, ctx: &'a mut WorkflowContext) -> HandlerFuture<'a>
fn execute<'a>(&'a self, ctx: &'a mut WorkflowContext) -> HandlerFuture<'a>
Execute the workflow with the given context.
The context provides shell,
http, and agent
methods that automatically persist each step.
§Errors
Return EngineError if any step fails. The engine will mark
the run as Failed and record the error.
Provided Methods§
Sourcefn version(&self) -> Option<&str>
fn version(&self) -> Option<&str>
Handler version string, used to trace which code version produced a run.
Override this to return a meaningful version (semver, git SHA, build
hash, etc.). The default is "1".
The engine records this value on every run it creates so that retries can detect when the handler has changed since the original execution.
Sourcefn compatible_versions(&self) -> &[&str]
fn compatible_versions(&self) -> &[&str]
Versions of this handler that can replay payloads produced by an
older run without requiring force.
When a retry targets a run whose handler_version differs from
version, the engine checks this list. If the
run’s version appears here, the retry proceeds normally; otherwise
it is refused with 409 HANDLER_VERSION_MISMATCH unless the caller
passes force=true.
The default is an empty slice (only the current version is accepted).
§Examples
struct MigratedHandler;
impl WorkflowHandler for MigratedHandler {
fn name(&self) -> &str { "migrated" }
fn version(&self) -> Option<&str> { Some("2.0.0") }
fn compatible_versions(&self) -> &[&str] { &["1.0.0", "1.5.0"] }
fn execute<'a>(&'a self, _ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
Box::pin(async move { Ok(()) })
}
}
assert_eq!(MigratedHandler.compatible_versions(), &["1.0.0", "1.5.0"]);Sourcefn category(&self) -> Option<&str>
fn category(&self) -> Option<&str>
Optional /-separated category path used to group workflows in the UI tree.
Return a value like "data/etl" to place the workflow under data → etl.
The default is None (uncategorized).
Validation (empty segments, leading or trailing /, //, whitespace
segments) is enforced at registration time by
Engine::register.
Sourcefn input_schema(&self) -> Option<Value>
fn input_schema(&self) -> Option<Value>
Return a JSON Schema describing the expected input payload.
When present, the dashboard renders a dynamic form from this schema
and the engine validates the payload before creating a run.
The default is None (no schema, free-form payload).
Sourcefn default_labels(&self) -> HashMap<String, String>
fn default_labels(&self) -> HashMap<String, String>
Labels automatically applied to every run of this workflow.
These are merged with any labels provided at run creation time. User-provided labels take precedence over defaults.
Sourcefn schedule(&self) -> Option<&CronSchedule>
fn schedule(&self) -> Option<&CronSchedule>
Optional cron schedule for automatic execution.
Return a CronSchedule built from a cron expression
(5 or 6 fields, as supported by croner).
When set, the engine exposes this handler via
Engine::scheduled_handlers
so the runtime can wire it into a cron scheduler automatically.
The default is None (no automatic scheduling).
§Examples
struct HourlySync;
impl WorkflowHandler for HourlySync {
fn name(&self) -> &str { "hourly-sync" }
fn schedule(&self) -> Option<&CronSchedule> {
// In practice, store as a field or use `std::sync::LazyLock`.
None
}
fn execute<'a>(&'a self, _ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
Box::pin(async move { Ok(()) })
}
}Sourcefn default_max_cost_usd(&self) -> Option<Decimal>
fn default_max_cost_usd(&self) -> Option<Decimal>
Default cumulative cost cap for runs of this workflow, in USD.
Applied when the run creation request does not supply one. Takes
precedence over the server-wide
IRONFLOW_DEFAULT_RUN_MAX_COST_USD.
The default is None (fall back to the server default, or no cap).
§Examples
use rust_decimal::Decimal;
struct ExpensiveAnalysis;
impl WorkflowHandler for ExpensiveAnalysis {
fn name(&self) -> &str { "expensive-analysis" }
fn default_max_cost_usd(&self) -> Option<Decimal> {
Some(Decimal::new(500, 2)) // $5.00
}
fn execute<'a>(&'a self, _ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
Box::pin(async move { Ok(()) })
}
}
assert_eq!(ExpensiveAnalysis.default_max_cost_usd(), Some(Decimal::new(500, 2)));Sourcefn is_version_compatible(&self, run_version: Option<&str>) -> bool
fn is_version_compatible(&self, run_version: Option<&str>) -> bool
Check whether a run carrying run_version can be replayed by this
handler without force.
Compatibility rules:
run_versionisNone(old run predating version tracking): always compatible.run_versionequalsversion: compatible.run_versionappears incompatible_versions: compatible.- Otherwise: incompatible.
§Examples
struct MyHandler;
impl WorkflowHandler for MyHandler {
fn name(&self) -> &str { "my-handler" }
fn version(&self) -> Option<&str> { Some("2.0.0") }
fn compatible_versions(&self) -> &[&str] { &["1.0.0"] }
fn execute<'a>(&'a self, _ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
Box::pin(async move { Ok(()) })
}
}
assert!(MyHandler.is_version_compatible(None));
assert!(MyHandler.is_version_compatible(Some("2.0.0")));
assert!(MyHandler.is_version_compatible(Some("1.0.0")));
assert!(!MyHandler.is_version_compatible(Some("0.5.0")));Sourcefn describe(&self) -> WorkflowInfo
fn describe(&self) -> WorkflowInfo
Return metadata about this workflow (description, source code).
Override this to provide a description and source code for the
dashboard UI. The default returns an empty description with no source
but propagates WorkflowHandler::category,
WorkflowHandler::version, WorkflowHandler::input_schema,
WorkflowHandler::default_labels,
WorkflowHandler::compatible_versions,
and WorkflowHandler::schedule.
Sourcefn create_run<'a>(
&self,
creator: &'a dyn RunCreator,
opts: CreateRunOpts,
) -> RunCreatorFuture<'a>
fn create_run<'a>( &self, creator: &'a dyn RunCreator, opts: CreateRunOpts, ) -> RunCreatorFuture<'a>
Create a run for this workflow, using handler metadata automatically.
Assembles a NewRun from name,
version, and default_max_cost_usd,
then delegates to the given RunCreator.
§Errors
Returns EngineError if the underlying store rejects the run.
§Examples
struct DeployWorkflow;
impl WorkflowHandler for DeployWorkflow {
fn name(&self) -> &str { "deploy" }
fn execute<'a>(&'a self, _ctx: &'a mut WorkflowContext) -> HandlerFuture<'a> {
Box::pin(async move { Ok(()) })
}
}
let opts = CreateRunOpts::new().trigger(TriggerKind::Api);
let run = DeployWorkflow.create_run(store, opts).await?.into_run();
assert_eq!(run.workflow_name, "deploy");Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".