emqx_utils_maps.erl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2024 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_utils_maps).
  17. -export([
  18. best_effort_recursive_sum/3,
  19. binary_key_map/1,
  20. binary_string/1,
  21. deep_convert/3,
  22. deep_find/2,
  23. deep_force_put/3,
  24. deep_get/2,
  25. deep_get/3,
  26. deep_merge/2,
  27. deep_put/3,
  28. deep_remove/2,
  29. diff_maps/2,
  30. if_only_to_toggle_enable/2,
  31. indent/3,
  32. jsonable_map/1,
  33. jsonable_map/2,
  34. key_comparer/1,
  35. put_if/4,
  36. rename/3,
  37. safe_atom_key_map/1,
  38. unindent/2,
  39. unsafe_atom_key_map/1,
  40. update_if_present/3
  41. ]).
  42. -export_type([config_key/0, config_key_path/0]).
  43. -type config_key() :: atom() | binary() | [byte()].
  44. -type config_key_path() :: [config_key()].
  45. -type convert_fun() :: fun((...) -> {K1 :: any(), V1 :: any()} | drop).
  46. -define(CONFIG_NOT_FOUND_MAGIC, '$0tFound').
  47. %%-----------------------------------------------------------------
  48. -spec deep_get(config_key_path(), map()) -> term().
  49. deep_get(ConfKeyPath, Map) ->
  50. case deep_get(ConfKeyPath, Map, ?CONFIG_NOT_FOUND_MAGIC) of
  51. ?CONFIG_NOT_FOUND_MAGIC -> error({config_not_found, ConfKeyPath});
  52. Res -> Res
  53. end.
  54. -spec deep_get(config_key_path(), map(), term()) -> term().
  55. deep_get(ConfKeyPath, Map, Default) ->
  56. case deep_find(ConfKeyPath, Map) of
  57. {not_found, _KeyPath, _Data} -> Default;
  58. {ok, Data} -> Data
  59. end.
  60. -spec deep_find(config_key_path(), map()) ->
  61. {ok, term()} | {not_found, config_key_path(), term()}.
  62. deep_find([], Map) ->
  63. {ok, Map};
  64. deep_find([Key | KeyPath] = Path, Map) when is_map(Map) ->
  65. case maps:find(Key, Map) of
  66. {ok, SubMap} -> deep_find(KeyPath, SubMap);
  67. error -> {not_found, Path, Map}
  68. end;
  69. deep_find(KeyPath, Data) ->
  70. {not_found, KeyPath, Data}.
  71. -spec deep_put(config_key_path(), map(), term()) -> map().
  72. deep_put([], _Map, Data) ->
  73. Data;
  74. deep_put([Key | KeyPath], Map, Data) ->
  75. SubMap = maps:get(Key, Map, #{}),
  76. Map#{Key => deep_put(KeyPath, SubMap, Data)}.
  77. %% Like deep_put, but ensures that the key path is present.
  78. %% If key path is not present in map, creates the keys, until it's present
  79. %% deep_force_put([x, y, z], #{a => 1}, 0) -> #{a => 1, x => #{y => #{z => 0}}}
  80. -spec deep_force_put(config_key_path(), map(), term()) -> map().
  81. deep_force_put([], _Map, Data) ->
  82. Data;
  83. deep_force_put([Key | KeyPath] = FullPath, Map, Data) ->
  84. case Map of
  85. #{Key := InnerValue} ->
  86. Map#{Key => deep_force_put(KeyPath, InnerValue, Data)};
  87. #{} ->
  88. maps:put(Key, path_to_map(KeyPath, Data), Map);
  89. _ ->
  90. path_to_map(FullPath, Data)
  91. end.
  92. -spec path_to_map(config_key_path(), term()) -> map().
  93. path_to_map([], Data) -> Data;
  94. path_to_map([Key | Tail], Data) -> #{Key => path_to_map(Tail, Data)}.
  95. -spec deep_remove(config_key_path(), map()) -> map().
  96. deep_remove([], Map) ->
  97. Map;
  98. deep_remove([Key], Map) ->
  99. maps:remove(Key, Map);
  100. deep_remove([Key | KeyPath], Map) ->
  101. case maps:find(Key, Map) of
  102. {ok, SubMap} when is_map(SubMap) ->
  103. Map#{Key => deep_remove(KeyPath, SubMap)};
  104. {ok, _Val} ->
  105. Map;
  106. error ->
  107. Map
  108. end.
  109. %% #{a => #{b => 3, c => 2}, d => 4}
  110. %% = deep_merge(#{a => #{b => 1, c => 2}, d => 4}, #{a => #{b => 3}}).
  111. -spec deep_merge(map(), map()) -> map().
  112. deep_merge(BaseMap, NewMap) ->
  113. NewKeys = maps:keys(NewMap) -- maps:keys(BaseMap),
  114. MergedBase = maps:fold(
  115. fun(K, V, Acc) ->
  116. case maps:find(K, NewMap) of
  117. error ->
  118. Acc#{K => V};
  119. {ok, NewV} when is_map(V), is_map(NewV) ->
  120. Acc#{K => deep_merge(V, NewV)};
  121. {ok, NewV} ->
  122. Acc#{K => NewV}
  123. end
  124. end,
  125. #{},
  126. BaseMap
  127. ),
  128. maps:merge(MergedBase, maps:with(NewKeys, NewMap)).
  129. -spec deep_convert(any(), convert_fun(), Args :: list()) -> any().
  130. deep_convert(Map, ConvFun, Args) when is_map(Map) ->
  131. maps:fold(
  132. fun(K, V, Acc) ->
  133. case apply(ConvFun, [K, deep_convert(V, ConvFun, Args) | Args]) of
  134. drop -> Acc;
  135. {K1, V1} -> Acc#{K1 => V1}
  136. end
  137. end,
  138. #{},
  139. Map
  140. );
  141. deep_convert(ListV, ConvFun, Args) when is_list(ListV) ->
  142. [deep_convert(V, ConvFun, Args) || V <- ListV];
  143. deep_convert(Val, _, _Args) ->
  144. Val.
  145. -spec unsafe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
  146. unsafe_atom_key_map(Map) ->
  147. convert_keys_to_atom(Map, fun(K) -> binary_to_atom(K, utf8) end).
  148. -spec binary_key_map
  149. (map()) -> map();
  150. (list()) -> list().
  151. binary_key_map(Map) ->
  152. deep_convert(
  153. Map,
  154. fun
  155. (K, V) when is_atom(K) -> {atom_to_binary(K, utf8), V};
  156. (K, V) when is_binary(K) -> {K, V}
  157. end,
  158. []
  159. ).
  160. -spec safe_atom_key_map(#{binary() | atom() => any()}) -> #{atom() => any()}.
  161. safe_atom_key_map(Map) ->
  162. convert_keys_to_atom(Map, fun(K) -> binary_to_existing_atom(K, utf8) end).
  163. -spec jsonable_map(map() | list()) -> map() | list().
  164. jsonable_map(Map) ->
  165. jsonable_map(Map, fun(K, V) -> {K, V} end).
  166. jsonable_map(Map, JsonableFun) ->
  167. deep_convert(Map, fun binary_string_kv/3, [JsonableFun]).
  168. -spec diff_maps(map(), map()) ->
  169. #{
  170. added := map(),
  171. identical := map(),
  172. removed := map(),
  173. changed := #{any() => {OldValue :: any(), NewValue :: any()}}
  174. }.
  175. diff_maps(NewMap, OldMap) ->
  176. InitR = #{identical => #{}, changed => #{}, removed => #{}},
  177. {Result, RemInNew} =
  178. lists:foldl(
  179. fun({OldK, OldV}, {Result0 = #{identical := I, changed := U, removed := D}, RemNewMap}) ->
  180. Result1 =
  181. case maps:find(OldK, NewMap) of
  182. error ->
  183. Result0#{removed => D#{OldK => OldV}};
  184. {ok, NewV} when NewV == OldV ->
  185. Result0#{identical => I#{OldK => OldV}};
  186. {ok, NewV} ->
  187. Result0#{changed => U#{OldK => {OldV, NewV}}}
  188. end,
  189. {Result1, maps:remove(OldK, RemNewMap)}
  190. end,
  191. {InitR, NewMap},
  192. maps:to_list(OldMap)
  193. ),
  194. Result#{added => RemInNew}.
  195. binary_string_kv(K, V, JsonableFun) ->
  196. case JsonableFun(K, V) of
  197. drop -> drop;
  198. {K1, V1} -> {binary_string(K1), V1}
  199. end.
  200. %% [FIXME] this doesn't belong here
  201. binary_string([]) ->
  202. [];
  203. binary_string(Val) when is_list(Val) ->
  204. case io_lib:printable_unicode_list(Val) of
  205. true -> unicode:characters_to_binary(Val);
  206. false -> [binary_string(V) || V <- Val]
  207. end;
  208. binary_string(Val) ->
  209. Val.
  210. %%---------------------------------------------------------------------------
  211. convert_keys_to_atom(BinKeyMap, Conv) ->
  212. deep_convert(
  213. BinKeyMap,
  214. fun
  215. (K, V) when is_atom(K) -> {K, V};
  216. (K, V) when is_binary(K) -> {Conv(K), V}
  217. end,
  218. []
  219. ).
  220. %% @doc Sum-merge map values.
  221. %% For bad merges, ErrorLogger is called to log the key, and value in M2 is ignored.
  222. best_effort_recursive_sum(M10, M20, ErrorLogger) ->
  223. FilterF = fun(K, V) ->
  224. case erlang:is_number(V) of
  225. true ->
  226. true;
  227. false ->
  228. ErrorLogger(#{failed_to_merge => K, bad_value => V}),
  229. false
  230. end
  231. end,
  232. M1 = deep_filter(M10, FilterF),
  233. M2 = deep_filter(M20, FilterF),
  234. do_best_effort_recursive_sum(M1, M2, ErrorLogger).
  235. do_best_effort_recursive_sum(M1, M2, ErrorLogger) ->
  236. F =
  237. fun(Key, V1, V2) ->
  238. case {erlang:is_map(V1), erlang:is_map(V2)} of
  239. {true, true} ->
  240. do_best_effort_recursive_sum(V1, V2, ErrorLogger);
  241. {true, false} ->
  242. ErrorLogger(#{failed_to_merge => Key, bad_value => V2}),
  243. do_best_effort_recursive_sum(V1, #{}, ErrorLogger);
  244. {false, true} ->
  245. ErrorLogger(#{failed_to_merge => Key, bad_value => V1}),
  246. do_best_effort_recursive_sum(V2, #{}, ErrorLogger);
  247. {false, false} ->
  248. true = is_number(V1),
  249. true = is_number(V2),
  250. V1 + V2
  251. end
  252. end,
  253. maps:merge_with(F, M1, M2).
  254. deep_filter(M, F) when is_map(M) ->
  255. %% maps:filtermap is not available before OTP 24
  256. maps:from_list(
  257. lists:filtermap(
  258. fun
  259. ({K, V}) when is_map(V) ->
  260. {true, {K, deep_filter(V, F)}};
  261. ({K, V}) ->
  262. F(K, V) andalso {true, {K, V}}
  263. end,
  264. maps:to_list(M)
  265. )
  266. ).
  267. if_only_to_toggle_enable(OldConf, Conf) ->
  268. #{added := Added, removed := Removed, changed := Updated} =
  269. emqx_utils_maps:diff_maps(OldConf, Conf),
  270. case {Added, Removed, Updated} of
  271. {Added, Removed, #{enable := _} = Updated} when
  272. map_size(Added) =:= 0,
  273. map_size(Removed) =:= 0,
  274. map_size(Updated) =:= 1
  275. ->
  276. true;
  277. {_, _, _} ->
  278. false
  279. end.
  280. %% Like `maps:update_with', but does nothing if key does not exist.
  281. update_if_present(Key, Fun, Map) ->
  282. case Map of
  283. #{Key := Val} ->
  284. Map#{Key := Fun(Val)};
  285. _ ->
  286. Map
  287. end.
  288. put_if(Acc, K, V, true) ->
  289. Acc#{K => V};
  290. put_if(Acc, _K, _V, false) ->
  291. Acc.
  292. rename(OldKey, NewKey, Map) ->
  293. case maps:find(OldKey, Map) of
  294. {ok, Value} ->
  295. maps:put(NewKey, Value, maps:remove(OldKey, Map));
  296. error ->
  297. Map
  298. end.
  299. -spec key_comparer(K) -> fun((M, M) -> boolean()) when M :: #{K => _V}.
  300. key_comparer(K) ->
  301. fun
  302. (#{K := V1}, #{K := V2}) ->
  303. V1 < V2;
  304. (#{K := _}, _) ->
  305. false;
  306. (_, #{K := _}) ->
  307. true;
  308. (M1, M2) ->
  309. M1 < M2
  310. end.
  311. -spec indent(term(), [term()], map()) -> map().
  312. indent(IndentKey, PickKeys, Map) ->
  313. maps:put(
  314. IndentKey,
  315. maps:with(PickKeys, Map),
  316. maps:without(PickKeys, Map)
  317. ).
  318. -spec unindent(term(), map()) -> map().
  319. unindent(Key, Map) ->
  320. deep_merge(
  321. maps:remove(Key, Map),
  322. maps:get(Key, Map, #{})
  323. ).