# `Macaw.Host.Config`
[🔗](https://github.com/dcoai/macaw_host/blob/v0.1.4/lib/macaw/host/config.ex#L1)

An app's configuration, from the literal `config.exs` on its data volume.

Each setting resolves as **env var > `config.exs` > default**. An app
declares its settings once, as `{name, type, default}`
(`Macaw.Host.Config.Types`), and loads them at boot:

    @settings [
      {:public_host, :string, "localhost"},
      {:port, :port, 4000},
      {:smtp_password, :secret, nil}
    ]

    Macaw.Host.Config.load!(:my_app, @settings,
      path: "/data/config.exs", env_prefix: "MY_APP")

    Macaw.Host.Config.get(:my_app, :port)

`MY_APP_PORT` wins over `port` in the file, which wins over `4000`. Every
invalid value and every unknown key in the file fails the load **at
once**, each named with where it came from, so a broken boot says
everything that is wrong in one message. The resolved values, with each
one's source, are kept in `:persistent_term` and read without copying.

`put!/4` and `delete!/3` edit the file for the `<app>-ctl config` command:
the value is parsed as a literal, checked against the setting's type, and
the file is written atomically; nothing is evaluated.

# `resolved`

```elixir
@type resolved() :: %{required(atom()) =&gt; %{value: term(), source: source()}}
```

# `source`

```elixir
@type source() :: :env | :file | :default
```

# `spec`

```elixir
@type spec() :: {atom(), Macaw.Host.Config.Types.t(), term()}
```

# `delete!`

```elixir
@spec delete!(Path.t(), [spec()], String.t()) :: :ok | {:error, String.t()}
```

Removes `name` from the file, so it falls back to its env var or default.

# `env_var`

```elixir
@spec env_var(String.t(), atom()) :: String.t()
```

The env var a setting is read from: `PREFIX_NAME`.

# `get`

```elixir
@spec get(atom(), atom()) :: term()
```

A loaded setting's value. Raises for a name the app didn't declare.

# `keys`

```elixir
@spec keys([spec()]) :: [atom()]
```

The settings' names, in declared order.

# `load!`

```elixir
@spec load!(atom(), [spec()], keyword()) :: resolved()
```

Reads the file and the environment, resolves, and stores the result under
`app`. Options: `:path` (the file), `:env_prefix`, `:env` (default
`System.get_env/0`). Raises `ArgumentError` listing every fault.

# `mask`

```elixir
@spec mask(Macaw.Host.Config.Types.t(), term()) :: String.t()
```

A value as shown; anything but a secret is inspected. A secret is
`unset` when nil or empty, `set (…last4)` when it is at least 16
characters long (so at most a quarter of it shows), else `set`. The
generated script's `Ctl.Secret.mask/1` follows the same rule.

# `put!`

```elixir
@spec put!(Path.t(), [spec()], String.t(), String.t()) ::
  {:ok, term()} | {:error, String.t()}
```

Sets `name` in the file at `path` to the literal `text`, after checking it
against the setting's type. `{:ok, value}` or `{:error, why}`; the file is
untouched on error.

# `read_file`

```elixir
@spec read_file(Path.t()) :: {:ok, map()} | {:error, String.t()}
```

The file at `path` as a map, never raising: `{:ok, map}` (`%{}` when it
doesn't exist) or `{:error, message}`, every fault on one line, for the
ctl, whose failure is one line.

# `resolve`

```elixir
@spec resolve([spec()], %{required(String.t()) =&gt; String.t()}, map(), String.t()) ::
  {:ok, resolved()} | {:error, [String.t()]}
```

Resolves settings from an env map, a file map and the env var prefix.
Pure: `{:ok, resolved}` or `{:error, faults}`.

# `resolve_setting`

```elixir
@spec resolve_setting(
  spec(),
  %{required(String.t()) =&gt; String.t()},
  map(),
  String.t()
) ::
  {:ok, %{value: term(), source: source()}}
  | {:error, %{raw: term(), source: source(), fault: String.t()}}
```

Resolves one setting as `resolve/4` does, env var > file > default, the
file's key an atom or a string: `{:ok, %{value, source}}`, or
`{:error, %{raw, source, fault}}` with the value as found (the env var's
string, the file's literal) and the fault as `resolve/4` words it.
`resolve/4` is built on it, and so is `<app>-ctl config list`, so the
two never disagree.

# `show`

```elixir
@spec show(atom()) :: [{atom(), String.t(), source()}]
```

Every loaded setting as `{name, shown value, source}`, secrets masked.

# `source`

```elixir
@spec source(atom(), atom()) :: source()
```

Where a loaded setting's value came from: `:env`, `:file` or `:default`.

---

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