defmodule Hush do
@moduledoc """
Extensible runtime configuration loader with pluggable providers
"""
@doc """
Resolve configuration
When called with resolve!/0 it will default to all loaded applications' configuration.
When called with resolve!/1 with the configuration as an argument it will process that.
"""
@spec resolve!() :: Keyword.t()
def resolve!() do
runtime_config() |> resolve!()
end
@spec resolve!(Keyword.t(), Keyword.t()) :: Keyword.t()
def resolve!(config, options \\ Keyword.new())
def resolve!(config, options) when is_list(config) do
{:ok, pid} =
config
|> load_providers!()
|> child_specs()
|> Supervisor.start_link(strategy: :one_for_one)
options = options || Application.get_all_env(:hush)
resolved = config |> Enum.map(&resolve!(&1, options))
Supervisor.stop(pid, :normal)
resolved
end
@spec resolve!({atom(), Keyword.t()}, Keyword.t()) :: {atom(), Keyword.t()}
def resolve!({app, config}, options) do
with config <- Hush.Resolver.resolve!(config, options),
:ok <- Application.put_all_env([{app, config}]) do
{app, config}
end
end
@doc """
Is the app running in release mode?
"""
@spec release_mode?() :: boolean()
def release_mode? do
!function_exported?(Mix, :env, 0)
end
@spec load_providers!(Keyword.t()) :: Keyword.t()
defp load_providers!(config) do
for provider <- providers(config) do
case provider.load(config) do
{:error, message} ->
raise ArgumentError, "Could not load provider #{provider}: #{message}"
result ->
result
end
end
end
@spec providers(Keyword.t()) :: Keyword.t()
defp providers(config) do
config
|> Keyword.get(:hush, providers: [])
|> Keyword.get(:providers, [])
end
@spec runtime_config() :: Keyword.t()
defp runtime_config() do
for {app, _, _} <- Application.loaded_applications() do
{app, Application.get_all_env(app)}
end
end
defp child_specs(providers), do: Enum.reduce(providers, [], &child_specs_reducer/2)
defp child_specs_reducer({:ok, children}, acc), do: acc ++ children
defp child_specs_reducer(_, acc), do: acc
end