Skip to main content

WorkflowHandler

Trait WorkflowHandler 

Source
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§

Source

fn name(&self) -> &str

The workflow name used for registration and lookup.

Source

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§

Source

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.

Source

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"]);
Source

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 dataetl. The default is None (uncategorized).

Validation (empty segments, leading or trailing /, //, whitespace segments) is enforced at registration time by Engine::register.

Source

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).

Source

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.

Source

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(()) })
    }
}
Source

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)));
Source

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_version is None (old run predating version tracking): always compatible.
  • run_version equals version: compatible.
  • run_version appears in compatible_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")));
Source

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.

Source

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".

Implementors§