diff --git a/src/hocon_tconf.erl b/src/hocon_tconf.erl index 9b75fef..816af17 100644 --- a/src/hocon_tconf.erl +++ b/src/hocon_tconf.erl @@ -423,8 +423,8 @@ map_fields([{_, FieldSchema} = Field | Fields], Conf0, Acc, Opts) -> map_fields_cont([{_, FieldSchema} = Field | Fields], Conf0, Acc, Opts) -> FieldType = field_schema(FieldSchema, type), - [FieldName | Aliases] = name_and_aliases(Field), - FieldValue = get_field_value(Opts, [FieldName | Aliases], Conf0), + [FieldName | Aliases] = NameAliases = name_and_aliases(Field), + FieldValue = get_field_value(Opts, NameAliases, Conf0), NewOpts = push_stack(Opts, FieldName), {FAcc, FValue} = try @@ -1047,12 +1047,18 @@ mkrich(Map, Box) when is_map(Map) -> mkrich(Val, Box) -> boxit(Val, Box). -get_field_value(_, [], _Conf) -> - undefined; -get_field_value(Opts, [Path | Rest], Conf) -> +get_field_value(Opts, NameAliases, Conf) -> + get_field_value(Opts, NameAliases, Conf, #{}). + +get_field_value(_, [], _Conf, Acc) when Acc =:= #{} -> undefined; +get_field_value(_, [], _Conf, Acc) -> + Acc; +get_field_value(Opts, [Path | Rest], Conf, Acc) -> case do_get_field_value(Opts, Path, Conf) of undefined -> - get_field_value(Opts, Rest, Conf); + get_field_value(Opts, Rest, Conf, Acc); + Value when is_map(Value) -> + get_field_value(Opts, Rest, Conf, hocon_maps:deep_merge(Value, Acc)); Value -> Value end. diff --git a/test/hocon_schema_aliases_tests.erl b/test/hocon_schema_aliases_tests.erl index 34ec6a1..7ffdb81 100644 --- a/test/hocon_schema_aliases_tests.erl +++ b/test/hocon_schema_aliases_tests.erl @@ -40,15 +40,21 @@ roots() -> ]. fields("root1") -> - [{key1, integer()}]; + [ + {key1, hoconsc:mk(integer(), #{required => false})}, + {key2, hoconsc:mk(integer(), #{required => false})} + ]; fields("root2") -> [ {key2, #{aliases => [old_key2], type => integer()}}, - {key3, string()} + {key3, string()}, + {key4, #{ + aliases => [old_key4], type => integer(), converter => fun incr/2, required => false + }} ]. %% test a root field can be safely renamed -%% in this case, one of the root level fieds in the test schema ?MODULE. +%% in this case, one of the root level fields in the test schema ?MODULE. %% old_root1 is renamed to root1 check_root_test() -> ConfText = "{old_root1 = {key1 = 1}, root2 = {key2 = 2, key3 = \"foo\"}}", @@ -59,6 +65,40 @@ check_root_test() -> <<"root2">> => #{<<"key2">> => 2, <<"key3">> => "foo"} }, hocon_tconf:check_plain(?MODULE, Conf) + ), + {ok, RichConf} = hocon:binary(ConfText, #{format => richmap}), + ?assertEqual( + #{ + <<"old_root1">> => #{<<"key1">> => 1}, + <<"root1">> => #{<<"key1">> => 1}, + <<"root2">> => #{<<"key2">> => 2, <<"key3">> => "foo"} + }, + hocon_util:richmap_to_map(hocon_tconf:check(?MODULE, RichConf)) + ). + +check_converter_test() -> + ConfText = "{old_root1 = {key1 = 1}, root2 = {key2 = 2, key3 = \"foo\", old_key4 = 3}}", + {ok, Conf} = hocon:binary(ConfText), + ?assertEqual( + #{ + <<"root1">> => #{<<"key1">> => 1}, + <<"root2">> => #{<<"key2">> => 2, <<"key3">> => "foo", <<"key4">> => 4} + }, + hocon_tconf:check_plain(?MODULE, Conf) + ), + {ok, RichConf} = hocon:binary(ConfText, #{format => richmap}), + ?assertEqual( + #{ + <<"old_root1">> => #{<<"key1">> => 1}, + <<"root1">> => #{<<"key1">> => 1}, + <<"root2">> => #{ + <<"key2">> => 2, + <<"key3">> => "foo", + <<"old_key4">> => 3, + <<"key4">> => 4 + } + }, + hocon_util:richmap_to_map(hocon_tconf:check(?MODULE, RichConf)) ). check_field_test() -> @@ -72,6 +112,18 @@ check_field_test() -> <<"root2">> => #{<<"key2">> => 2, <<"key3">> => "foo"} }, hocon_tconf:check_plain(?MODULE, Conf) + ), + {ok, RichConf} = hocon:binary(ConfText, #{format => richmap}), + ?assertEqual( + #{ + <<"old_root1">> => #{<<"key1">> => 1}, + <<"root1">> => #{<<"key1">> => 1}, + <<"root2">> => #{<<"key2">> => 2, <<"key3">> => "foo", <<"old_key2">> => 2} + %% deprecated field(old_root3,root3) is not included in the map + %%<<"old_root3">> => a, + %% <<"root3">> => b + }, + hocon_util:richmap_to_map(hocon_tconf:check(?MODULE, RichConf)) ). check_env_test() -> @@ -83,12 +135,74 @@ check_env_test() -> ?assertEqual( #{ <<"root1">> => #{<<"key1">> => 42}, - <<"root2">> => #{<<"key2">> => 43, <<"key3">> => "foo"} + <<"root2">> => #{<<"key2">> => 43, <<"key3">> => "foo", <<"key4">> => 2} }, hocon_tconf:check_plain(?MODULE, Conf) + ), + {ok, RichConf} = hocon:binary(ConfText, #{format => richmap}), + Conf1 = hocon_tconf:merge_env_overrides(?MODULE, RichConf, all, #{format => richmap}), + ?assertEqual( + #{ + <<"root1">> => #{<<"key1">> => 42}, + <<"old_root1">> => #{<<"key1">> => 42}, + <<"root2">> => #{ + <<"key2">> => 43, + <<"old_key2">> => 43, + <<"key3">> => "foo", + <<"key4">> => 2, + <<"old_key4">> => 1 + } + }, + hocon_util:richmap_to_map(hocon_tconf:check(?MODULE, Conf1)) ) end, - with_envs(Fun, [], envs([{"EMQX_OLD_ROOT1__key1", "42"}, {"EMQX_ROOT2__OLD_KEY2", "43"}])). + with_envs( + Fun, + [], + envs([ + {"EMQX_OLD_ROOT1__key1", "42"}, + {"EMQX_ROOT2__OLD_KEY2", "43"}, + {"EMQX_ROOT2__OLD_KEY4", "1"} + ]) + ). + +check_mix_env_test() -> + Fun = + fun() -> + ConfText = "{old_root1 = {key1 = 0, key2 = 1}}", + {ok, Conf0} = hocon:binary(ConfText), + Conf = hocon_tconf:merge_env_overrides(?MODULE, Conf0, all, #{format => map}), + ?assertEqual( + #{ + <<"root1">> => #{<<"key1">> => 0, <<"key2">> => 2}, + <<"root2">> => #{<<"key2">> => 42, <<"key3">> => "foo"} + }, + hocon_tconf:check_plain(?MODULE, Conf) + ), + {ok, RichConf} = hocon:binary(ConfText, #{format => richmap}), + Conf1 = hocon_tconf:merge_env_overrides(?MODULE, RichConf, all, #{format => richmap}), + ?assertEqual( + #{ + <<"root1">> => #{<<"key1">> => 0, <<"key2">> => 2}, + <<"old_root1">> => #{<<"key1">> => 0, <<"key2">> => 1}, + <<"root2">> => #{ + <<"key2">> => 42, + <<"old_key2">> => 42, + <<"key3">> => "foo" + } + }, + hocon_util:richmap_to_map(hocon_tconf:check(?MODULE, Conf1)) + ) + end, + with_envs( + Fun, + [], + envs([ + {"EMQX_ROOT1__KEY2", "2"}, + {"EMQX_ROOT2__OLD_KEY2", "42"}, + {"EMQX_ROOT2__KEY3", "foo"} + ]) + ). no_value_test() -> ConfText = "{root3 = b, old_root3 = a}", @@ -100,3 +214,6 @@ with_envs(Fun, Args, Envs) -> envs(Envs) -> [{"HOCON_ENV_OVERRIDE_PREFIX", "EMQX_"} | Envs]. + +incr(undefined, _Opts) -> undefined; +incr(Val, _Opts) -> Val + 1. diff --git a/test/hocon_tconf_tests.erl b/test/hocon_tconf_tests.erl index 674da8d..dc98138 100644 --- a/test/hocon_tconf_tests.erl +++ b/test/hocon_tconf_tests.erl @@ -825,7 +825,7 @@ type_stack_cannot_concatenate_test() -> #{ path := "f1.1.maybe", matched_type := "s1/s2", - errors := [_ | _] + reason := required_field }, hocon_tconf:check_plain(Sc, #{<<"f1">> => [#{<<"maybe">> => #{}}]}, #{}) ),