cowboy_constraints.erl (4875B)
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 15 -module(cowboy_constraints). 16 17 -export([validate/2]). 18 -export([reverse/2]). 19 -export([format_error/1]). 20 21 -type constraint() :: int | nonempty | fun(). 22 -export_type([constraint/0]). 23 24 -type reason() :: {constraint(), any(), any()}. 25 -export_type([reason/0]). 26 27 -spec validate(binary(), constraint() | [constraint()]) 28 -> {ok, any()} | {error, reason()}. 29 validate(Value, Constraints) when is_list(Constraints) -> 30 apply_list(forward, Value, Constraints); 31 validate(Value, Constraint) -> 32 apply_list(forward, Value, [Constraint]). 33 34 -spec reverse(any(), constraint() | [constraint()]) 35 -> {ok, binary()} | {error, reason()}. 36 reverse(Value, Constraints) when is_list(Constraints) -> 37 apply_list(reverse, Value, Constraints); 38 reverse(Value, Constraint) -> 39 apply_list(reverse, Value, [Constraint]). 40 41 -spec format_error(reason()) -> iodata(). 42 format_error({Constraint, Reason, Value}) -> 43 apply_constraint(format_error, {Reason, Value}, Constraint). 44 45 apply_list(_, Value, []) -> 46 {ok, Value}; 47 apply_list(Type, Value0, [Constraint|Tail]) -> 48 case apply_constraint(Type, Value0, Constraint) of 49 {ok, Value} -> 50 apply_list(Type, Value, Tail); 51 {error, Reason} -> 52 {error, {Constraint, Reason, Value0}} 53 end. 54 55 %% @todo {int, From, To}, etc. 56 apply_constraint(Type, Value, int) -> 57 int(Type, Value); 58 apply_constraint(Type, Value, nonempty) -> 59 nonempty(Type, Value); 60 apply_constraint(Type, Value, F) when is_function(F) -> 61 F(Type, Value). 62 63 %% Constraint functions. 64 65 int(forward, Value) -> 66 try 67 {ok, binary_to_integer(Value)} 68 catch _:_ -> 69 {error, not_an_integer} 70 end; 71 int(reverse, Value) -> 72 try 73 {ok, integer_to_binary(Value)} 74 catch _:_ -> 75 {error, not_an_integer} 76 end; 77 int(format_error, {not_an_integer, Value}) -> 78 io_lib:format("The value ~p is not an integer.", [Value]). 79 80 nonempty(Type, <<>>) when Type =/= format_error -> 81 {error, empty}; 82 nonempty(Type, Value) when Type =/= format_error, is_binary(Value) -> 83 {ok, Value}; 84 nonempty(format_error, {empty, Value}) -> 85 io_lib:format("The value ~p is empty.", [Value]). 86 87 -ifdef(TEST). 88 89 validate_test() -> 90 F = fun(_, Value) -> 91 try 92 {ok, binary_to_atom(Value, latin1)} 93 catch _:_ -> 94 {error, not_a_binary} 95 end 96 end, 97 %% Value, Constraints, Result. 98 Tests = [ 99 {<<>>, [], <<>>}, 100 {<<"123">>, int, 123}, 101 {<<"123">>, [int], 123}, 102 {<<"123">>, [nonempty, int], 123}, 103 {<<"123">>, [int, nonempty], 123}, 104 {<<>>, nonempty, error}, 105 {<<>>, [nonempty], error}, 106 {<<"hello">>, F, hello}, 107 {<<"hello">>, [F], hello}, 108 {<<"123">>, [F, int], error}, 109 {<<"123">>, [int, F], error}, 110 {<<"hello">>, [nonempty, F], hello}, 111 {<<"hello">>, [F, nonempty], hello} 112 ], 113 [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() -> 114 case R of 115 error -> {error, _} = validate(V, C); 116 _ -> {ok, R} = validate(V, C) 117 end 118 end} || {V, C, R} <- Tests]. 119 120 reverse_test() -> 121 F = fun(_, Value) -> 122 try 123 {ok, atom_to_binary(Value, latin1)} 124 catch _:_ -> 125 {error, not_an_atom} 126 end 127 end, 128 %% Value, Constraints, Result. 129 Tests = [ 130 {<<>>, [], <<>>}, 131 {123, int, <<"123">>}, 132 {123, [int], <<"123">>}, 133 {123, [nonempty, int], <<"123">>}, 134 {123, [int, nonempty], <<"123">>}, 135 {<<>>, nonempty, error}, 136 {<<>>, [nonempty], error}, 137 {hello, F, <<"hello">>}, 138 {hello, [F], <<"hello">>}, 139 {123, [F, int], error}, 140 {123, [int, F], error}, 141 {hello, [nonempty, F], <<"hello">>}, 142 {hello, [F, nonempty], <<"hello">>} 143 ], 144 [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() -> 145 case R of 146 error -> {error, _} = reverse(V, C); 147 _ -> {ok, R} = reverse(V, C) 148 end 149 end} || {V, C, R} <- Tests]. 150 151 int_format_error_test() -> 152 {error, Reason} = validate(<<"string">>, int), 153 Bin = iolist_to_binary(format_error(Reason)), 154 true = is_binary(Bin), 155 ok. 156 157 nonempty_format_error_test() -> 158 {error, Reason} = validate(<<>>, nonempty), 159 Bin = iolist_to_binary(format_error(Reason)), 160 true = is_binary(Bin), 161 ok. 162 163 fun_format_error_test() -> 164 F = fun 165 (format_error, {test, <<"value">>}) -> 166 formatted; 167 (_, _) -> 168 {error, test} 169 end, 170 {error, Reason} = validate(<<"value">>, F), 171 formatted = format_error(Reason), 172 ok. 173 174 -endif.