# `Cucumber.Messages`
[🔗](https://github.com/huddlz-hq/cucumber/blob/main/lib/cucumber/messages.ex#L1)

Builders and NDJSON encoding for [Cucumber Messages](https://github.com/cucumber/messages),
the protocol every Cucumber implementation emits so formatters, report
services, and the Cucumber Compatibility Kit can consume runs uniformly.

A message is an *envelope*: a single-key map whose key names the message
type (mirroring the schema's `oneof`) and whose value is the payload,
with camelCase keys matching the JSON schema. One envelope per line,
JSON-encoded, makes the NDJSON stream.

Two groups of builders live here:

  * **Static messages** — derivable from source files alone: `meta/0`,
    `source/2`, `gherkin_document/3`, `pickle/1`, `step_definition/2`,
    `parameter_type/2`, and `hook/5`.
  * **Run-time messages** — describing an execution: `test_run_started/2`,
    `test_case/4`, `test_case_started/4`, `test_step_started/3`,
    `test_step_finished/4`, `test_case_finished/3`, `attachment/2`,
    `test_run_hook_started/4`, `test_run_hook_finished/3`, and
    `test_run_finished/3`.

These builders are pure — id allocation, ordering, and NDJSON output are
owned by `Cucumber.RunCoordinator` (the run-wide message sink) and driven
by `Cucumber.Messages.Emitter`. Enable the stream with

    config :cucumber, messages: "cucumber-messages.ndjson"

The file is written when the test suite finishes, via
`ExUnit.after_suite/1`. Those callbacks fire on every `ExUnit.run/1`, so
a project that invokes a nested `ExUnit.run/1` mid-suite will flush (and
close) the stream at that point rather than at the end of the real run.

## Example

Ids must be unique across the whole stream, so when emitting several
features, thread `next_id` from one compilation into the next (as
`Cucumber.compile_features!/1` does):

    {lines, _next_id} =
      Enum.flat_map_reduce(features, 0, fn feature, start_id ->
        compilation = Gherkin.Pickles.compile(feature, start_id)

        envelopes = [
          Cucumber.Messages.source(feature.file, feature.source),
          Cucumber.Messages.gherkin_document(
            feature.file,
            compilation.document,
            compilation.comments
          )
          | Enum.map(compilation.pickles, &Cucumber.Messages.pickle/1)
        ]

        {Enum.map(envelopes, &Cucumber.Messages.encode!/1), compilation.next_id}
      end)

## Known deltas from the reference stream

`testCase.testSteps` entries do not carry `stepMatchArgumentsLists`
(expression matching returns converted values, not source offsets), and
`parameterType`/`hook` envelopes have no source line (their macros don't
record one). The CCK approval harness (`test/cucumber/cck_approval_test.exs`)
normalizes these; its samples table documents the three samples with
further per-sample divergences.

# `envelope`

```elixir
@type envelope() :: %{required(atom()) =&gt; map()}
```

A single-key envelope map, e.g. `%{pickle: %{...}}`.

# `instant`

```elixir
@type instant() :: %{seconds: integer(), nanos: integer()}
```

A `%{seconds: integer, nanos: integer}` map (timestamp or duration).

# `status`

```elixir
@type status() ::
  :passed | :failed | :pending | :skipped | :undefined | :ambiguous | :unknown
```

A step/hook result status accepted by `test_step_result/3`.

# `attachment`

```elixir
@spec attachment(Cucumber.Attachment.t(), map() | nil) :: envelope()
```

Builds an `attachment` envelope from a `Cucumber.Attachment`.

`ref` carries `:test_case_started_id`/`:test_step_id` when the attachment
was recorded during a scenario (both optional in the schema), or
`:test_run_hook_started_id` when it was recorded by a `before_all` or
`after_all` hook.

## Examples

    attachment = %Cucumber.Attachment{
      body: "provisioned",
      media_type: "text/plain",
      encoding: :identity
    }

    Cucumber.Messages.attachment(attachment, %{test_run_hook_started_id: "7"})
    # => %{
    #      attachment: %{
    #        body: "provisioned",
    #        mediaType: "text/plain",
    #        contentEncoding: "IDENTITY",
    #        testRunHookStartedId: "7"
    #      }
    #    }

# `duration`

```elixir
@spec duration(integer()) :: instant()
```

Converts a monotonic nanosecond difference into a duration.

Negative differences are clamped at zero.

# `encode!`

```elixir
@spec encode!(envelope()) :: String.t()
```

Encodes an envelope as one NDJSON line (no trailing newline).

# `gherkin_document`

```elixir
@spec gherkin_document(String.t(), map(), [map()]) :: envelope()
```

Builds a `gherkinDocument` envelope from a compiled feature node.

`feature_node` and `comments` are the `document` and `comments`
produced by `Gherkin.Pickles.compile/2`.

# `hook`

```elixir
@spec hook(
  String.t(),
  Cucumber.Hooks.hook_type(),
  String.t() | nil,
  String.t() | nil,
  map()
) ::
  envelope()
```

Builds a `hook` envelope.

`type` is a `Cucumber.Hooks` hook kind; `tag` and `name` may be nil.

# `meta`

```elixir
@spec meta() :: envelope()
```

Builds the `meta` envelope describing this implementation and its host.

# `parameter_type`

```elixir
@spec parameter_type(String.t(), %{name: String.t(), regexp: Regex.t()}) :: envelope()
```

Builds a `parameterType` envelope from a custom parameter type definition.

See `Cucumber.ParameterTypes` for how custom types are defined.

# `pickle`

```elixir
@spec pickle(Gherkin.Pickle.t()) :: envelope()
```

Builds a `pickle` envelope from a `Gherkin.Pickle`.

# `source`

```elixir
@spec source(String.t(), String.t()) :: envelope()
```

Builds a `source` envelope carrying a feature file's raw text.

The media type follows the file extension: Markdown feature files
(`.feature.md`) get the MDG media type, everything else plain Gherkin.

# `step_definition`

```elixir
@spec step_definition(String.t(), Cucumber.StepRegistry.Definition.t()) :: envelope()
```

Builds a `stepDefinition` envelope from a `Cucumber.StepRegistry.Definition`.

The source reference points at the definition's file (relative to the
working directory) and line.

# `step_definition`

> This function is deprecated. Use step_definition/2 with a Cucumber.StepRegistry.Definition; removed in 2.0.

```elixir
@spec step_definition(
  String.t(),
  {:expression, String.t()} | {:regex, {String.t(), term()}},
  map()
) ::
  envelope()
```

Builds a `stepDefinition` envelope from a pattern key and a source reference.

`key` is `{:expression, source}` or `{:regex, {source, opts}}`, the step
registry key shape before `Cucumber.StepRegistry`; `source_reference` is a
`%{uri: ..., location: %{line: ...}}` map.

## Examples

    Cucumber.Messages.step_definition("9", {:expression, "I have {int} cukes"}, %{})

# `test_case`

```elixir
@spec test_case(String.t(), String.t(), [map()], String.t() | nil) :: envelope()
```

Builds a `testCase` envelope binding a pickle to its executable test steps.

Each entry in `test_steps` is either a pickle step
(`%{id: ..., pickleStepId: ..., stepDefinitionIds: [...]}`) or a
scenario-hook step (`%{id: ..., hookId: ...}`), in execution order.

# `test_case_finished`

```elixir
@spec test_case_finished(String.t(), instant(), boolean()) :: envelope()
```

Builds a `testCaseFinished` envelope.

`will_be_retried` marks a failed attempt that a retry follows.

# `test_case_started`

```elixir
@spec test_case_started(String.t(), String.t(), non_neg_integer(), instant()) ::
  envelope()
```

Builds a `testCaseStarted` envelope.

`attempt` is 0-based; retries increment it.

# `test_run_finished`

```elixir
@spec test_run_finished(boolean(), instant(), String.t() | nil) :: envelope()
```

Builds a `testRunFinished` envelope.

`success` is false when any scenario failed.

# `test_run_hook_finished`

```elixir
@spec test_run_hook_finished(String.t(), map(), instant()) :: envelope()
```

Builds a `testRunHookFinished` envelope.

# `test_run_hook_started`

```elixir
@spec test_run_hook_started(String.t(), String.t() | nil, String.t() | nil, instant()) ::
  envelope()
```

Builds a `testRunHookStarted` envelope.

Marks a `before_all`/`after_all` hook beginning execution.

# `test_run_started`

```elixir
@spec test_run_started(String.t(), instant()) :: envelope()
```

Builds a `testRunStarted` envelope.

# `test_step_finished`

```elixir
@spec test_step_finished(String.t(), String.t(), map(), instant()) :: envelope()
```

Builds a `testStepFinished` envelope.

Build `result` with `test_step_result/3`.

# `test_step_result`

```elixir
@spec test_step_result(status(), non_neg_integer(), String.t() | nil) :: map()
```

Builds a `TestStepResult` payload.

Used by `test_step_finished/4` and `test_run_hook_finished/3`.

# `test_step_started`

```elixir
@spec test_step_started(String.t(), String.t(), instant()) :: envelope()
```

Builds a `testStepStarted` envelope.

# `timestamp`

```elixir
@spec timestamp(integer()) :: instant()
```

Converts a `System.system_time(:nanosecond)` value into a timestamp.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
