StatifierUI.Fixtures (StatifierUI v0.9.1)

Copy Markdown View Source

Example data a host supplies for a chart: named scenario datamodels, example event payloads, and named datasets for evaluating expressions (ADR-0003, ADR-0006).

A fixture bundle is a set of maps:

  • scenarios - a scenario name (for example "within-budget-account") mapped to a complete example of the host-supplied datamodel for that situation.
  • events - an event name (for example "authorize.approved") mapped to a sample _event.data payload for that event.
  • datasets - a dataset name mapped to a situation for evaluating a free-standing expression against.

A scenario and a dataset are not the same thing, even though both are string-keyed example data. A scenario is a complete example of the host-supplied datamodel for running a chart; it stands in for the whole state a chart would actually see. A dataset exists only to evaluate expressions and may be as small as the expression needs - a two-key map naming just the variables a single guard reads, with nothing else a real chart run would carry. ADR-0006 keeps datasets separate from scenarios rather than asking a scenario to double as both, so a dataset can stay minimal and a scenario can stay a faithful example.

Two delivery paths converge on this one struct: a host module implementing StatifierUI.Fixtures.Source (from_source/1), and a JSON sidecar next to a chart (a later phase's loader). Every consumer downstream of either path is indifferent to which one produced the struct.

new/1 validates rather than raises: scenario datamodels must have string keys at every level, so a fixture that would blow up deep inside the engine is instead rejected at load time. Event payloads are preserved verbatim, including the three-way :undefined / nil / map distinction predicator's _event.data semantics require.

Scenario validation is deliberately stricter than the engine's, not a mirror of it. Statifier.MachineState.new/2 rejects only atom keys that are not booleans, so it accepts an integer or boolean key; this rejects every key that is not a string. The reason is convergence: a JSON object key is always a string, so a scenario a behaviour source could supply but a sidecar could never express would break the one-struct guarantee ADR-0003 rests on. Rejecting it in both paths keeps them interchangeable.

A dataset is validated exactly as a scenario is - string keys at every depth, for the same convergence reason - so a predicator duration cannot appear inside a dataset either: it decodes to a bare atom-keyed map, and the same rule that rejects it inside a scenario (test/support/fixtures/tagged_source.ex) rejects it here.

expressions is the fourth map: an expression name mapped to a free-standing predicator "source" string and an optional "expect" map of expected results, keyed by dataset name. Unlike a scenario or a dataset, an expression entry is not walked for string keys - it is validated only for the shape ADR-0006 fixes (a "source" string, an "expect" map with binary keys), because its expect values are predicator values in their own right and a duration is a legal one there, unlike inside a datamodel. An absent "expect" entry for a dataset name means no expectation is stated for that dataset - not that the expression is expected to be undefined against it - and expect/3 returns :error for both that case and an unknown expression name, since neither is a bundle error.

Summary

Types

An example datamodel: string keys at every level.

The name of a fixture dataset.

A diagnostic surfaced while loading a fixture bundle. path is the key path inside the bundle; source is the file it came from, or nil for a bundle that never was a file.

The name of a fixture event, matching an SCXML event name.

A free-standing expression fixture: a predicator "source" string and an optional "expect" map keyed by dataset name (ADR-0006). Keys beyond those two are preserved verbatim.

The name of a fixture expression.

The name of a fixture scenario.

t()

Functions

Fetches a dataset by name.

Dataset names, sorted (ADR-0005's canonical-order rule).

Fetches an event's sample payload by name. Returns :error when the event has no fixture, distinct from an event whose fixture value is nil or :undefined.

Event names, sorted (ADR-0005's canonical-order rule).

Fetches the expected result stated for expression_name against dataset_name.

Fetches an expression by name.

Expression names, sorted (ADR-0005's canonical-order rule).

Builds a validated fixture bundle from a StatifierUI.Fixtures.Source module.

Builds a validated fixture bundle from :scenarios, :events, :datasets, and :expressions options.

Fetches a scenario's datamodel by name.

Scenario names, sorted (ADR-0005's canonical-order rule).

Types

datamodel()

@type datamodel() :: %{optional(String.t()) => term()}

An example datamodel: string keys at every level.

dataset_name()

@type dataset_name() :: String.t()

The name of a fixture dataset.

diagnostic()

@type diagnostic() :: %{
  kind: atom(),
  message: String.t(),
  path: [String.t()],
  source: Path.t() | nil
}

A diagnostic surfaced while loading a fixture bundle. path is the key path inside the bundle; source is the file it came from, or nil for a bundle that never was a file.

event_name()

@type event_name() :: String.t()

The name of a fixture event, matching an SCXML event name.

expression()

@type expression() :: %{required(String.t()) => term()}

A free-standing expression fixture: a predicator "source" string and an optional "expect" map keyed by dataset name (ADR-0006). Keys beyond those two are preserved verbatim.

expression_name()

@type expression_name() :: String.t()

The name of a fixture expression.

scenario_name()

@type scenario_name() :: String.t()

The name of a fixture scenario.

t()

@type t() :: %StatifierUI.Fixtures{
  datasets: %{optional(dataset_name()) => datamodel()},
  diagnostics: [diagnostic()],
  events: %{optional(event_name()) => term()},
  expressions: %{optional(expression_name()) => expression()},
  scenarios: %{optional(scenario_name()) => datamodel()}
}

Functions

dataset(fixtures, name)

@spec dataset(t(), dataset_name()) :: {:ok, datamodel()} | :error

Fetches a dataset by name.

dataset_names(fixtures)

@spec dataset_names(t()) :: [dataset_name()]

Dataset names, sorted (ADR-0005's canonical-order rule).

event(fixtures, name)

@spec event(t(), event_name()) :: {:ok, term()} | :error

Fetches an event's sample payload by name. Returns :error when the event has no fixture, distinct from an event whose fixture value is nil or :undefined.

event_names(fixtures)

@spec event_names(t()) :: [event_name()]

Event names, sorted (ADR-0005's canonical-order rule).

expect(fixtures, expression_name, dataset_name)

@spec expect(t(), expression_name(), dataset_name()) :: {:ok, term()} | :error

Fetches the expected result stated for expression_name against dataset_name.

Returns :error both when expression_name names no expression and when the expression exists but states no expectation for dataset_name - an absent expect key means no expectation is stated (ADR-0006), which is not an error condition, so the two cases are deliberately not distinguished.

expression(fixtures, name)

@spec expression(t(), expression_name()) :: {:ok, expression()} | :error

Fetches an expression by name.

expression_names(fixtures)

@spec expression_names(t()) :: [expression_name()]

Expression names, sorted (ADR-0005's canonical-order rule).

from_source(module)

@spec from_source(module()) :: {:ok, t()} | {:error, term()}

Builds a validated fixture bundle from a StatifierUI.Fixtures.Source module.

Calls module.scenarios() and module.example_events(), and - when the module implements the optional datasets/0 and expressions/0 callbacks - module.datasets() and module.expressions(), and routes all of them through new/1, so this path and the sidecar loader share exactly one validation implementation. Returns {:error, {:not_a_source, module}} when module does not implement the behaviour.

new(opts \\ [])

@spec new(keyword()) :: {:ok, t()} | {:error, term()}

Builds a validated fixture bundle from :scenarios, :events, :datasets, and :expressions options.

All four options default to %{}. Returns {:error, reason} rather than raising when a scenario, event, or dataset key is not a string, when a scenario or dataset datamodel is not a map, or when one contains a non-string key at any depth. A non-string key found inside a datamodel is reported as {:invalid_key, key, path}, where path starts with the scenario or dataset name - or, when the offending map is a predicator duration, as {:duration_in_scenario, path} or {:duration_in_dataset, path}, which says why rather than naming a unit key the author never wrote.

Examples

iex> StatifierUI.Fixtures.new(scenarios: %{"within-budget-account" => %{"currency" => "USD"}})
{:ok, %StatifierUI.Fixtures{scenarios: %{"within-budget-account" => %{"currency" => "USD"}}}}

scenario(fixtures, name)

@spec scenario(t(), scenario_name()) :: {:ok, datamodel()} | :error

Fetches a scenario's datamodel by name.

scenario_names(fixtures)

@spec scenario_names(t()) :: [scenario_name()]

Scenario names, sorted (ADR-0005's canonical-order rule).