PageRenderTime 26ms CodeModel.GetById 10ms RepoModel.GetById 0ms 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. -include("uce.hrl").
  20. -export([init/0,
  21. get/1,
  22. get/2,
  23. get/3]).
  24. init() ->
  25. Routes = setup_routes(),
  26. TableId = ets:new(uce_routes, [bag, public, named_table]),
  27. [ets:insert(TableId, Route) || Route <- Routes],
  28. ok.
  29. %%
  30. %% Search route matching a path
  31. %%
  32. get(Path) ->
  33. get('_', Path).
  34. %%
  35. %% Search route matching a path and the method
  36. %%
  37. get(Method, Path) ->
  38. route(Method, Path, ets:tab2list(uce_routes)).
  39. get(Method, Path, undefined) -> % default value of the #headers record (in yaw_api.hrl)
  40. route(Method, Path, undefined, ets:tab2list(uce_routes));
  41. get(Method, Path, "") ->
  42. route(Method, Path, "", ets:tab2list(uce_routes));
  43. get(Method, Path, ContentType) when is_list(ContentType) ->
  44. [ContentType2|_] = string:tokens(ContentType, ";"),
  45. route(Method, Path, ContentType2, ets:tab2list(uce_routes)).
  46. route(Method, Path, Routes) ->
  47. route(Method, Path, "", Routes).
  48. -spec route(Method :: atom(), Path :: string(), ContentType :: string(), Routes :: [route()])
  49. -> {error, not_found} | {ok, list(atom()|string()), fun()}.
  50. route(_, _, _, []) ->
  51. {error, not_found};
  52. route('_', Path, ContentType, [#uce_route{callback=Callback} = Route|Routes]) ->
  53. case match_path(Path, Route) of
  54. false ->
  55. route('_', Path, ContentType, Routes);
  56. {ok, Binds, List} ->
  57. {ok, lists:reverse(Binds) ++ List, Callback}
  58. end;
  59. route(Method, Path, ContentType, [#uce_route{
  60. method=Method,
  61. content_type=any,
  62. callback=Callback} = Route|Routes]) ->
  63. case match_path(Path, Route) of
  64. false ->
  65. route(Method, Path, ContentType, Routes);
  66. {ok, Binds, List} ->
  67. {ok, lists:reverse(Binds) ++ List, Callback}
  68. end;
  69. route(Method, Path, ContentType, [#uce_route{
  70. method=Method,
  71. content_type=ContentType,
  72. callback=Callback} = Route|Routes]) ->
  73. case match_path(Path, Route) of
  74. false ->
  75. route(Method, Path, ContentType, Routes);
  76. {ok, Binds, List} ->
  77. {ok, lists:reverse(Binds) ++ List, Callback}
  78. end;
  79. route(Method, Path, ContentType, [#uce_route{}|Routes]) ->
  80. route(Method, Path, ContentType, Routes).
  81. match_path("/"++ Path, #uce_route{path=PathRule}) ->
  82. List = re:split(Path, "[/]", [{return,list}, trim]),
  83. case list_match(List, PathRule, []) of
  84. false ->
  85. false;
  86. {true, Binds, undefined} ->
  87. {ok, Binds, []};
  88. {true, Binds, List2} ->
  89. {ok, Binds, List2}
  90. end.
  91. % We are too lazy
  92. % we have stolen this code from cowboy
  93. % https://github.com/extend/cowboy/blob/master/src/cowboy_dispatcher.erl
  94. % Cowboy: Loïc Hoguin, Hans Ulrich Niedermann, Anthony Ramine
  95. -type bindings() :: list({atom(), binary()}).
  96. -type path_tokens() :: list(binary()).
  97. -type match_rule() :: '_' | '*' | list(binary() | '_' | atom()).
  98. -spec list_match(path_tokens(), match_rule(), bindings())
  99. -> {true, bindings(), undefined | path_tokens()} | false.
  100. %% Atom '...' matches any trailing path, stop right now.
  101. list_match(List, ['...'], Binds) ->
  102. {true, Binds, List};
  103. %% Atom '_' matches anything, continue.
  104. list_match([_E|Tail], ['_'|TailMatch], Binds) ->
  105. list_match(Tail, TailMatch, Binds);
  106. %% Both values match, continue.
  107. list_match([E|Tail], [E|TailMatch], Binds) ->
  108. list_match(Tail, TailMatch, Binds);
  109. %% Bind E to the variable name V and continue.
  110. list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->
  111. list_match(Tail, TailMatch, [{V, E}|Binds]);
  112. %% Match complete.
  113. list_match([], [], Binds) ->
  114. {true, Binds, undefined};
  115. %% Values don't match, stop.
  116. list_match(_List, _Match, _Binds) ->
  117. false.
  118. setup_routes([]) ->
  119. [];
  120. setup_routes([Controller|Controllers]) ->
  121. Controller:init() ++ setup_routes(Controllers).
  122. % TODO : make list of controllers more generic
  123. setup_routes() ->
  124. setup_routes([user_controller,
  125. presence_controller,
  126. meeting_controller,
  127. role_controller,
  128. event_controller,
  129. file_controller,
  130. time_controller,
  131. search_controller]).
  132. -ifdef(TEST).
  133. -include_lib("eunit/include/eunit.hrl").
  134. route_without_method_test() ->
  135. Routes = [#uce_route{method='GET',
  136. path=["user"],
  137. callback={?MODULE, get, []}},
  138. #uce_route{method='POST',
  139. path=["user", name],
  140. callback={?MODULE, update_user, []}},
  141. #uce_route{method='PUT',
  142. path=["user", name, '...'],
  143. callback={?MODULE, put_user_plop, []}}],
  144. ?assertMatch({error, not_found}, route('_', "/user/", [])),
  145. ?assertMatch({ok, [], _}, route('_', "/user/", Routes)).
  146. route__with_method_test() ->
  147. Routes = [#uce_route{method='GET',
  148. path=["user"],
  149. callback={?MODULE, get, []}},
  150. #uce_route{method='GET',
  151. path=["user", name],
  152. callback={?MODULE, get_user, []}},
  153. #uce_route{method='GET',
  154. path=["user", name, id],
  155. callback={?MODULE, get_user_name_id, []}},
  156. #uce_route{method='POST',
  157. path=["user", name],
  158. callback={?MODULE, update_user, []}},
  159. #uce_route{method='PUT',
  160. path=["user", name, '...'],
  161. callback={?MODULE, put_user_plop, []}}],
  162. ?assertMatch({error, not_found}, route('GET', "/user/", [])),
  163. ?assertMatch({ok, [], {?MODULE, get, []}}, route('GET', "/user/", Routes)),
  164. ?assertMatch({ok, [], {?MODULE, get, []}}, route('GET', "/user", Routes)),
  165. ?assertMatch({ok, [{name, "plop"}], {?MODULE, get_user, []}}, route('GET', "/user/plop", Routes)),
  166. ?assertMatch({ok, [{name, "plop"}, {id, "myid"}], {?MODULE, get_user_name_id, []}}, route('GET', "/user/plop/myid", Routes)),
  167. ?assertMatch({ok, [{name, "plop"}], {?MODULE, update_user, []}}, route('POST', "/user/plop", Routes)),
  168. ?assertMatch({ok, [{name, "plop"}, "plip"], {?MODULE, put_user_plop, []}}, route('PUT', "/user/plop/plip", Routes)).
  169. route_with_content_type_test() ->
  170. Routes = [#uce_route{method='GET',
  171. path=["user"],
  172. callback={?MODULE, get, []}},
  173. #uce_route{method='POST',
  174. content_type="application/json",
  175. path=["user", name],
  176. callback={?MODULE, update_user, []}},
  177. #uce_route{method='POST',
  178. path=["user", name],
  179. callback={?MODULE, update_user_inline, []}},
  180. #uce_route{method='PUT',
  181. path=["user", name, '...'],
  182. callback={?MODULE, put_user_plop, []}}],
  183. io:format("~p~n", [Routes]),
  184. ?assertMatch({ok, [{name, "42"}], {?MODULE, update_user, []}}, route('POST', "/user/42", "application/json", Routes)),
  185. ?assertMatch({ok, _, {?MODULE, update_user_inline, []}}, route('POST', "/user/42", "", Routes)),
  186. ?assertMatch({ok, _, {?MODULE, update_user_inline, []}}, route('POST', "/user/42", "text/plain", Routes)).
  187. -endif.