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

Project-defined stages, declared in `.quality.exs` under `custom:`.

A project with a house check, a schema linter, a custom mix task or a shell
script gate could have ExQuality's parallelism, timing, report and printer,
or it could have its own check, but not both. Custom stages are the way to
have both, and a check that runs inside `mix quality` is a check the JSON
report can route on.

Two layers, one mechanism:

    custom: [
      # A command. This is the case most projects want.
      [
        key: :nullability,
        name: "Nullability",
        command: "mix",
        args: ["schema.nullability", "--format", "json"],
        env: [{"MIX_ENV", "test"}],
        kind: :reader
      ],

      # A module, for anything the command form cannot express.
      [key: :house_rules, name: "House rules", module: MyApp.Quality.HouseRules]
    ]

Every entry is a keyword list with `key` and `name`, plus either `module` or
`command`. Registration data lives in the entry rather than in callbacks on a
module so that the config file alone says what a run will contain: a stage
that only announces itself once it has run cannot be reported as skipped, and
a run that says nothing about a stage reads as a run where the stage passed.

## Module entries

`module:` names a module exporting `run/1` and returning an
`ExQuality.Stage.result()`, which is the contract every built-in stage
already satisfies. It may also export `stage_kind/1`; a module that does not
is a `:reader`. See `ExQuality.Stage`.

## Command entries

`command:` is run by `ExQuality.Stages.Command`, which documents the options
and the JSON finding contract.

## What custom stages are not for

Filling gaps in built-in stages. Routing a second `mix credo` config through
a custom command would work and would be a mistake: the output comes back as
text instead of per-check findings, and per-check routing is the reason the
JSON report exists. If a built-in stage cannot express something its tool
supports, that is a bug in the stage.

# `kind`

```elixir
@spec kind(keyword(), keyword()) :: ExQuality.Stage.kind()
```

Returns whether an entry reads the build or writes to it.

An entry that declares `kind:` is taken at its word. Otherwise a module entry
is asked, via `ExQuality.Stage.kind/2`, and a command entry is a `:reader`.

    iex> ExQuality.Custom.kind([key: :a, name: "A", command: "true"], [])
    :reader

# `runner`

```elixir
@spec runner(keyword()) :: (keyword() -&gt; ExQuality.Stage.result())
```

Returns the function that runs an entry, given the run's config.

# `skip`

```elixir
@spec skip(keyword(), keyword()) :: {String.t(), ExQuality.Stage.skip_kind()} | nil
```

Returns why a custom stage will not run and what kind of skip that is, or
`nil` when it will run. The kinds are `ExQuality.Config.skip/2`'s: a switch
is a `:run` skip, an `enabled: false` is a `:project` one.

# `skip_reason`

```elixir
@spec skip_reason(keyword(), keyword()) :: String.t() | nil
```

Returns why a custom stage will not run, or `nil` when it will.

`--skip <key>` is read through `ExQuality.Config.skip_reason/2`, exactly as a
built-in stage's switch is. `enabled: false` in the entry itself is the
config-file spelling.

# `stages`

```elixir
@spec stages(keyword()) :: [keyword()]
```

Returns the custom stage entries in a loaded config, in declaration order.

    iex> ExQuality.Custom.stages([])
    []

# `validate!`

```elixir
@spec validate!(keyword()) :: :ok
```

Raises unless every custom entry in `config` is well formed.

Custom stages are the place a config file can be wrong in ways that silently
weaken a run - a stage that never registers is a check nobody is told is not
running - so this fails the run at load time and names the offending entry
rather than letting it go quiet.

---

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