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.InvariantViolation (HTTP 422) — a rejected transition is a
domain-invariant breach against the aggregate's current state, not inbound-input validation
or 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 trigger and returns the outcome as a Result<TValue>. Short-circuits
with Error.InvariantViolation when the transition is not permitted in the current
state (i.e., CanFire returns false), without invoking any
consumer-registered OnUnhandledTrigger callback. Consumers who want their
OnUnhandledTrigger callback to run must call Fire directly — FireResult
is the guarded entry point that prefers a typed Error.InvariantViolation over
running side-effect code.
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.InvariantViolation with reason code
state-machine.invalid-transitionif the trigger cannot be fired from the current state or a guard throws InvalidOperationException.
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: Idle permits Trigger.Start, advancing the machine to Running.
Result<State> result = machine.FireResult(Trigger.Start);
// result.IsSuccess == true; result holds State.Running.
// Invalid transition: Running has no transition configured for Trigger.Pause.
Result<State> invalid = machine.FireResult(Trigger.Pause);
// invalid.IsFailure == true; invalid.Error is Error.InvariantViolation.
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 both Stateless exception-message parsing and invoking consumer
OnUnhandledTrigger callbacks from the typed-result path.
HTTP semantics. An invalid state-machine transition is a domain-invariant breach
(the aggregate cannot honor the requested action from its current state), not inbound-input
validation or a concurrent-modification conflict — retry will not succeed. The returned error
is therefore Error.InvariantViolation (HTTP 422), not Error.InvalidInput
or Error.Conflict (HTTP 409). Callers can distinguish state-machine rejections from
other 422s by matching on the Code value state-machine.invalid-transition.
InvalidOperationException thrown while evaluating a guard is converted to Error.InvariantViolation. Exceptions thrown by user entry, exit, or transition actions are not swallowed — they propagate to the caller.
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.