flowstate 0.5.0

Workflow runtime powered by finite state machines.
Documentation
# `flowstate`

Flowstate is library for modelling multi-step processes as self-executing state
machines. It is heavily inspired by the [typestate pattern](https://cliffle.com/blog/rust-typestate/#variation-state-type-parameter)
but with the goal of making it easier to model self-executing workflows as
finite state machines.

Typestate APIs make invalid state transitions impossible. However, the caller is
typically responsible for driving the state transitions. In contrast, each state
in a Flowstate workflow is responsible for transitioning to the next state. This
allows workflows to be self-executing.

## Basic Usage

The following is an example of a very basic workflow.

```rs
/**
 * ╔═[BasicWorkflow]════════════════════════════╗
 * ║ [StateA] ──> [StateB] ──> [WorkflowResult] ║
 * ╚════════════════════════════════════════════╝
 */

use flowstate::prelude::*;

#[derive(Workflow)]
#[flowstate(
    result = WorkflowResult,
    state_trait = BasicWorkflowState,
)]
struct BasicWorkflow<State> {
    #[state]
    _state: State,
}

#[derive(State)]
struct StateA;

impl BasicWorkflowState for BasicWorkflow<StateA> {
    fn next(self: Box<Self>) -> StaticTransition<WorkflowResult> {
        self.transition(StateB)
    }
}

#[derive(State)]
struct StateB;

impl BasicWorkflowState for BasicWorkflow<StateB> {
    fn next(self: Box<Self>) -> StaticTransition<WorkflowResult> {
        self.finish(WorkflowResult)
    }
}

#[derive(Debug, PartialEq)]
struct WorkflowResult;

#[test]
fn test_basic_workflow() {
    let workflow = BasicWorkflow::new(StateA);
    let result = workflow.run();
    assert_eq!(result, WorkflowResult);
}
```

## Getting started

Add `flowstate` to your `Cargo.toml`.

```toml
[dependencies]
flowstate = "0.3"
```

The prelude brings all the essential types into scope.

```rust
use flowstate::prelude::*;
```

Next, derive the `Workflow` trait. Flowstate can be [used without procedural macros](tests/basic_workflow_manual_impls.rs),
but it requires a little more boilerplate.

```rs
#[derive(Workflow)]
#[flowstate(
    result = MyWorkflowResult,
    state_trait = MyWorkflowState,
)]
struct MyWorkflow<State> {
    #[state]
    _state: State,
    ctx: MyWorkflowContext,
}
```

The `#[flowstate(..)]` attribute defines the result type, and the identifier
for the workflow state trait.

The result type, is the type returned on completion of the workflow. If your
workflow has multiple terminal states, this should be an enum representing each
of those terminal states.

The `state_trait`, if specified, causes a trait to be generated, which should be
implemented by each of the workflow states. This is optional, and we could also
forgo generating this trait, and instead implement `flowstate::WorkflowState`
for each of our states.

The `#[state]` attribute lets Typestate know which field stores your state.

You can also define one or more context field, such as `ctx` in the example
above. These will be automatically propogated each time your workflow
transitions to a new state.

Next, define your states.

```rs
#[derive(State)]
struct MyState;

impl MyWorkflowState for MyWorkflow<MyState> {
    fn next(self: Box<Self>) -> StaticTransition<MyWorkflowResult> {
        // Do some work...

        self.transition(MyNextState)
    }
}
```

The `MyWorkflowState` trait was generated by the `#[derive(Workflow)]` macro.
Implementing it allows you to define the transition logic for your state.
`MyWorkflowState` should be implemented on `MyWorkflow<MyState>`, not on
`MyState`. This allows us to access context fields.

Also worth noting, is that we are returning a `StaticTransition`. Provided that
your workflow type (e.g. `MyWorkflow`) does not have any generic lifetime
parameters, all transitions will be `'static`. If you do require a generic
lifetime parameter on your workflow, then you can find more details in the
section on "working with generic lifetimes" below.

In the above example, we return `self.transition(MyNextState)`. This is a
helper function generated by `#[derive(Workflow)]`. You can manually instantiate
the `Transition<MyWorkflowResult>` type, but the `transition` function removes
some of the boilerplate.

You can also return `self.finish(result)` to terminate the workflow with a
result.

On occasion, you may need move out of the previous state, or access the context
when constructing the next state, or result. In such cases, you may encounter
borrow checker errors. To avoid these, you can use `self.transition_with(|state| ...)`
or `self.finish_with(|workflow| ...)`.

Finally, you can construct and run your workflow.

```rs
let workflow = MyWorkflow::new(MyState, MyWorkflowContext { /* ... */ });
let result = workflow.run();
```

## Concepts

### States

A state is any type that implements [`State`]. The `#[derive(State)]` macro
implements it automatically.

```rust
#[derive(State)]
struct Loading;

#[derive(State)]
struct Processing;

#[derive(State)]
struct Done;
```

### Workflows

A workflow is a struct generic over a `State` type parameter. Workflows must
implement the `Workflow` trait. The `#[derive(Workflow)]` macro implements this
automatically, and generates some other utility methods and traits.

One field must be annotated with `#[state]`; all other fields are context shared
across every state. The `#[derive(Workflow)]` macro also requires a
`#[flowstate(result = T)]` attribute, specifying the type the workflow produces
when it terminates.

```rust
#[derive(Workflow)]
#[flowstate(result = MyResult)]
struct MyWorkflow<State> {
    #[state]
    _state: State,
    // context fields shared across all states
    input: Vec<u8>,
}
```

The macro generates:

- A `new(state, ...context_fields)` constructor.
- An implementation of [`Workflow`].
- A `MyWorkflow::transition(next_state)` method, which moves to the next state, carrying context through.
- A `MyWorkflow::transition_with(map_fn)` method, which transitions by mapping the current state to the next.
- A `{WorkflowName}State` trait (e.g. `MyWorkflowState`) that should be implemented for each state.

### Transitions

[`Transition<R>`] is an alias for `ControlFlow<R, Box<dyn WorkflowState<R>>>`.
Each state's `next` method returns one of:

- `self.transition(next_state)` continues to another state.
- `self.transition_with(|state| next_state)` continues to another state by mapping the previous state to the new state.
- `self.finish(result)` terminates with a result value.
- `self.finish_with(|workflow| result)` terminates by mapping the whole workflow to a result.

These map to `ControlFlow::Continue` for the `transition` and `transition_with`
methods, and `ControlFlow::Break` for the `finish` and `finish_with` methods.

## Advanced topics

### Working with generic lifetimes

On occasion, your workflow struct may require a generic lifetime parameter.

```rs
struct MyWorkflow<'workflow, State> {
    state: State,
    ctx: &'workflow str,
}
```

The derive macro does not currently support this pattern (though it should do
soon). For now, you can refer to [tests/workflow_with_lifetime_generics_manual_impls.rs](tests/workflow_with_lifetime_generics_manual_impls.rs)
for more details.