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
stateMachineStateMachine<TState, TTrigger>The state machine to fire the trigger on.
triggerTTriggerThe 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.transitionif the trigger cannot be fired from the current state (including when blocked by a guard).
Type Parameters
TStateThe type representing the states of the state machine.
TTriggerThe 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.