Browse Source

Add the 'int' constraint

Loïc Hoguin 12 years ago
parent
commit
a5a69353f1
2 changed files with 59 additions and 10 deletions
  1. 3 2
      guide/routing.md
  2. 56 8
      src/cowboy_router.erl

+ 3 - 2
guide/routing.md

@@ -196,8 +196,9 @@ Constraints
 -----------
 -----------
 
 
 After the matching has completed, the resulting bindings can be tested
 After the matching has completed, the resulting bindings can be tested
-against a set of constraints. The match will succeed only if they all
-succeed.
+against a set of constraints. Constraints are only tested when the
+binding is defined. They run in the order you defined them. The match
+will succeed only if they all succeed.
 
 
 They are always given as a two or three elements tuple, where the first
 They are always given as a two or three elements tuple, where the first
 element is the name of the binding, the second element is the constraint's
 element is the name of the binding, the second element is the constraint's

+ 56 - 8
src/cowboy_router.erl

@@ -33,7 +33,7 @@
 -export_type([bindings/0]).
 -export_type([bindings/0]).
 -export_type([tokens/0]).
 -export_type([tokens/0]).
 
 
--type constraints() :: [].
+-type constraints() :: [{atom(), int}].
 -export_type([constraints/0]).
 -export_type([constraints/0]).
 
 
 -type route_match() :: binary() | string().
 -type route_match() :: binary() | string().
@@ -222,16 +222,22 @@ match([], _, _) ->
 %% If the host is '_' then there can be no constraints.
 %% If the host is '_' then there can be no constraints.
 match([{'_', [], PathMatchs}|_Tail], _, Path) ->
 match([{'_', [], PathMatchs}|_Tail], _, Path) ->
 	match_path(PathMatchs, undefined, Path, []);
 	match_path(PathMatchs, undefined, Path, []);
-match([{HostMatch, _Constraints, PathMatchs}|Tail], Tokens, Path)
+match([{HostMatch, Constraints, PathMatchs}|Tail], Tokens, Path)
 		when is_list(Tokens) ->
 		when is_list(Tokens) ->
 	case list_match(Tokens, HostMatch, []) of
 	case list_match(Tokens, HostMatch, []) of
 		false ->
 		false ->
 			match(Tail, Tokens, Path);
 			match(Tail, Tokens, Path);
-		{true, Bindings, undefined} ->
-			match_path(PathMatchs, undefined, Path, Bindings);
 		{true, Bindings, HostInfo} ->
 		{true, Bindings, HostInfo} ->
-			match_path(PathMatchs, lists:reverse(HostInfo),
-				Path, Bindings)
+			HostInfo2 = case HostInfo of
+				undefined -> undefined;
+				_ -> lists:reverse(HostInfo)
+			end,
+			case check_constraints(Constraints, Bindings) of
+				{ok, Bindings2} ->
+					match_path(PathMatchs, HostInfo2, Path, Bindings2);
+				nomatch ->
+					match(Tail, Tokens, Path)
+			end
 	end;
 	end;
 match(Dispatch, Host, Path) ->
 match(Dispatch, Host, Path) ->
 	match(Dispatch, split_host(Host), Path).
 	match(Dispatch, split_host(Host), Path).
@@ -249,19 +255,50 @@ match_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
 	{ok, Handler, Opts, Bindings, HostInfo, undefined};
 	{ok, Handler, Opts, Bindings, HostInfo, undefined};
 match_path([{<<"*">>, _Constraints, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
 match_path([{<<"*">>, _Constraints, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
 	{ok, Handler, Opts, Bindings, HostInfo, undefined};
 	{ok, Handler, Opts, Bindings, HostInfo, undefined};
-match_path([{PathMatch, _Constraints, Handler, Opts}|Tail], HostInfo, Tokens,
+match_path([{PathMatch, Constraints, Handler, Opts}|Tail], HostInfo, Tokens,
 		Bindings) when is_list(Tokens) ->
 		Bindings) when is_list(Tokens) ->
 	case list_match(Tokens, PathMatch, []) of
 	case list_match(Tokens, PathMatch, []) of
 		false ->
 		false ->
 			match_path(Tail, HostInfo, Tokens, Bindings);
 			match_path(Tail, HostInfo, Tokens, Bindings);
 		{true, PathBinds, PathInfo} ->
 		{true, PathBinds, PathInfo} ->
-			{ok, Handler, Opts, Bindings ++ PathBinds, HostInfo, PathInfo}
+			case check_constraints(Constraints, PathBinds) of
+				{ok, PathBinds2} ->
+					{ok, Handler, Opts, Bindings ++ PathBinds2,
+						HostInfo, PathInfo};
+				nomatch ->
+					match_path(Tail, HostInfo, Tokens, Bindings)
+			end
 	end;
 	end;
 match_path(_Dispatch, _HostInfo, badrequest, _Bindings) ->
 match_path(_Dispatch, _HostInfo, badrequest, _Bindings) ->
 	{error, badrequest, path};
 	{error, badrequest, path};
 match_path(Dispatch, HostInfo, Path, Bindings) ->
 match_path(Dispatch, HostInfo, Path, Bindings) ->
 	match_path(Dispatch, HostInfo, split_path(Path), Bindings).
 	match_path(Dispatch, HostInfo, split_path(Path), Bindings).
 
 
+check_constraints([], Bindings) ->
+	{ok, Bindings};
+check_constraints([Constraint|Tail], Bindings) ->
+	Name = element(1, Constraint),
+	case lists:keyfind(Name, 1, Bindings) of
+		false ->
+			check_constraints(Tail, Bindings);
+		{_, Value} ->
+			case check_constraint(Constraint, Value) of
+				true ->
+					check_constraints(Tail, Bindings);
+				{true, Value2} ->
+					Bindings2 = lists:keyreplace(Name, 1, Bindings,
+						{Name, Value2}),
+					check_constraints(Tail, Bindings2);
+				false ->
+					nomatch
+			end
+	end.
+
+check_constraint({_, int}, Value) ->
+	try {true, list_to_integer(binary_to_list(Value))}
+	catch _:_ -> false
+	end.
+
 %% @doc Split a hostname into a list of tokens.
 %% @doc Split a hostname into a list of tokens.
 -spec split_host(binary()) -> tokens().
 -spec split_host(binary()) -> tokens().
 split_host(Host) ->
 split_host(Host) ->
@@ -478,4 +515,15 @@ match_info_test_() ->
 		R = match(Dispatch, H, P)
 		R = match(Dispatch, H, P)
 	end} || {H, P, R} <- Tests].
 	end} || {H, P, R} <- Tests].
 
 
+match_constraints_test() ->
+	Dispatch = [{'_', [],
+		[{[<<"path">>, value], [{value, int}], match, []}]}],
+	{ok, _, [], [{value, 123}], _, _} = match(Dispatch,
+		<<"ninenines.eu">>, <<"/path/123">>),
+	{ok, _, [], [{value, 123}], _, _} = match(Dispatch,
+		<<"ninenines.eu">>, <<"/path/123/">>),
+	{error, notfound, path} = match(Dispatch,
+		<<"ninenines.eu">>, <<"/path/NaN/">>),
+	ok.
+
 -endif.
 -endif.