README.md

## Deferred Config

Seamless runtime config with one line of code. In
your application's `start/2` method, call:

    DeferredConfig.populate(:otp_app_name)

And now you and users of your application or library
will be able to write config that is deferred to
runtime, like the following:

    config :otp_app_name,
      http: %{ # nested config is ok
        # common 'system tuple' pattern is fully supported
        port: {:system, "PORT", {String, :to_integer}}
      },
      # more general 'mfa tuple' pattern is also supported
      secret_key: {:apply, {MyKey, :fetch, ["arg"]}}

That's it.

- No 'mappings,' no special access methods -- just
  keep using `Application.get_env/2`.
- Works for arbitrarily nested config.
- Works just as well when run with mix as it does
  in releases built with `:distillery`, `:exrm`,
  or `:relx`.
- Lets library authors support the
  common "system tuples" pattern *effortlessly.*

**Why this library?**

See '[Rationale](#rationale)' for more detail. But
**TLDR:** `REPLACE_OS_VARS` is string-only and
release-only, and `{:system, ...}` support among
libraries is inconsistent and easy to get wrong in
ways that bite your users come release time -- in
other words, until now it's been a burden on
library authors. This library tries to make it
1 LOC to do the right thing.

There are other libraries to manage runtime config
(see list at end of readme) but using them is harder
as they add things -- like special config accessor functions,
and/or their own config files, or mappings, or DSLs. We don't
need to, because we rely on a `ReplacingWalk`
of an app's config during `Application.start/2`, and
the only DSL are configuration patterns sourced from
the community, like system and mfa tuples.


## Usage

In mix.exs,

    defp deps, do: [{:deferred_config, "~> 0.1.0"}]

Then, in your application startup, add the following line:

    defmodule Mine.Application do
      ...
      def start(_type, _args) do

        :mine |> DeferredConfig.populate(:app_name)

        ...
      end
    end

Where the app name is `:mine`.

Now, you and users of your app can configure
as follows, and it'll work -- regardless of if they're
running it from iex, or a release with env vars set:

      config :mine, 
      
        # string from env var, or `nil` if missing.
        port1: {:system, "PORT"},

        # string from env var |> integer; `nil` if missing.
        port2: {:system, "PORT", {String, :to_integer}},
        
        # string from env var, or "4000" as default.
        port3: {:system, "PORT", "4000"},
        
        # converts env var to integer, or 4000 as default.
        port4: {:system, "PORT", 4000, {String, :to_integer}}

## Features

**Accessing config does not change.** If you used
`Application.get_env(:mine, :port1)` before, that will
keep working.

Since you can use arbitrary transformation functions,
**you can do advanced transformations** if you need to:

    # lib/mine/ip.ex
    defmodule Mine.Ip do
      @doc ":inet uses `{0,0,0,0}` for ipv4 addrs"
      def str2ip(str) do
        case :inet_parse:address(str) do
          {:ok, ip = {_, _, _, _}} -> ip
          {:error, _}              -> nil
        end
      end
    end

    # config.exs 
    config :my_app,
      port: {:system, "MY_IP", {127,0,0,1}, {Mine.Ip, :str2ip}

If you need even more control -- say, the
source of your config isn't the system env, but a file
in a directory, which is more secure in some use
cases -- you can use the deferred MFA (module, function,
arguments) form:

    config :mine,
      api_key: {:apply, {File, :read!, ["k.txt"]}}

**Nested and arbitrary config** should work.

**Can be extended** to recognize and transform
other kinds of config as well (`DeferredConfig.populate/2`),
ie if there's a pattern like 'system tuples' that you
wanted to support, and `{:apply, mfa}` was bad UX.

If you have another use case that this doesn't cover, 
please file an issue or reach out to github.com/mrluc

### Limitations

Note that this only applies to **one OTP app's config.**
We can't (and shouldn't try to) monkey-patch every app's 
config; they all start up at different times.

This limitation applies to all approaches to runtime
config except `REPLACE_OS_VARS`.


## Rationale

Mix configs don't always work like users would
like when they build releases, whether with relx, exrm, 
distillery, or something else.

There are 3 approaches we'll look at to identify pain points:

1. `REPLACE_OS_VARS` for releases
2. `{:system, ...}` tuples for deferred config
3. Other runtime config libraries


### 1) REPLACE_OS_VARS is for releases only

The best-supported method
of injecting run-time configuration -- running the release
with `REPLACE_OS_VARS` or `RELX_REPLACE_OS_VARS`, supported 
by `distillery`, `relx` and `exrm` -- will result in 
config like the following:

    config :my_app, field: "${SOME_VAR}"

That works in **all your config, for all apps you configure**,
even if the app doesn't do anything particular to support it.

Drawbacks of `REPLACE_OS_VARS`
  
  - It only works when running a release.
    Otherwise, your `DB_URL` will literally be `"${DB_URL}"`.
  - It only gives you string values. Some libs will require
    that eg `PORT` be a number.

Neither is a show-stopper *by any means*, but it's
a small complication ... shared users of thousands of
libraries.
  
### 2) `{:system, ...}` tuples have inconsistent support
  
Apps that want to allow
run-time configuration from Mix configs (which you could
argue is 'all of them') should be configurable
with lazy values, which can be filled **on startup of 
that application, before they are used**.

What should those lazy values look like? Many libraries have 
settled on so-called 'system tuples', like:

    config :someapp, 
      field: {:system, "ENV_VAR_NAME", "default value"}

**The downside**: that approach requires every
library author to recognize and support that kind
of tuple. 

Some big libraries do! However, it can be a pain to add 
support for that kind of config consistently, converting 
data types appropriately, for all configurable options in 
your app. (A small pain, spread over many libraries).
This library automates that pattern.


### 3) Other runtime config libs use special config files and/or access methods

There are many other libs for config, most of which also
deal with runtime config:

- [:confex](https://hexdocs.pm/confex)
- [:flasked](https://hexdocs.pm/flasked)
- [:env_config](https://hexdocs.pm/env_config)
- [:config_ext](https://hexdocs.pm/config_ext)
- [libex_config](https://hex.pm/packages/libex_config)
- [:configparser](https://hexdocs.pm/configparser_ex)
- [:config](https://hexdocs.pm/config)
- [:spellbook](https://hex.pm/packages/spellbook)

They solve a wide variety of config-related problems.

However, these **all** introduce their own methods for
accessing Application config, and other complexity
as well (mappings, config files, etc).

We avoid that, by doing a replacing walk on the
app's config at startup.


## 'When should I REPLACE_OS_VARS?'

Always, but not always because of app config!

For injecting config, it has the limitations
mentioned above in 'Rationale.'

- For config: **if** you need to configure many libraries that don't
support deferred config, **and** what you want to configure
can be a string (`DB_URL`, for instance) ... in
that case, maybe a release-only config is a good option.

But you should probably use `REPLACE_OS_VARS`
(or `RELX_REPLACE_OS_VARS`), because it **also**
allows **interpolation in `vm.args`**.

- That lets you drive node short/longnames in releases with env vars.
Which can be important when eg you don't know the node
IP for a release at compile-time.
- It's nice that the same approach for vm.args templating
works across release builders.