PageRenderTime 59ms CodeModel.GetById 19ms app.highlight 35ms RepoModel.GetById 2ms app.codeStats 0ms

/ucengine/src/core/routes.erl

http://github.com/AF83/ucengine
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.