A submission workflow where each state is its own struct.
%Validating{} has a violations field. %Quoted{} has quotes
and selected. %Bound{} has quote and bound_at. Each struct
carries only the fields that exist in that state. A %Quoted{}
can't have a violations field because the struct doesn't define
one. The compiler enforces this.
This is Scott Wlaschin's "Making Illegal States Unrepresentable"
pattern. It works in Crank without any special support because
Machine.state is term() -- structs are valid states.
Validating ──validate──→ Quoted ──bind──→ Bound
│ │
└──decline──→ Declined ←──decline──┘State structs carry state-specific data. The data map carries
cross-cutting concerns shared across all states (parameters, audit).
When a field on the current state struct changes (adding a violation
to %Validating{}), the return is {:next_state, %Validating{updated}, data}.
The state value changed, so it's a state transition. :keep_state
is reserved for changes to data only.
Every event is handled in every state (total function).
Type annotations
The @type state union below lists every valid state struct. Today
these annotations serve as documentation and Dialyzer input. When
Elixir's set-theoretic type system (introduced in v1.17) can check
them, unhandled state variants will produce compiler warnings
without any code changes.
Summary
Types
Data shared across all states: parameters and an audit trail.
Events the submission machine accepts.
One of the four state structs: Validating, Quoted, Bound, or Declined.
Functions
Returns the list of atom events. Tuple events (:violation, :add_quote, :select) are generated separately in tests.
Returns the list of all state struct modules.
Types
Data shared across all states: parameters and an audit trail.
@type event() :: {:violation, atom()} | :validate | {:add_quote, map()} | {:select, non_neg_integer()} | :bind | :decline | :note | :noop
Events the submission machine accepts.
@type state() ::
Crank.Examples.Submission.Validating.t()
| Crank.Examples.Submission.Quoted.t()
| Crank.Examples.Submission.Bound.t()
| Crank.Examples.Submission.Declined.t()
One of the four state structs: Validating, Quoted, Bound, or Declined.