# `ExQuality.Report`
[🔗](https://github.com/riddler/ex_quality/blob/v0.15.0/lib/ex_quality/report.ex#L1)

Builds the machine-readable form of a run.

`mix quality` exits 0 or 1 for the whole run, which tells a caller that
something failed but not what. A script that wants to route on the result -
hand the credo findings to one fixer, the test failures to another - would
otherwise have to scrape the console or re-run the tools.

A report answers "which stages ran, which failed, and what did they find"
from the same results the human output is rendered from, so the two streams
can never disagree.

## Shape

    {
      "version": "0.6.0",
      "status": "error",
      "duration_ms": 48213,
      "profile": "loop",
      "scope": "changed",
      "base_ref": "origin/main",
      "stages": [
        {
          "name": "Credo",
          "status": "error",
          "summary": "5 issues (2 readability, 3 design)",
          "duration_ms": 412,
          "stats": {"issue_count": 5},
          "findings": [
            {
              "file": "lib/user.ex", "line": 42, "column": 3,
              "app": "web", "severity": "info",
              "check": "Credo.Check.Readability.ModuleDoc",
              "message": "Modules should have a @moduledoc tag."
            }
          ]
        },
        {
          "name": "Dialyzer", "status": "skipped",
          "summary": "--quick", "skip_kind": "run",
          "duration_ms": 0, "stats": {}, "findings": []
        }
      ]
    }

Every stage carries the same keys whatever its status, so a consumer reads
one field for the explanation rather than branching: a skipped stage puts its
reason in `summary`, exactly as `ExQuality.Stage.skipped/3` records it.

`skip_kind` is `null` unless the stage was skipped, `"run"` when the skip
names this run (a full `mix quality` closes it) and `"project"` when it
names the project (a fuller run cannot close it). It is what lets a consumer
such as `mix quality.verify` tell the two apart without parsing the reason's
prose. A skipped stage that carries no kind - a custom stage that has not
opted in - reports `"project"`, the conservative direction. See
`t:ExQuality.Stage.skip_kind/0`.

A stage that failed without producing findings carries its tool's full output
under `output` instead, mirroring the human renderer. Findings are the parsed
form; `output` is the fallback, and one of the two is always present for a
failure.

## How much the run covered

`profile`, `scope` and `base_ref` are always present at the root, `null` when
they do not apply. They are what makes `"status": "ok"` interpretable: a green
run scoped to three test files is not the same claim as a green full run, and a
caller that lowers a recorded coverage figure or moves a baseline on a green
run has to be able to refuse the narrow one. The test stage repeats them, plus
the files it ran, in its own object. See `ExQuality.Scope`.

# `t`

```elixir
@type t() :: %{
  version: String.t(),
  status: String.t(),
  duration_ms: non_neg_integer(),
  profile: String.t() | nil,
  scope: String.t() | nil,
  base_ref: String.t() | nil,
  stages: [map()]
}
```

# `build`

```elixir
@spec build([ExQuality.Stage.result()], non_neg_integer(), keyword()) :: t()
```

Builds a report from the results of a run.

`duration_ms` is the wall clock time of the whole run, which is not the sum
of the stage durations because the analysis stages run in parallel.

`config` is the run's loaded config, used for the root `profile` and for the
scope when the test stage did not run to report one of its own.

    iex> alias ExQuality.{Report, Stage}
    iex> report = Report.build([Stage.skipped("Dialyzer", "--quick")], 12)
    iex> {report.status, report.duration_ms, length(report.stages)}
    {"ok", 12, 1}

    iex> alias ExQuality.{Report, Stage}
    iex> report = Report.build([Stage.skipped("Tests", "--skip test")], 12, profile: :loop)
    iex> {report.profile, report.scope}
    {"loop", "all"}

# `encode!`

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

Encodes a report as pretty-printed JSON, newline terminated.

    iex> ExQuality.Report.encode!(%{status: "ok"})
    "{\n  \"status\": \"ok\"\n}\n"

---

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