prop_emqx_json.erl 5.3 KB

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