| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- %%--------------------------------------------------------------------
- %% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
- %%
- %% 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(emqx_map_lib).
- -export([ deep_get/2
- , deep_get/3
- , deep_find/2
- , deep_put/3
- , deep_remove/2
- , deep_merge/2
- , safe_atom_key_map/1
- , unsafe_atom_key_map/1
- , jsonable_map/1
- , jsonable_map/2
- , binary_string/1
- , deep_convert/3
- , diff_maps/2
- , merge_with/3
- ]).
- -export_type([config_key/0, config_key_path/0]).
- -type config_key() :: atom() | binary() | [byte()].
- -type config_key_path() :: [config_key()].
- -type convert_fun() :: fun((...) -> {K1::any(), V1::any()} | drop).
- %%-----------------------------------------------------------------
- -spec deep_get(config_key_path(), map()) -> term().
- deep_get(ConfKeyPath, Map) ->
- Ref = make_ref(),
- Res = deep_get(ConfKeyPath, Map, Ref),
- case Res =:= Ref of
- true -> error({config_not_found, ConfKeyPath});
- false -> Res
- end.
- -spec deep_get(config_key_path(), map(), term()) -> term().
- deep_get(ConfKeyPath, Map, Default) ->
- case deep_find(ConfKeyPath, Map) of
- {not_found, _KeyPath, _Data} -> Default;
- {ok, Data} -> Data
- end.
- -spec deep_find(config_key_path(), map()) ->
- {ok, term()} | {not_found, config_key_path(), term()}.
- deep_find([], Map) ->
- {ok, Map};
- deep_find([Key | KeyPath] = Path, Map) when is_map(Map) ->
- case maps:find(Key, Map) of
- {ok, SubMap} -> deep_find(KeyPath, SubMap);
- error -> {not_found, Path, Map}
- end;
- deep_find(KeyPath, Data) ->
- {not_found, KeyPath, Data}.
- -spec deep_put(config_key_path(), map(), term()) -> map().
- deep_put([], _Map, Data) ->
- Data;
- deep_put([Key | KeyPath], Map, Data) ->
- SubMap = maps:get(Key, Map, #{}),
- Map#{Key => deep_put(KeyPath, SubMap, Data)}.
- -spec deep_remove(config_key_path(), map()) -> map().
- deep_remove([], Map) ->
- Map;
- deep_remove([Key], Map) ->
- maps:remove(Key, Map);
- deep_remove([Key | KeyPath], Map) ->
- case maps:find(Key, Map) of
- {ok, SubMap} when is_map(SubMap) ->
- Map#{Key => deep_remove(KeyPath, SubMap)};
- {ok, _Val} -> Map;
- error -> Map
- end.
- %% #{a => #{b => 3, c => 2}, d => 4}
- %% = deep_merge(#{a => #{b => 1, c => 2}, d => 4}, #{a => #{b => 3}}).
- -spec deep_merge(map(), map()) -> map().
- deep_merge(BaseMap, NewMap) ->
- NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap),
- MergedBase = maps:fold(fun(K, V, Acc) ->
- case maps:find(K, NewMap) of
- error ->
- Acc#{K => V};
- {ok, NewV} when is_map(V), is_map(NewV) ->
- Acc#{K => deep_merge(V, NewV)};
- {ok, NewV} ->
- Acc#{K => NewV}
- end
- end, #{}, BaseMap),
- maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
- -spec deep_convert(map(), convert_fun(), Args::list()) -> map().
- deep_convert(Map, ConvFun, Args) when is_map(Map) ->
- maps:fold(fun(K, V, Acc) ->
- case apply(ConvFun, [K, deep_convert(V, ConvFun, Args) | Args]) of
- drop -> Acc;
- {K1, V1} -> Acc#{K1 => V1}
- end
- end, #{}, Map);
- deep_convert(ListV, ConvFun, Args) when is_list(ListV) ->
- [deep_convert(V, ConvFun, Args) || V <- ListV];
- deep_convert(Val, _, _Args) -> Val.
- -spec unsafe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
- unsafe_atom_key_map(Map) ->
- covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
- -spec safe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
- safe_atom_key_map(Map) ->
- covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
- -spec jsonable_map(map() | list()) -> map() | list().
- jsonable_map(Map) ->
- jsonable_map(Map, fun(K, V) -> {K, V} end).
- jsonable_map(Map, JsonableFun) ->
- deep_convert(Map, fun binary_string_kv/3, [JsonableFun]).
- -spec diff_maps(map(), map()) ->
- #{added := map(), identical := map(), removed := map(),
- changed := #{any() => {OldValue::any(), NewValue::any()}}}.
- diff_maps(NewMap, OldMap) ->
- InitR = #{identical => #{}, changed => #{}, removed => #{}},
- {Result, RemInNew} =
- lists:foldl(fun({OldK, OldV}, {Result0 = #{identical := I, changed := U, removed := D},
- RemNewMap}) ->
- Result1 = case maps:find(OldK, NewMap) of
- error ->
- Result0#{removed => D#{OldK => OldV}};
- {ok, NewV} when NewV == OldV ->
- Result0#{identical => I#{OldK => OldV}};
- {ok, NewV} ->
- Result0#{changed => U#{OldK => {OldV, NewV}}}
- end,
- {Result1, maps:remove(OldK, RemNewMap)}
- end, {InitR, NewMap}, maps:to_list(OldMap)),
- Result#{added => RemInNew}.
- binary_string_kv(K, V, JsonableFun) ->
- case JsonableFun(K, V) of
- drop -> drop;
- {K1, V1} -> {binary_string(K1), V1}
- end.
- binary_string([]) -> [];
- binary_string(Val) when is_list(Val) ->
- case io_lib:printable_unicode_list(Val) of
- true -> unicode:characters_to_binary(Val);
- false -> [binary_string(V) || V <- Val]
- end;
- binary_string(Val) ->
- Val.
- %%---------------------------------------------------------------------------
- covert_keys_to_atom(BinKeyMap, Conv) ->
- deep_convert(BinKeyMap, fun
- (K, V) when is_atom(K) -> {K, V};
- (K, V) when is_binary(K) -> {Conv(K), V}
- end, []).
- %% copy from maps.erl OTP24.0
- -compile({inline, [error_with_info/2]}).
- merge_with(Combiner, Map1, Map2) when is_map(Map1),
- is_map(Map2),
- is_function(Combiner, 3) ->
- case map_size(Map1) > map_size(Map2) of
- true ->
- Iterator = maps:iterator(Map2),
- merge_with_t(maps:next(Iterator),
- Map1,
- Map2,
- Combiner);
- false ->
- Iterator = maps:iterator(Map1),
- merge_with_t(maps:next(Iterator),
- Map2,
- Map1,
- fun(K, V1, V2) -> Combiner(K, V2, V1) end)
- end;
- merge_with(Combiner, Map1, Map2) ->
- error_with_info(error_type_merge_intersect(Map1, Map2, Combiner),
- [Combiner, Map1, Map2]).
- merge_with_t({K, V2, Iterator}, Map1, Map2, Combiner) ->
- case Map1 of
- #{ K := V1 } ->
- NewMap1 = Map1#{ K := Combiner(K, V1, V2) },
- merge_with_t(maps:next(Iterator), NewMap1, Map2, Combiner);
- #{ } ->
- merge_with_t(maps:next(Iterator), maps:put(K, V2, Map1), Map2, Combiner)
- end;
- merge_with_t(none, Result, _, _) ->
- Result.
- error_type_merge_intersect(M1, M2, Combiner) when is_function(Combiner, 3) ->
- error_type_two_maps(M1, M2);
- error_type_merge_intersect(_M1, _M2, _Combiner) ->
- badarg.
- error_with_info(_, _) ->
- {error_info, #{module => erl_stdlib_errors}}.
- error_type_two_maps(M1, M2) when is_map(M1) ->
- {badmap, M2};
- error_type_two_maps(M1, _M2) ->
- {badmap, M1}.
|