orka 0.1.0

An asynchronous, pluggable, and type-safe workflow engine for Rust, designed for orchestrating complex multi-step business processes.
Documentation
# Orka Workflow Engine - API Reference

## 1. Introduction / Core Concepts

Orka is an asynchronous, pluggable, and type-safe workflow engine for Rust. It allows developers to define complex, multi-step processes (pipelines) with fine-grained control over execution flow, error handling, and context management.

**Core Concepts & Primary Structs:**

*   **`Pipeline<TData, Err>`:** The central construct representing a workflow. It's generic over:
    *   `TData`: The primary data type for the pipeline's shared context. Must be `'static + Send + Sync`.
    *   `Err`: The error type returned by this pipeline's handlers. Must be `std::error::Error + From<OrkaError> + Send + Sync + 'static`.
    It manages a sequence of named steps, and handlers can be registered for `before`, `on`, and `after` phases of each step.

*   **`ContextData<T>`:** A smart-pointer wrapper (`Arc<RwLock<T>>`) providing shared, mutable access to context data within and across pipeline handlers. Clones of `ContextData<T>` share the same underlying data. **Lock guards obtained from `ContextData` must be dropped before any `.await` suspension point.**

*   **`Handler<TData, Err>` (Type Alias):** Represents an asynchronous function executed as part of a pipeline step. It takes `ContextData<TData>` and returns a `Future` resolving to `Result<PipelineControl, Err>`.

*   **`ConditionalScopeBuilder<'pipeline, TData, Err>`:** A fluent builder API used to define conditional execution of "scoped" sub-pipelines within a step of a main pipeline. This allows for dynamic branching of workflows.

