cowboy_constraints.erl 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. %% Copyright (c) 2014-2017, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cowboy_constraints).
  15. -export([validate/2]).
  16. -export([reverse/2]).
  17. -export([format_error/1]).
  18. -type constraint() :: int | nonempty | fun().
  19. -export_type([constraint/0]).
  20. -type reason() :: {constraint(), any(), any()}.
  21. -export_type([reason/0]).
  22. -spec validate(binary(), constraint() | [constraint()])
  23. -> {ok, any()} | {error, reason()}.
  24. validate(Value, Constraints) when is_list(Constraints) ->
  25. apply_list(forward, Value, Constraints);
  26. validate(Value, Constraint) ->
  27. apply_list(forward, Value, [Constraint]).
  28. -spec reverse(any(), constraint() | [constraint()])
  29. -> {ok, binary()} | {error, reason()}.
  30. reverse(Value, Constraints) when is_list(Constraints) ->
  31. apply_list(reverse, Value, Constraints);
  32. reverse(Value, Constraint) ->
  33. apply_list(reverse, Value, [Constraint]).
  34. -spec format_error(reason()) -> iodata().
  35. format_error({Constraint, Reason, Value}) ->
  36. apply_constraint(format_error, {Reason, Value}, Constraint).
  37. apply_list(_, Value, []) ->
  38. {ok, Value};
  39. apply_list(Type, Value0, [Constraint|Tail]) ->
  40. case apply_constraint(Type, Value0, Constraint) of
  41. {ok, Value} ->
  42. apply_list(Type, Value, Tail);
  43. {error, Reason} ->
  44. {error, {Constraint, Reason, Value0}}
  45. end.
  46. %% @todo {int, From, To}, etc.
  47. apply_constraint(Type, Value, int) ->
  48. int(Type, Value);
  49. apply_constraint(Type, Value, nonempty) ->
  50. nonempty(Type, Value);
  51. apply_constraint(Type, Value, F) when is_function(F) ->
  52. F(Type, Value).
  53. %% Constraint functions.
  54. int(forward, Value) ->
  55. try
  56. {ok, binary_to_integer(Value)}
  57. catch _:_ ->
  58. {error, not_an_integer}
  59. end;
  60. int(reverse, Value) ->
  61. try
  62. {ok, integer_to_binary(Value)}
  63. catch _:_ ->
  64. {error, not_an_integer}
  65. end;
  66. int(format_error, {not_an_integer, Value}) ->
  67. io_lib:format("The value ~p is not an integer.", [Value]).
  68. nonempty(Type, <<>>) when Type =/= format_error ->
  69. {error, empty};
  70. nonempty(Type, Value) when Type =/= format_error, is_binary(Value) ->
  71. {ok, Value};
  72. nonempty(format_error, {empty, Value}) ->
  73. io_lib:format("The value ~p is empty.", [Value]).
  74. -ifdef(TEST).
  75. validate_test() ->
  76. F = fun(_, Value) ->
  77. try
  78. {ok, binary_to_atom(Value, latin1)}
  79. catch _:_ ->
  80. {error, not_a_binary}
  81. end
  82. end,
  83. %% Value, Constraints, Result.
  84. Tests = [
  85. {<<>>, [], <<>>},
  86. {<<"123">>, int, 123},
  87. {<<"123">>, [int], 123},
  88. {<<"123">>, [nonempty, int], 123},
  89. {<<"123">>, [int, nonempty], 123},
  90. {<<>>, nonempty, error},
  91. {<<>>, [nonempty], error},
  92. {<<"hello">>, F, hello},
  93. {<<"hello">>, [F], hello},
  94. {<<"123">>, [F, int], error},
  95. {<<"123">>, [int, F], error},
  96. {<<"hello">>, [nonempty, F], hello},
  97. {<<"hello">>, [F, nonempty], hello}
  98. ],
  99. [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() ->
  100. case R of
  101. error -> {error, _} = validate(V, C);
  102. _ -> {ok, R} = validate(V, C)
  103. end
  104. end} || {V, C, R} <- Tests].
  105. reverse_test() ->
  106. F = fun(_, Value) ->
  107. try
  108. {ok, atom_to_binary(Value, latin1)}
  109. catch _:_ ->
  110. {error, not_an_atom}
  111. end
  112. end,
  113. %% Value, Constraints, Result.
  114. Tests = [
  115. {<<>>, [], <<>>},
  116. {123, int, <<"123">>},
  117. {123, [int], <<"123">>},
  118. {123, [nonempty, int], <<"123">>},
  119. {123, [int, nonempty], <<"123">>},
  120. {<<>>, nonempty, error},
  121. {<<>>, [nonempty], error},
  122. {hello, F, <<"hello">>},
  123. {hello, [F], <<"hello">>},
  124. {123, [F, int], error},
  125. {123, [int, F], error},
  126. {hello, [nonempty, F], <<"hello">>},
  127. {hello, [F, nonempty], <<"hello">>}
  128. ],
  129. [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() ->
  130. case R of
  131. error -> {error, _} = reverse(V, C);
  132. _ -> {ok, R} = reverse(V, C)
  133. end
  134. end} || {V, C, R} <- Tests].
  135. int_format_error_test() ->
  136. {error, Reason} = validate(<<"string">>, int),
  137. Bin = iolist_to_binary(format_error(Reason)),
  138. true = is_binary(Bin),
  139. ok.
  140. nonempty_format_error_test() ->
  141. {error, Reason} = validate(<<>>, nonempty),
  142. Bin = iolist_to_binary(format_error(Reason)),
  143. true = is_binary(Bin),
  144. ok.
  145. fun_format_error_test() ->
  146. F = fun
  147. (format_error, {test, <<"value">>}) ->
  148. formatted;
  149. (_, _) ->
  150. {error, test}
  151. end,
  152. {error, Reason} = validate(<<"value">>, F),
  153. formatted = format_error(Reason),
  154. ok.
  155. -endif.