prop_emqx_utils_json.erl 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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(prop_emqx_utils_json).
  17. -import(
  18. emqx_utils_json,
  19. [
  20. decode/1,
  21. decode/2,
  22. encode/1,
  23. safe_decode/1,
  24. safe_decode/2,
  25. safe_encode/1
  26. ]
  27. ).
  28. -include_lib("proper/include/proper.hrl").
  29. %%--------------------------------------------------------------------
  30. %% Properties
  31. %%--------------------------------------------------------------------
  32. prop_json_basic() ->
  33. ?FORALL(
  34. T,
  35. json_basic(),
  36. begin
  37. {ok, J} = safe_encode(T),
  38. {ok, T} = safe_decode(J),
  39. T = decode(encode(T)),
  40. true
  41. end
  42. ).
  43. prop_json_basic_atom() ->
  44. ?FORALL(
  45. T0,
  46. latin_atom(),
  47. begin
  48. T = atom_to_binary(T0, utf8),
  49. {ok, J} = safe_encode(T0),
  50. {ok, T} = safe_decode(J),
  51. T = decode(encode(T0)),
  52. true
  53. end
  54. ).
  55. prop_object_proplist_to_proplist() ->
  56. ?FORALL(
  57. T,
  58. json_object(),
  59. begin
  60. {ok, J} = safe_encode(T),
  61. {ok, T} = safe_decode(J),
  62. T = decode(encode(T), []),
  63. true
  64. end
  65. ).
  66. prop_object_map_to_map() ->
  67. ?FORALL(
  68. T,
  69. json_object_map(),
  70. begin
  71. {ok, J} = safe_encode(T),
  72. {ok, T} = safe_decode(J, [return_maps]),
  73. T = decode(encode(T), [return_maps]),
  74. true
  75. end
  76. ).
  77. %% The duplicated key will be overridden
  78. prop_object_proplist_to_map() ->
  79. ?FORALL(
  80. T0,
  81. json_object(),
  82. begin
  83. T = to_map(T0),
  84. {ok, J} = safe_encode(T0),
  85. {ok, T} = safe_decode(J, [return_maps]),
  86. T = decode(encode(T0), [return_maps]),
  87. true
  88. end
  89. ).
  90. prop_object_map_to_proplist() ->
  91. ?FORALL(
  92. T0,
  93. json_object_map(),
  94. begin
  95. %% jiffy encode a map with descending order, that is,
  96. %% it is opposite with maps traversal sequence
  97. %% see: the `to_list` implementation
  98. T = to_list(T0),
  99. {ok, J} = safe_encode(T0),
  100. {ok, T} = safe_decode(J),
  101. T = decode(encode(T0), []),
  102. true
  103. end
  104. ).
  105. prop_safe_encode() ->
  106. ?FORALL(
  107. T,
  108. invalid_json_term(),
  109. begin
  110. {error, _} = safe_encode(T),
  111. true
  112. end
  113. ).
  114. prop_safe_decode() ->
  115. ?FORALL(
  116. T,
  117. invalid_json_str(),
  118. begin
  119. {error, _} = safe_decode(T),
  120. true
  121. end
  122. ).
  123. %%--------------------------------------------------------------------
  124. %% Helpers
  125. %%--------------------------------------------------------------------
  126. to_map([{_, _} | _] = L) ->
  127. lists:foldl(
  128. fun({Name, Value}, Acc) ->
  129. Acc#{Name => to_map(Value)}
  130. end,
  131. #{},
  132. L
  133. );
  134. to_map(L) when is_list(L) ->
  135. [to_map(E) || E <- L];
  136. to_map(T) ->
  137. T.
  138. to_list(L) when is_list(L) ->
  139. [to_list(E) || E <- L];
  140. to_list(M) when is_map(M) ->
  141. maps:fold(
  142. fun(K, V, Acc) ->
  143. [{K, to_list(V)} | Acc]
  144. end,
  145. [],
  146. M
  147. );
  148. to_list(T) ->
  149. T.
  150. %%--------------------------------------------------------------------
  151. %% Generators (https://tools.ietf.org/html/rfc8259)
  152. %%--------------------------------------------------------------------
  153. %% true, false, null, and number(), string()
  154. json_basic() ->
  155. oneof([true, false, null, number(), json_string()]).
  156. latin_atom() ->
  157. emqx_proper_types:limited_latin_atom().
  158. json_string() -> utf8().
  159. json_object() ->
  160. oneof([
  161. json_array_1(),
  162. json_object_1(),
  163. json_array_object_1(),
  164. json_array_2(),
  165. json_object_2(),
  166. json_array_object_2()
  167. ]).
  168. json_object_map() ->
  169. ?LET(L, json_object(), to_map(L)).
  170. json_array_1() ->
  171. list(json_basic()).
  172. json_array_2() ->
  173. list([json_basic(), json_array_1()]).
  174. json_object_1() ->
  175. list({json_key(), json_basic()}).
  176. json_object_2() ->
  177. list({
  178. json_key(),
  179. oneof([
  180. json_basic(),
  181. json_array_1(),
  182. json_object_1()
  183. ])
  184. }).
  185. json_array_object_1() ->
  186. list(json_object_1()).
  187. json_array_object_2() ->
  188. list(json_object_2()).
  189. %% @private
  190. json_key() ->
  191. ?LET(K, latin_atom(), atom_to_binary(K, utf8)).
  192. invalid_json_term() ->
  193. ?SUCHTHAT(T, tuple(), (tuple_size(T) /= 1)).
  194. invalid_json_str() ->
  195. ?LET(T, json_object_2(), chaos(encode(T))).
  196. %% @private
  197. chaos(S) when is_binary(S) ->
  198. T = [$\r, $\n, $", ${, $}, $[, $], $:, $,],
  199. iolist_to_binary(chaos(binary_to_list(S), 100, T)).
  200. chaos(S, 0, _) ->
  201. S;
  202. chaos(S, N, T) ->
  203. I = rand:uniform(length(S)),
  204. {L1, L2} = lists:split(I, S),
  205. chaos(lists:flatten([L1, lists:nth(rand:uniform(length(T)), T), L2]), N - 1, T).