*   **Scoped Pipelines:** These are independent `Pipeline<SData, Err>` instances (where `Err` is the main pipeline's error type) that can be executed conditionally. They operate on an extracted sub-context `SData`.

*   **`PipelineProvider<TData, SData, MainErr>` (Trait):** Defines how scoped pipelines are sourced, either statically or dynamically via a factory.

*   **`Orka<ApplicationError>`:** A type-keyed registry for managing and running different `Pipeline` instances. It's generic over `ApplicationError`, which is the top-level error type returned by `Orka::run`. `ApplicationError` must be `From<OrkaError>`.

*   **`OrkaError` (Enum):** The primary error type for the Orka framework itself, covering issues like configuration errors, missing handlers, or internal problems. Application-level error types should be `From<OrkaError>`.

**Main Entry Points:**

*   Creating a `Pipeline::new(...)`.
*   Registering handlers using methods like `pipeline.on_root(...)`, `pipeline.before_root(...)`, `pipeline.after_root(...)`.
*   Defining conditional logic using `pipeline.conditional_scopes_for_step(...)` to get a `ConditionalScopeBuilder`.
*   Creating an `Orka::new()` registry.
*   Registering pipelines with the registry: `orka_instance.register_pipeline(pipeline)`.
*   Executing a registered pipeline: `orka_instance.run(context_data).await`.

**Pervasive Types/Patterns:**

*   **`OrkaResult<T, E = OrkaError>` (Type Alias):** The standard `Result` type used for Orka's internal operations, defaulting to `OrkaError`.
*   **`ContextData<T>`:** Used ubiquitously for passing shared state to handlers.
*   **`PipelineControl` (Enum):** Returned by handlers to signal whether the pipeline should continue or stop.
*   **`PipelineResult` (Enum):** The outcome of a full pipeline execution (Completed or Stopped).
*   **`From<OrkaError>` Trait Bound:** Frequently required for application-specific error types used with `Pipeline` or `Orka` to allow them to absorb framework-level errors.

## 2. Main Types and Their Public Methods

### Struct `orka::pipeline::definition::Pipeline<TData, Err>`

The core workflow definition.

**Generic Parameters:**

*   `TData: 'static + Send + Sync`
*   `Err: std::error::Error + From<crate::error::OrkaError> + Send + Sync + 'static`

**Public Methods:**

*   **`pub fn new(step_defs: &[(&str, bool, Option<SkipCondition<TData>>)]) -> Self`**
    *   Creates a new pipeline with an initial set of step definitions.
    *   `SkipCondition<TData>` is `std::sync::Arc<dyn Fn(ContextData<TData>) -> bool + Send + Sync + 'static>`.

*   **`pub fn insert_before_step<S: Into<String>>(&mut self, existing_step_name: &str, new_step_name: S, optional: bool, skip_if: Option<SkipCondition<TData>>)`**
    *   Inserts a new step definition before an existing step. Panics if `existing_step_name` is not found or if `new_step_name` already exists.

*   **`pub fn insert_after_step<S: Into<String>>(&mut self, existing_step_name: &str, new_step_name: S, optional: bool, skip_if: Option<SkipCondition<TData>>)`**
    *   Inserts a new step definition after an existing step. Panics if `existing_step_name` is not found or if `new_step_name` already exists.

*   **`pub fn remove_step(&mut self, step_name: &str)`**
    *   Removes a step definition and its associated handlers/configurations. No-op if the step is not found.

*   **`pub fn set_optional(&mut self, step_name: &str, optional: bool)`**
    *   Sets the optional flag for a given step. Panics if `step_name` is not found.

*   **`pub fn set_skip_condition(&mut self, step_name: &str, skip_if: Option<SkipCondition<TData>>)`**
    *   Sets or clears the skip condition for a given step. Panics if `step_name` is not found.

*   **`pub fn before_root<F, UserProvidedErr>(&mut self, step_name: &str, handler_fn: impl Fn(ContextData<TData>) -> F + Send + Sync + 'static)`**
    *   Where:
        *   `F: Future<Output = Result<PipelineControl, UserProvidedErr>> + Send + 'static`
        *   `UserProvidedErr: Into<Err> + Send + Sync + 'static`
    *   Registers a handler to be executed *before* the main `on` handlers for the specified step.

*   **`pub fn on_root<F, UserProvidedErr>(&mut self, step_name: &str, handler_fn: impl Fn(ContextData<TData>) -> F + Send + Sync + 'static)`**
    *   Where:
        *   `F: Future<Output = Result<PipelineControl, UserProvidedErr>> + Send + 'static`
        *   `UserProvidedErr: Into<Err> + Send + Sync + 'static`
    *   Registers a main handler for the specified step.

*   **`pub fn after_root<F, UserProvidedErr>(&mut self, step_name: &str, handler_fn: impl Fn(ContextData<TData>) -> F + Send + Sync + 'static)`**
    *   Where:
        *   `F: Future<Output = Result<PipelineControl, UserProvidedErr>> + Send + 'static`
        *   `UserProvidedErr: Into<Err> + Send + Sync + 'static`
    *   Registers a handler to be executed *after* the main `on` handlers for the specified step.

*   **`pub fn set_extractor<SData>(&mut self, step_name: &str, extractor_fn: impl Fn(ContextData<TData>) -> Result<ContextData<SData>, OrkaError> + Send + Sync + 'static)`**
    *   Where:
        *   `SData: 'static + Send + Sync`
    *   Registers an extractor function for a step, allowing subsequent handlers (registered with `on<SData>`) to operate on a sub-context `ContextData<SData>`. The extractor itself returns `Result<_, OrkaError>` as extraction failure is a framework concern.

*   **`pub fn on<SData, F, SubHandlerErr>(&mut self, step_name: &str, handler_fn: impl Fn(ContextData<SData>) -> F + Send + Sync + 'static)`**
    *   Where:
        *   `SData: 'static + Send + Sync`
        *   `F: Future<Output = Result<PipelineControl, SubHandlerErr>> + Send + 'static`
        *   `SubHandlerErr: Into<Err> + Send + Sync + 'static + std::fmt::Debug`
        *   *(Implicitly `Err: From<OrkaError>` from the `impl` block)*
    *   Registers a handler that operates on an extracted sub-context `ContextData<SData>`. An extractor must be set first using `set_extractor`. Errors from the extractor (`OrkaError`) are converted to `Err`.

*   **`pub fn conditional_scopes_for_step(&mut self, step_name: &str) -> ConditionalScopeBuilder<TData, Err>`**
    *   Returns a builder to define conditional execution of scoped sub-pipelines for the specified step. The step will be created if it doesn't exist.

*   **`pub async fn run(&self, ctx_data: ContextData<TData>) -> Result<PipelineResult, Err>`**
    *   Executes the entire pipeline sequentially.
    *   Internal Orka framework errors during execution (e.g., missing handler for a non-optional step) are converted to `Err`.

### Struct `orka::core::context_data::ContextData<T>`

A wrapper for shared context data using `Arc<RwLock<T>>`.

**Generic Parameters:**

*   `T: 'static + Send + Sync`

**Public Methods:**

*   **`pub fn new(data: T) -> Self`**
    *   Creates a new `ContextData` instance wrapping the given data.

*   **`pub fn read(&self) -> parking_lot::RwLockReadGuard<'_, T>`**
    *   Acquires a read lock. Panics if poisoned. Guard must be dropped before `.await`.

*   **`pub fn write(&self) -> parking_lot::RwLockWriteGuard<'_, T>`**
    *   Acquires a write lock. Panics if poisoned. Guard must be dropped before `.await`.

*   **`pub fn try_read(&self) -> Option<parking_lot::RwLockReadGuard<'_, T>>`**
    *   Attempts to acquire a read lock without blocking.

*   **`pub fn try_write(&self) -> Option<parking_lot::RwLockWriteGuard<'_, T>>`**
    *   Attempts to acquire a write lock without blocking.

*   **`pub fn map_read<F, U: ?Sized>(&self, f: F) -> parking_lot::MappedRwLockReadGuard<'_, U>`**
    *   Where `F: FnOnce(&T) -> &U`
    *   Acquires a read lock and maps it to a part of the data.

*   **`pub fn map_write<F, U: ?Sized>(&self, f: F) -> parking_lot::MappedRwLockWriteGuard<'_, U>`**
    *   Where `F: FnOnce(&mut T) -> &mut U`
    *   Acquires a write lock and maps it to a part of the data.

**Implemented Traits:** `Clone`, `Debug`, `Default` (if `T: Default`).

### Struct `orka::conditional::builder::ConditionalScopeBuilder<'pipeline, TData, Err>`

Builder for defining conditional scopes within a pipeline step.

**Generic Parameters:**

*   `'pipeline` (lifetime)
*   `TData: 'static + Send + Sync`
*   `Err: std::error::Error + From<OrkaError> + Send + Sync + 'static`

**Public Methods:**

*   **`pub fn add_static_scope<SData>(self, static_pipeline: Arc<Pipeline<SData, Err>>, extractor_fn: impl Fn(ContextData<TData>) -> Result<ContextData<SData>, OrkaError> + Send + Sync + 'static) -> ConditionalScopeConfigurator<'pipeline, TData, SData, Err, StaticPipelineProvider<SData, Err>>`**
    *   Where:
        *   `SData: 'static + Send + Sync`
    *   Adds a scope that uses a statically provided `Pipeline<SData, Err>`. The `extractor_fn` produces the sub-context, and its potential failure is an `OrkaError`.

*   **`pub fn add_dynamic_scope<SData, F, Fut>(self, pipeline_factory: F, extractor_fn: impl Fn(ContextData<TData>) -> Result<ContextData<SData>, OrkaError> + Send + Sync + 'static) -> ConditionalScopeConfigurator<'pipeline, TData, SData, Err, FunctionalPipelineProvider<TData, SData, Err, F, Fut>>`**
    *   Where:
        *   `SData: 'static + Send + Sync`
        *   `F: Fn(ContextData<TData>) -> Fut + Send + Sync + 'static`
        *   `Fut: Future<Output = Result<Arc<Pipeline<SData, Err>>, OrkaError>> + Send + 'static`
    *   Adds a scope that uses an asynchronous factory function to obtain a `Pipeline<SData, Err>`. The factory itself can fail with an `OrkaError`. The `extractor_fn` can also fail with an `OrkaError`.

*   **`pub fn if_no_scope_matches(mut self, behavior: PipelineControl) -> Self`**
    *   Specifies the `PipelineControl` behavior if no conditional scopes match during execution. Defaults to `PipelineControl::Continue`.

*   **`pub fn finalize_conditional_step(self, optional_for_main_step: bool)`**
    *   Finalizes the conditional scopes for the current step, registers a master handler in the main pipeline, and sets the optionality of this conditional step within the main pipeline.

### Struct `orka::conditional::builder::ConditionalScopeConfigurator<'pipeline, TData, SData, Err, P>`

Intermediate builder for configuring a single conditional scope (setting its condition).

**Generic Parameters:**

*   `'pipeline` (lifetime)
*   `TData: 'static + Send + Sync`
*   `SData: 'static + Send + Sync`
*   `Err: std::error::Error + From<OrkaError> + Send + Sync + 'static`
*   `P: PipelineProvider<TData, SData, Err> + 'static`

**Public Methods:**

*   **`pub fn on_condition(mut self, condition_fn: impl Fn(ContextData<TData>) -> bool + Send + Sync + 'static) -> ConditionalScopeBuilder<'pipeline, TData, Err>`**
    *   Sets the boolean condition for the current scope to be executed. Returns the main `ConditionalScopeBuilder` to allow chaining or adding more scopes.

### Struct `orka::registry::Orka<ApplicationError = OrkaError>`

A type-keyed registry for managing and executing different `Pipeline` instances.

**Generic Parameters:**

*   `ApplicationError: std::error::Error + From<OrkaError> + Send + Sync + 'static` (defaults to `OrkaError`)

**Public Methods:**

*   **`pub fn new() -> Self`**
    *   Creates a new, empty Orka registry.

*   **`pub fn new_default() -> Orka<OrkaError>`**
    *   Convenience method to create `Orka` with `OrkaError` as its application error type. *(Only available if `ApplicationError` is `OrkaError`)*.

*   **`pub fn register_pipeline<TData, PipelineHandlerError>(&self, pipeline: Pipeline<TData, PipelineHandlerError>)`**
    *   Where:
        *   `TData: 'static + Send + Sync`
        *   `PipelineHandlerError: std::error::Error + From<OrkaError> + Send + Sync + 'static`
        *   `ApplicationError: From<PipelineHandlerError>`
        *   `Pipeline<TData, PipelineHandlerError>: Send + Sync`
    *   Registers a pipeline with the Orka instance. It will be keyed by the `TypeId` of `TData`.

*   **`pub async fn run<TData>(&self, ctx_data: ContextData<TData>) -> Result<PipelineResult, ApplicationError>`**
    *   Where:
        *   `TData: 'static + Send + Sync`
    *   Executes the pipeline registered for the type `TData` using the provided `ctx_data`. Returns the outcome of the pipeline or an `ApplicationError`.

## 3. Public Traits and Their Methods

### Trait `orka::conditional::provider::PipelineProvider<TData, SData, MainErr>`

Defines a contract for objects that can provide instances of scoped pipelines.

**Generic Parameters:**

*   `TData: 'static + Send + Sync`
*   `SData: 'static + Send + Sync`
*   `MainErr: std::error::Error + From<OrkaError> + Send + Sync + 'static`

**Methods:**

*   **`async fn get_pipeline(&self, main_ctx_data: ContextData<TData>) -> Result<Arc<Pipeline<SData, MainErr>>, OrkaError>`**
    *   Asynchronously gets or creates an `Arc<Pipeline<SData, MainErr>>`. The provider's own operation can fail with an `OrkaError`.

## 4. Public Enums (Non-Config)

### Enum `orka::core::control::PipelineControl`

Signal from a handler indicating whether the pipeline should continue or stop.

**Variants:**

*   **`Continue`**
*   **`Stop`**

### Enum `orka::core::control::PipelineResult`

Outcome of a full pipeline execution.

**Variants:**

*   **`Completed`**: The pipeline executed all non-skipped, non-optional steps.
*   **`Stopped`**: The pipeline was explicitly stopped by a handler.

## 5. Public Type Aliases

### `pub type Handler<TData, Err> = Box<dyn Fn(ContextData<TData>) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<PipelineControl, Err>> + Send>> + Send + Sync>`
*   (Located in `orka::core::context`)
*   The type for a pipeline step handler function.

### `pub type OrkaResult<T, E = OrkaError> = std::result::Result<T, E>`
*   (Located in `orka::error`)
*   A standard result type defaulting to `OrkaError` for the error variant.

### `pub type SkipCondition<TData> = std::sync::Arc<dyn Fn(ContextData<TData>) -> bool + Send + Sync + 'static>`
*   (Located in `orka::core::step`)
*   Type for a closure used to determine if a pipeline step should be skipped.

## 6. Error Handling

### Enum `orka::error::OrkaError`

The primary error type for the Orka framework.

**Variants:**

*   **`StepNotFound { step_name: String }`**
*   **`HandlerMissing { step_name: String }`**
*   **`ExtractorFailure { step_name: String, source: anyhow::Error }`**
*   **`PipelineProviderFailure { step_name: String, source: anyhow::Error }`**
*   **`TypeMismatch { step_name: String, expected_type: String }`**
*   **`HandlerError { source: anyhow::Error }`**: Wraps an error originating from user-provided handler logic or external operations, typically when an external error is converted to `OrkaError`.
*   **`ConfigurationError { step_name: String, message: String }`**
*   **`Internal(String)`**: For miscellaneous internal Orka errors.
*   **`NoConditionalScopeMatched { step_name: String }`**: Used if conditional execution completes without any scope's condition being met and no default behavior is specified to handle this scenario (though currently, `ConditionalScopeBuilder` defaults to `Continue` or user-specified control).

**Standard Result Type:**

*   **`pub type OrkaResult<T, E = OrkaError> = std::result::Result<T, E>;`**
    *   Applications using Orka will typically define their own error enum (e.g., `AppError`) and implement `From<OrkaError>` for it, allowing seamless conversion of Orka framework errors into the application's error domain.

## 7. Modules

The public API is primarily exposed through re-exports in `orka::lib.rs`. Key modules include:

*   **`orka::pipeline`**: Contains `Pipeline` definition.
*   **`orka::core`**: Contains core types like `ContextData`, `Handler`, `PipelineControl`, `PipelineResult`, `StepDef`.
*   **`orka::conditional`**: Contains `ConditionalScopeBuilder`, `ConditionalScopeConfigurator`, and `PipelineProvider` trait.
*   **`orka::registry`**: Contains the `Orka` registry.
*   **`orka::error`**: Contains `OrkaError` and `OrkaResult`.