Table of Contents

Class StateMachineExtensions

Namespace
Trellis.StateMachine
Assembly
Trellis.StateMachine.dll

Provides extension methods for Stateless.StateMachine<TState, TTrigger> that return Result<TValue> instead of throwing on invalid transitions.

public static class StateMachineExtensions
Inheritance
StateMachineExtensions
Inherited Members

Remarks

These extensions pre-check the trigger with Stateless.StateMachine<TState, TTrigger>.CanFire(TTrigger) (which honors PermitIf/IgnoreIf guards) and translate disallowed transitions into an Error.UnprocessableContent (HTTP 422) — the requested action is a semantic rule violation against the aggregate's current state, not a concurrent-modification conflict. Exceptions thrown by user-supplied entry/exit/transition actions are not swallowed.

These extensions do not change the concurrency model of Stateless.StateMachine<TState, TTrigger>. Stateless state machines are not thread-safe, so concurrent calls to FireResult<TState, TTrigger>(StateMachine<TState, TTrigger>, TTrigger) on the same machine instance must still be externally synchronized. Because Stateless is single-threaded by contract, the CanFire+Fire pre-check pattern is race-free when used as documented.

Usage with Railway Oriented Programming:

var machine = new StateMachine<OrderState, OrderTrigger>(OrderState.New);
machine.Configure(OrderState.New)
    .Permit(OrderTrigger.Submit, OrderState.Submitted);

Result<OrderState> result = machine.FireResult(OrderTrigger.Submit);

Methods

FireResult<TState, TTrigger>(StateMachine<TState, TTrigger>, TTrigger)

Fires the specified trigger on the state machine and returns the new state as a Result<TValue>.

public static Result<TState> FireResult<TState, TTrigger>(this StateMachine<TState, TTrigger> stateMachine, TTrigger trigger) where TState : notnull where TTrigger : notnull

Parameters

stateMachine StateMachine<TState, TTrigger>

The state machine to fire the trigger on.

trigger TTrigger

The trigger to fire.

Returns

Result<TState>

A Result<TValue> containing the new state if the transition is valid, or an Error.UnprocessableContent carrying a single RuleViolation with reason code state.machine.invalid.transition if the trigger cannot be fired from the current state (including when blocked by a guard).

Type Parameters

TState

The type representing the states of the state machine.

TTrigger

The type representing the triggers/events of the state machine.

Examples

var machine = new StateMachine<State, Trigger>(State.Idle);
machine.Configure(State.Idle).Permit(Trigger.Start, State.Running);

// Valid transition
Result<State> result = machine.FireResult(Trigger.Start);
// result.IsSuccess == true; result holds State.Running.

// Invalid transition — Idle has no Trigger.Start defined here.
Result<State> invalid = machine.FireResult(Trigger.Pause);
// invalid.IsFailure == true; invalid.Error is Error.UnprocessableContent.

Remarks

Pre-checks with Stateless.StateMachine<TState, TTrigger>.CanFire(TTrigger) — which honors PermitIf/IgnoreIf guards — and only invokes Stateless.StateMachine<TState, TTrigger>.Fire(TTrigger) when the transition is permitted. This avoids any dependency on Stateless's exception message format and is therefore resilient to library upgrades.

HTTP semantics. An invalid state-machine transition is a semantic rule violation (the aggregate cannot honor the requested action from its current state), not a concurrent-modification conflict — retry will not succeed. The returned error is therefore Error.UnprocessableContent (HTTP 422), not Error.Conflict (HTTP 409). Callers can still distinguish state-machine rejections from other 422s by matching on the ReasonCode value state.machine.invalid.transition.

Exceptions thrown by user entry, exit, or transition actions are not swallowed — they propagate to the caller as InvalidOperationException or whatever type the user code threw.

The underlying Stateless.StateMachine<TState, TTrigger> remains not thread-safe, so callers must not invoke this method concurrently on the same machine instance without synchronization.