src/conf_env.erl

%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <xramtsov@gmail.com>
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%%     http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
%%%-------------------------------------------------------------------
-module(conf_env).

%% API
-export([load/2]).
-export([file/0]).
-export([callback_module/1]).
-export([on_fail/0]).
-export([format_error/1]).
-export_type([error_reason/0]).

-type error_reason() :: {undefined_env, atom()} |
                        {invalid_env, atom(), term()}.

%%%===================================================================
%%% API
%%%===================================================================
-spec load(conf:apps_config(), boolean()) -> ok | {error, _}.
load(Config, Reload) ->
    case Reload of
        false ->
            set_env(Config);
        true ->
            OldConfig = application_controller:prep_config_change(),
            set_env(Config),
            application_controller:config_change(OldConfig)
    end.

-spec file() -> {ok, binary()} | {error, error_reason()}.
file() ->
    case application:get_env(conf, file) of
        {ok, Path0} ->
            try unicode:characters_to_binary(Path0) of
                Path when is_binary(Path), Path /= <<>> ->
                    {ok, Path};
                _ ->
                    {error, {invalid_env, file, Path0}}
            catch _:_ ->
                    {error, {invalid_env, file, Path0}}
            end;
        undefined ->
            {error, {undefined_env, file}}
    end.

-spec on_fail() -> stop | crash | halt.
on_fail() ->
    case application:get_env(conf, on_fail, stop) of
        stop -> stop;
        crash -> crash;
        _ -> halt
    end.

-spec callback_module(atom()) -> {ok, module()} | {error, error_reason()}.
callback_module(App) ->
    Env = callback_yaml,
    case application:get_env(conf, Env) of
        {ok, AppMods} when is_list(AppMods) ->
            case lists:keyfind(App, 1, AppMods) of
                {_, Mod} when is_atom(Mod) ->
                    {ok, Mod};
                false ->
                    {error, {undefined_env, Env}};
                _ ->
                    {error, {invalid_env, Env, AppMods}}
            end;
        {ok, Junk} ->
            {error, {invalid_env, Env, Junk}};
        undefined ->
            {error, {undefined_env, Env}}
    end.

-spec format_error(error_reason()) -> string().
format_error({undefined_env, Env}) ->
    "Erlang environment variable '" ++ atom_to_list(Env) ++ "' is not set";
format_error({invalid_env, Env, Val}) ->
    lists:flatten(
      io_lib:format(
        "Invalid value of Erlang environment variable '~s': ~p",
        [Env, Val])).

%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec set_env(conf:apps_config()) -> ok.
-ifdef(old_set_env). % Erlang/OTP < 21.3.
set_env(Config) ->
    lists:foreach(
      fun({App, Opts}) when is_map(Opts) ->
              maps:fold(
                fun(Par, Val, ok) ->
                        application:set_env(App, Par, Val, [{persistent, true}])
                end, ok, Opts);
         ({App, Opts}) when is_list(Opts) ->
              lists:foreach(
                fun({Par, Val}) ->
                        application:set_env(App, Par, Val, [{persistent, true}])
                end, Opts)
      end, Config).
-else.
set_env(Config) ->
    NewConfig = lists:map(
                  fun({App, Opts}) when is_map(Opts) ->
                          {App, maps:to_list(Opts)};
                     ({App, Opts}) when is_list(Opts) ->
                          {App, Opts}
                  end, Config),
    application:set_env(NewConfig, [{persistent, true}]).
-endif.