emqx_map_lib.erl 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_map_lib).
  17. -export([ deep_get/2
  18. , deep_get/3
  19. , deep_find/2
  20. , deep_put/3
  21. , deep_remove/2
  22. , deep_merge/2
  23. , safe_atom_key_map/1
  24. , unsafe_atom_key_map/1
  25. , jsonable_map/1
  26. , jsonable_map/2
  27. , binary_string/1
  28. , deep_convert/3
  29. , diff_maps/2
  30. , merge_with/3
  31. ]).
  32. -export_type([config_key/0, config_key_path/0]).
  33. -type config_key() :: atom() | binary() | [byte()].
  34. -type config_key_path() :: [config_key()].
  35. -type convert_fun() :: fun((...) -> {K1::any(), V1::any()} | drop).
  36. %%-----------------------------------------------------------------
  37. -spec deep_get(config_key_path(), map()) -> term().
  38. deep_get(ConfKeyPath, Map) ->
  39. Ref = make_ref(),
  40. Res = deep_get(ConfKeyPath, Map, Ref),
  41. case Res =:= Ref of
  42. true -> error({config_not_found, ConfKeyPath});
  43. false -> Res
  44. end.
  45. -spec deep_get(config_key_path(), map(), term()) -> term().
  46. deep_get(ConfKeyPath, Map, Default) ->
  47. case deep_find(ConfKeyPath, Map) of
  48. {not_found, _KeyPath, _Data} -> Default;
  49. {ok, Data} -> Data
  50. end.
  51. -spec deep_find(config_key_path(), map()) ->
  52. {ok, term()} | {not_found, config_key_path(), term()}.
  53. deep_find([], Map) ->
  54. {ok, Map};
  55. deep_find([Key | KeyPath] = Path, Map) when is_map(Map) ->
  56. case maps:find(Key, Map) of
  57. {ok, SubMap} -> deep_find(KeyPath, SubMap);
  58. error -> {not_found, Path, Map}
  59. end;
  60. deep_find(KeyPath, Data) ->
  61. {not_found, KeyPath, Data}.
  62. -spec deep_put(config_key_path(), map(), term()) -> map().
  63. deep_put([], _Map, Data) ->
  64. Data;
  65. deep_put([Key | KeyPath], Map, Data) ->
  66. SubMap = maps:get(Key, Map, #{}),
  67. Map#{Key => deep_put(KeyPath, SubMap, Data)}.
  68. -spec deep_remove(config_key_path(), map()) -> map().
  69. deep_remove([], Map) ->
  70. Map;
  71. deep_remove([Key], Map) ->
  72. maps:remove(Key, Map);
  73. deep_remove([Key | KeyPath], Map) ->
  74. case maps:find(Key, Map) of
  75. {ok, SubMap} when is_map(SubMap) ->
  76. Map#{Key => deep_remove(KeyPath, SubMap)};
  77. {ok, _Val} -> Map;
  78. error -> Map
  79. end.
  80. %% #{a => #{b => 3, c => 2}, d => 4}
  81. %% = deep_merge(#{a => #{b => 1, c => 2}, d => 4}, #{a => #{b => 3}}).
  82. -spec deep_merge(map(), map()) -> map().
  83. deep_merge(BaseMap, NewMap) ->
  84. NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap),
  85. MergedBase = maps:fold(fun(K, V, Acc) ->
  86. case maps:find(K, NewMap) of
  87. error ->
  88. Acc#{K => V};
  89. {ok, NewV} when is_map(V), is_map(NewV) ->
  90. Acc#{K => deep_merge(V, NewV)};
  91. {ok, NewV} ->
  92. Acc#{K => NewV}
  93. end
  94. end, #{}, BaseMap),
  95. maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
  96. -spec deep_convert(map(), convert_fun(), Args::list()) -> map().
  97. deep_convert(Map, ConvFun, Args) when is_map(Map) ->
  98. maps:fold(fun(K, V, Acc) ->
  99. case apply(ConvFun, [K, deep_convert(V, ConvFun, Args) | Args]) of
  100. drop -> Acc;
  101. {K1, V1} -> Acc#{K1 => V1}
  102. end
  103. end, #{}, Map);
  104. deep_convert(ListV, ConvFun, Args) when is_list(ListV) ->
  105. [deep_convert(V, ConvFun, Args) || V <- ListV];
  106. deep_convert(Val, _, _Args) -> Val.
  107. -spec unsafe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
  108. unsafe_atom_key_map(Map) ->
  109. covert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
  110. -spec safe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
  111. safe_atom_key_map(Map) ->
  112. covert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
  113. -spec jsonable_map(map() | list()) -> map() | list().
  114. jsonable_map(Map) ->
  115. jsonable_map(Map, fun(K, V) -> {K, V} end).
  116. jsonable_map(Map, JsonableFun) ->
  117. deep_convert(Map, fun binary_string_kv/3, [JsonableFun]).
  118. -spec diff_maps(map(), map()) ->
  119. #{added := map(), identical := map(), removed := map(),
  120. changed := #{any() => {OldValue::any(), NewValue::any()}}}.
  121. diff_maps(NewMap, OldMap) ->
  122. InitR = #{identical => #{}, changed => #{}, removed => #{}},
  123. {Result, RemInNew} =
  124. lists:foldl(fun({OldK, OldV}, {Result0 = #{identical := I, changed := U, removed := D},
  125. RemNewMap}) ->
  126. Result1 = case maps:find(OldK, NewMap) of
  127. error ->
  128. Result0#{removed => D#{OldK => OldV}};
  129. {ok, NewV} when NewV == OldV ->
  130. Result0#{identical => I#{OldK => OldV}};
  131. {ok, NewV} ->
  132. Result0#{changed => U#{OldK => {OldV, NewV}}}
  133. end,
  134. {Result1, maps:remove(OldK, RemNewMap)}
  135. end, {InitR, NewMap}, maps:to_list(OldMap)),
  136. Result#{added => RemInNew}.
  137. binary_string_kv(K, V, JsonableFun) ->
  138. case JsonableFun(K, V) of
  139. drop -> drop;
  140. {K1, V1} -> {binary_string(K1), V1}
  141. end.
  142. binary_string([]) -> [];
  143. binary_string(Val) when is_list(Val) ->
  144. case io_lib:printable_unicode_list(Val) of
  145. true -> unicode:characters_to_binary(Val);
  146. false -> [binary_string(V) || V <- Val]
  147. end;
  148. binary_string(Val) ->
  149. Val.
  150. %%---------------------------------------------------------------------------
  151. covert_keys_to_atom(BinKeyMap, Conv) ->
  152. deep_convert(BinKeyMap, fun
  153. (K, V) when is_atom(K) -> {K, V};
  154. (K, V) when is_binary(K) -> {Conv(K), V}
  155. end, []).
  156. %% copy from maps.erl OTP24.0
  157. -compile({inline, [error_with_info/2]}).
  158. merge_with(Combiner, Map1, Map2) when is_map(Map1),
  159. is_map(Map2),
  160. is_function(Combiner, 3) ->
  161. case map_size(Map1) > map_size(Map2) of
  162. true ->
  163. Iterator = maps:iterator(Map2),
  164. merge_with_t(maps:next(Iterator),
  165. Map1,
  166. Map2,
  167. Combiner);
  168. false ->
  169. Iterator = maps:iterator(Map1),
  170. merge_with_t(maps:next(Iterator),
  171. Map2,
  172. Map1,
  173. fun(K, V1, V2) -> Combiner(K, V2, V1) end)
  174. end;
  175. merge_with(Combiner, Map1, Map2) ->
  176. error_with_info(error_type_merge_intersect(Map1, Map2, Combiner),
  177. [Combiner, Map1, Map2]).
  178. merge_with_t({K, V2, Iterator}, Map1, Map2, Combiner) ->
  179. case Map1 of
  180. #{ K := V1 } ->
  181. NewMap1 = Map1#{ K := Combiner(K, V1, V2) },
  182. merge_with_t(maps:next(Iterator), NewMap1, Map2, Combiner);
  183. #{ } ->
  184. merge_with_t(maps:next(Iterator), maps:put(K, V2, Map1), Map2, Combiner)
  185. end;
  186. merge_with_t(none, Result, _, _) ->
  187. Result.
  188. error_type_merge_intersect(M1, M2, Combiner) when is_function(Combiner, 3) ->
  189. error_type_two_maps(M1, M2);
  190. error_type_merge_intersect(_M1, _M2, _Combiner) ->
  191. badarg.
  192. error_with_info(_, _) ->
  193. {error_info, #{module => erl_stdlib_errors}}.
  194. error_type_two_maps(M1, M2) when is_map(M1) ->
  195. {badmap, M2};
  196. error_type_two_maps(M1, _M2) ->
  197. {badmap, M1}.