Crank.Examples.Submission (Crank v0.2.0)

Copy Markdown View Source

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()

@type data() :: %{parameters: map(), audit: [term()]}

Data shared across all states: parameters and an audit trail.

event()

@type event() ::
  {:violation, atom()}
  | :validate
  | {:add_quote, map()}
  | {:select, non_neg_integer()}
  | :bind
  | :decline
  | :note
  | :noop

Events the submission machine accepts.

state()

@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.

Functions

child_spec(args)

events()

@spec events() :: [atom()]

Returns the list of atom events. Tuple events (:violation, :add_quote, :select) are generated separately in tests.

state_modules()

@spec state_modules() :: [module()]

Returns the list of all state struct modules.