/ucengine/src/core/routes.erl
Erlang | 204 lines | 153 code | 17 blank | 34 comment | 1 complexity | b63556d3a413e5337ea3c79e57a51e83 MD5 | raw file
1%% 2%% U.C.Engine - Unified Collaboration Engine 3%% Copyright (C) 2011 af83 4%% 5%% This program is free software: you can redistribute it and/or modify 6%% it under the terms of the GNU Affero General Public License as published by 7%% the Free Software Foundation, either version 3 of the License, or 8%% (at your option) any later version. 9%% 10%% This program is distributed in the hope that it will be useful, 11%% but WITHOUT ANY WARRANTY; without even the implied warranty of 12%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13%% GNU Affero General Public License for more details. 14%% 15%% You should have received a copy of the GNU Affero General Public License 16%% along with this program. If not, see <http://www.gnu.org/licenses/>. 17%% 18-module(routes). 19 20-include("uce.hrl"). 21 22-export([init/0, 23 get/1, 24 get/2, 25 get/3]). 26 27init() -> 28 Routes = setup_routes(), 29 TableId = ets:new(uce_routes, [bag, public, named_table]), 30 [ets:insert(TableId, Route) || Route <- Routes], 31 ok. 32 33%% 34%% Search route matching a path 35%% 36get(Path) -> 37 get('_', Path). 38%% 39%% Search route matching a path and the method 40%% 41get(Method, Path) -> 42 route(Method, Path, ets:tab2list(uce_routes)). 43 44get(Method, Path, undefined) -> % default value of the #headers record (in yaw_api.hrl) 45 route(Method, Path, undefined, ets:tab2list(uce_routes)); 46get(Method, Path, "") -> 47 route(Method, Path, "", ets:tab2list(uce_routes)); 48get(Method, Path, ContentType) when is_list(ContentType) -> 49 [ContentType2|_] = string:tokens(ContentType, ";"), 50 route(Method, Path, ContentType2, ets:tab2list(uce_routes)). 51 52route(Method, Path, Routes) -> 53 route(Method, Path, "", Routes). 54 55-spec route(Method :: atom(), Path :: string(), ContentType :: string(), Routes :: [route()]) 56 -> {error, not_found} | {ok, list(atom()|string()), fun()}. 57route(_, _, _, []) -> 58 {error, not_found}; 59route('_', Path, ContentType, [#uce_route{callback=Callback} = Route|Routes]) -> 60 case match_path(Path, Route) of 61 false -> 62 route('_', Path, ContentType, Routes); 63 {ok, Binds, List} -> 64 {ok, lists:reverse(Binds) ++ List, Callback} 65 end; 66route(Method, Path, ContentType, [#uce_route{ 67 method=Method, 68 content_type=any, 69 callback=Callback} = Route|Routes]) -> 70 case match_path(Path, Route) of 71 false -> 72 route(Method, Path, ContentType, Routes); 73 {ok, Binds, List} -> 74 {ok, lists:reverse(Binds) ++ List, Callback} 75 end; 76route(Method, Path, ContentType, [#uce_route{ 77 method=Method, 78 content_type=ContentType, 79 callback=Callback} = Route|Routes]) -> 80 case match_path(Path, Route) of 81 false -> 82 route(Method, Path, ContentType, Routes); 83 {ok, Binds, List} -> 84 {ok, lists:reverse(Binds) ++ List, Callback} 85 end; 86route(Method, Path, ContentType, [#uce_route{}|Routes]) -> 87 route(Method, Path, ContentType, Routes). 88 89match_path("/"++ Path, #uce_route{path=PathRule}) -> 90 List = re:split(Path, "[/]", [{return,list}, trim]), 91 case list_match(List, PathRule, []) of 92 false -> 93 false; 94 {true, Binds, undefined} -> 95 {ok, Binds, []}; 96 {true, Binds, List2} -> 97 {ok, Binds, List2} 98 end. 99 100% We are too lazy 101% we have stolen this code from cowboy 102% https://github.com/extend/cowboy/blob/master/src/cowboy_dispatcher.erl 103% Cowboy: Loïc Hoguin, Hans Ulrich Niedermann, Anthony Ramine 104 105-type bindings() :: list({atom(), binary()}). 106-type path_tokens() :: list(binary()). 107-type match_rule() :: '_' | '*' | list(binary() | '_' | atom()). 108 109-spec list_match(path_tokens(), match_rule(), bindings()) 110 -> {true, bindings(), undefined | path_tokens()} | false. 111%% Atom '...' matches any trailing path, stop right now. 112list_match(List, ['...'], Binds) -> 113 {true, Binds, List}; 114%% Atom '_' matches anything, continue. 115list_match([_E|Tail], ['_'|TailMatch], Binds) -> 116 list_match(Tail, TailMatch, Binds); 117%% Both values match, continue. 118list_match([E|Tail], [E|TailMatch], Binds) -> 119 list_match(Tail, TailMatch, Binds); 120%% Bind E to the variable name V and continue. 121list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) -> 122 list_match(Tail, TailMatch, [{V, E}|Binds]); 123%% Match complete. 124list_match([], [], Binds) -> 125 {true, Binds, undefined}; 126%% Values don't match, stop. 127list_match(_List, _Match, _Binds) -> 128 false. 129 130setup_routes([]) -> 131 []; 132setup_routes([Controller|Controllers]) -> 133 Controller:init() ++ setup_routes(Controllers). 134 135% TODO : make list of controllers more generic 136setup_routes() -> 137 setup_routes([user_controller, 138 presence_controller, 139 meeting_controller, 140 role_controller, 141 event_controller, 142 file_controller, 143 time_controller, 144 search_controller]). 145 146-ifdef(TEST). 147-include_lib("eunit/include/eunit.hrl"). 148 149route_without_method_test() -> 150 Routes = [#uce_route{method='GET', 151 path=["user"], 152 callback={?MODULE, get, []}}, 153 #uce_route{method='POST', 154 path=["user", name], 155 callback={?MODULE, update_user, []}}, 156 #uce_route{method='PUT', 157 path=["user", name, '...'], 158 callback={?MODULE, put_user_plop, []}}], 159 ?assertMatch({error, not_found}, route('_', "/user/", [])), 160 ?assertMatch({ok, [], _}, route('_', "/user/", Routes)). 161 162route__with_method_test() -> 163 Routes = [#uce_route{method='GET', 164 path=["user"], 165 callback={?MODULE, get, []}}, 166 #uce_route{method='GET', 167 path=["user", name], 168 callback={?MODULE, get_user, []}}, 169 #uce_route{method='GET', 170 path=["user", name, id], 171 callback={?MODULE, get_user_name_id, []}}, 172 #uce_route{method='POST', 173 path=["user", name], 174 callback={?MODULE, update_user, []}}, 175 #uce_route{method='PUT', 176 path=["user", name, '...'], 177 callback={?MODULE, put_user_plop, []}}], 178 ?assertMatch({error, not_found}, route('GET', "/user/", [])), 179 ?assertMatch({ok, [], {?MODULE, get, []}}, route('GET', "/user/", Routes)), 180 ?assertMatch({ok, [], {?MODULE, get, []}}, route('GET', "/user", Routes)), 181 ?assertMatch({ok, [{name, "plop"}], {?MODULE, get_user, []}}, route('GET', "/user/plop", Routes)), 182 ?assertMatch({ok, [{name, "plop"}, {id, "myid"}], {?MODULE, get_user_name_id, []}}, route('GET', "/user/plop/myid", Routes)), 183 ?assertMatch({ok, [{name, "plop"}], {?MODULE, update_user, []}}, route('POST', "/user/plop", Routes)), 184 ?assertMatch({ok, [{name, "plop"}, "plip"], {?MODULE, put_user_plop, []}}, route('PUT', "/user/plop/plip", Routes)). 185 186route_with_content_type_test() -> 187 Routes = [#uce_route{method='GET', 188 path=["user"], 189 callback={?MODULE, get, []}}, 190 #uce_route{method='POST', 191 content_type="application/json", 192 path=["user", name], 193 callback={?MODULE, update_user, []}}, 194 #uce_route{method='POST', 195 path=["user", name], 196 callback={?MODULE, update_user_inline, []}}, 197 #uce_route{method='PUT', 198 path=["user", name, '...'], 199 callback={?MODULE, put_user_plop, []}}], 200 io:format("~p~n", [Routes]), 201 ?assertMatch({ok, [{name, "42"}], {?MODULE, update_user, []}}, route('POST', "/user/42", "application/json", Routes)), 202 ?assertMatch({ok, _, {?MODULE, update_user_inline, []}}, route('POST', "/user/42", "", Routes)), 203 ?assertMatch({ok, _, {?MODULE, update_user_inline, []}}, route('POST', "/user/42", "text/plain", Routes)). 204-endif.