Skip to main content

wfe_core/
lib.rs

1#![warn(missing_docs)]
2//! wfe-core — Core traits, models, builder, executor, and primitives for the WFE
3//! persistent workflow engine.
4//!
5//! # What is WFE?
6//!
7//! WFE (Workflow Engine) is a trait-based, pluggable workflow engine for Rust.
8//! It is designed for long-running, persistent workflows that survive process
9//! restarts. You define workflows as code using a fluent builder API, and the
10//! executor drives them to completion with support for parallel branches,
11//! conditional logic, loops, saga compensation, and event-driven pausing.
12//!
13//! # Core concepts
14//!
15//! | Concept | Description |
16//! |---------|-------------|
17//! | [`StepBody`](crate::traits::step::StepBody) | The trait you implement to define a unit of work. |
18//! | [`WorkflowData`](crate::traits::step::WorkflowData) | The data type that flows between steps. Must be serializable. |
19//! | [`WorkflowBuilder`](crate::builder::WorkflowBuilder) | Fluent API for composing workflow definitions. |
20//! | [`StepBuilder`](crate::builder::StepBuilder) | Per-step configuration (name, error handling, compensation). |
21//! | [`WorkflowExecutor`](crate::executor::WorkflowExecutor) | Drives execution: acquires locks, runs steps, persists state. |
22//! | [`StepExecutionContext`](crate::traits::step::StepExecutionContext) | Runtime context passed to each step (data, pointers, tokens). |
23//! | [`ExecutionResult`](crate::models::ExecutionResult) | What a step returns to control flow (`next`, `branch`, `sleep`, etc.). |
24//! | [`WorkflowDefinition`](crate::models::WorkflowDefinition) | The compiled, serializable blueprint of a workflow. |
25//! | [`WorkflowInstance`](crate::models::WorkflowInstance) | A running (or persisted) execution of a definition. |
26//! | [`ExecutionPointer`](crate::models::ExecutionPointer) | Tracks the position of a single branch of execution. |
27//!
28//! # Hello workflow
29//!
30//! ```ignore
31//! use async_trait::async_trait;
32//! use serde::{Deserialize, Serialize};
33//! use wfe_core::builder::WorkflowBuilder;
34//! use wfe_core::models::ExecutionResult;
35//! use wfe_core::traits::step::{StepBody, StepExecutionContext};
36//!
37//! #[derive(Debug, Clone, Default, Serialize, Deserialize)]
38//! struct OrderData {
39//!     order_id: String,
40//!     amount: f64,
41//! }
42//!
43//! #[derive(Default)]
44//! struct ValidateOrder;
45//!
46//! #[async_trait]
47//! impl StepBody for ValidateOrder {
48//!     async fn run(&mut self, ctx: &StepExecutionContext<'_>) -> wfe_core::Result<ExecutionResult> {
49//!         let data: OrderData = ctx.workflow.data()?;
50//!         if data.amount <= 0.0 {
51//!             return Err(wfe_core::WfeError::Execution("amount must be positive".into()));
52//!         }
53//!         Ok(ExecutionResult::next())
54//!     }
55//! }
56//!
57//! #[derive(Default)]
58//! struct ProcessPayment;
59//!
60//! #[async_trait]
61//! impl StepBody for ProcessPayment {
62//!     async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> wfe_core::Result<ExecutionResult> {
63//!         println!("Processing payment...");
64//!         Ok(ExecutionResult::next())
65//!     }
66//! }
67//!
68//! let definition = WorkflowBuilder::<OrderData>::new()
69//!     .start_with::<ValidateOrder>()
70//!         .name("Validate")
71//!     .then::<ProcessPayment>()
72//!         .name("Payment")
73//!     .end_workflow()
74//!     .build("order-pipeline", 1);
75//! ```
76//!
77//! # Builder patterns
78//!
79//! The builder supports linear chains, containers, and control flow:
80//!
81//! - `.then::<S>()` — sequential next step
82//! - `.parallel(|b| { b.add_step_typed::<A>("a", None); b.add_step_typed::<B>("b", None); })` — run branches in parallel
83//! - `.if_do(|b| { b.add_step_typed::<Then>("then", None); })` — conditional branch
84//! - `.while_do(|b| { b.add_step_typed::<LoopBody>("loop", None); })` — loop while condition holds
85//! - `.for_each("items", |b| { b.add_step_typed::<ProcessItem>("item", None); })` — iterate over a collection
86//! - `.saga(|b| { b.add_step_typed::<Do>("do", None); }).compensate_with::<Undo>()` — saga with compensation
87//! - `.wait_for("event_name", "event_key")` — suspend until an external event arrives
88//! - `.delay(Duration::from_secs(30))` — pause for a fixed duration
89//! - `.then_fn(|| { println!("inline"); ExecutionResult::next() })` — inline closure step
90//!
91//! # Execution model
92//!
93//! 1. You build a [`WorkflowDefinition`](crate::models::WorkflowDefinition) using the builder API.
94//! 2. You register the definition and all step types with a `WorkflowHost` (from the `wfe` crate).
95//! 3. The host creates a [`WorkflowInstance`](crate::models::WorkflowInstance), persists it, and queues it for execution.
96//! 4. The [`WorkflowExecutor`](crate::executor::WorkflowExecutor) picks up the instance, acquires a distributed lock, and runs each active [`ExecutionPointer`](crate::models::ExecutionPointer).
97//! 5. After each step, the executor processes the [`ExecutionResult`](crate::models::ExecutionResult) — branching, suspending, or completing pointers — and persists the new state.
98//! 6. The lock is released, and the instance is re-queued if there is more work to do.
99//!
100//! This means workflows are **durable**: if the process crashes after step 3, the next executor invocation will resume from step 4 because the pointer state was persisted.
101//!
102//! # Primitives
103//!
104//! Built-in control-flow steps live in [`primitives`]:
105//!
106//! | Primitive | Purpose |
107//! |-----------|---------|
108//! | [`IfStep`](primitives::if_step::IfStep) | Conditional branching with `then`/`else` children |
109//! | [`WhileStep`](primitives::while_step::WhileStep) | Loop while a condition evaluates to `true` |
110//! | [`ForEachStep`](primitives::foreach_step::ForEachStep) | Iterate over a JSON array in workflow data |
111//! | [`SequenceStep`](primitives::sequence::SequenceStep) | Parallel branch container (all children run concurrently) |
112//! | [`DecideStep`](primitives::decide::DecideStep) | Multi-way branch (switch/case style) |
113//! | [`DelayStep`](primitives::delay::DelayStep) | Pause execution for a duration |
114//! | [`ScheduleStep`](primitives::schedule::ScheduleStep) | Resume at a specific wall-clock time |
115//! | [`WaitForStep`](primitives::wait_for::WaitForStep) | Suspend until an external event is published |
116//! | [`SagaContainerStep`](primitives::saga_container::SagaContainerStep) | Transaction-like container with compensation on failure |
117//! | [`RecurStep`](primitives::recur::RecurStep) | Recurring/periodic execution |
118//! | [`PollEndpointStep`](primitives::poll_endpoint::PollEndpointStep) | Poll an HTTP endpoint until a condition is met |
119//! | [`SubWorkflowStep`](primitives::sub_workflow::SubWorkflowStep) | Start a child workflow and wait for it to complete |
120//! | [`EndStep`](primitives::end_step::EndStep) | Explicit workflow termination |
121//!
122//! # Error handling
123//!
124//! Steps return [`ExecutionResult`](crate::models::ExecutionResult) which can signal:
125//! - [`ExecutionResult::next()`](crate::models::ExecutionResult::next) — continue to the next step
126//! - [`ExecutionResult::outcome(v)`](crate::models::ExecutionResult::outcome) — follow a named outcome branch
127//! - [`ExecutionResult::sleep(d)`](crate::models::ExecutionResult::sleep) — pause execution (used by `DelayStep`)
128//! - `Err(WfeError::Execution(...))` — mark the pointer as failed
129//! - [`ExecutionResult::persist(d)`](crate::models::ExecutionResult::persist) — mark the pointer as complete
130//!
131//! When a step fails, the executor checks the step's [`ErrorBehavior`](models::ErrorBehavior):
132//! - `Retry { interval, max_retries }` — retry with backoff
133//! - `Suspend` — pause the workflow for manual intervention
134//! - `CompensateThenRetry` — run the compensation step, then retry
135//! - `CompensateThenSuspend` — run compensation, then suspend
136//!
137//! # Feature flags
138//!
139//! | Feature | Description |
140//! |---------|-------------|
141//! | `test-support` | In-memory persistence, lock, and queue providers for unit testing |
142//! | `otel` | OpenTelemetry tracing integration |
143//!
144//! # Testing
145//!
146//! ```sh
147//! cargo test -p wfe-core
148//! ```
149//!
150//! No external dependencies required.
151
152/// Fluent builder API for composing workflow definitions.
153///
154/// See [`WorkflowBuilder`](crate::builder::WorkflowBuilder) for the main entry point and
155/// [`StepBuilder`](crate::builder::StepBuilder) for per-step configuration.
156pub mod builder;
157
158/// Error types and the [`Result`](error::Result) alias used throughout WFE.
159pub mod error;
160
161/// The workflow executor and supporting infrastructure.
162///
163/// [`WorkflowExecutor`](executor::WorkflowExecutor) is the heart of the engine.
164/// It acquires locks, loads instances, runs active pointers, and persists state.
165pub mod executor;
166
167/// Data models for workflows, instances, pointers, events, and execution results.
168pub mod models;
169
170/// Built-in control-flow primitives (if, while, foreach, saga, etc.).
171pub mod primitives;
172
173/// Core traits that define the plugin architecture.
174///
175/// Implement these traits to provide persistence, locking, queuing, lifecycle
176/// events, search, logging, and service provisioning.
177pub mod traits;
178
179#[cfg(any(test, feature = "test-support"))]
180/// In-memory test doubles for every provider trait.
181pub mod test_support;
182
183/// Artifact volume abstraction for distributed workflow execution.
184pub mod artifact_volume;
185
186/// Local filesystem artifact store (OCI Image Layout).
187pub mod local_artifact_store;
188
189pub use artifact_volume::{ArtifactVolume, ArtifactVolumePackage};
190pub use error::{Result, WfeError};
191pub use local_artifact_store::{LocalArtifactStore, extract_artifact_to_dir};