/src/support/z_service.erl

http://github.com/zotonic/zotonic · Erlang · 192 lines · 117 code · 30 blank · 45 comment · 7 complexity · 1b2f8ad96dadf54cfeeba7490f938112 MD5 · raw file

  1. %% @author Arjan Scherpenisse <arjan@scherpenisse.net>
  2. %% @copyright 2009-2012 Arjan Scherpenisse
  3. %% Date: 2009-10-03
  4. %% @doc Support functions for API calls.
  5. %% Copyright 2009-2012 Arjan Scherpenisse
  6. %%
  7. %% Licensed under the Apache License, Version 2.0 (the "License");
  8. %% you may not use this file except in compliance with the License.
  9. %% You may obtain a copy of the License at
  10. %%
  11. %% http://www.apache.org/licenses/LICENSE-2.0
  12. %%
  13. %% Unless required by applicable law or agreed to in writing, software
  14. %% distributed under the License is distributed on an "AS IS" BASIS,
  15. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. %% See the License for the specific language governing permissions and
  17. %% limitations under the License.
  18. -module(z_service).
  19. -author("Arjan Scherpenisse <arjan@scherpenisse.net>").
  20. -export([
  21. needauth/1,
  22. title/1,
  23. all/1,
  24. all/2,
  25. serviceinfo/2,
  26. http_methods/1,
  27. handler/1,
  28. grouped/1,
  29. applies/2,
  30. module_to_api_prefix/1,
  31. api_prefix_to_module/1
  32. ]).
  33. -include_lib("zotonic.hrl").
  34. %%
  35. %% Give information about all API services.
  36. %%
  37. all(Context) ->
  38. F = fun() ->
  39. [ ServiceModule || #module_index{erlang_module=ServiceModule} <- z_module_indexer:find_all(service, true, Context) ]
  40. end,
  41. z_depcache:memo(F, {z_services}, ?HOUR, [z_modules, module_index], Context).
  42. %%
  43. %% All services grouped by module
  44. %%
  45. grouped(Context) ->
  46. grouped(all(info, Context), Context).
  47. grouped(Services, _Context) ->
  48. P = [{proplists:get_value(module, Service), Service} || Service <- Services],
  49. [{Mod, proplists:get_all_values(Mod, P)} || Mod <- proplists:get_keys(P) ].
  50. %%
  51. %% All services augmented as serviceinfo/1 record.
  52. %%
  53. all(info, Context) ->
  54. F = fun() ->
  55. Info = z_module_indexer:find_all(service, true, Context),
  56. [ serviceinfo(Name, Module, ErlangModule) || #module_index{key=#module_index_key{name=Name}, module=Module, erlang_module=ErlangModule} <- Info ]
  57. end,
  58. z_depcache:memo(F, {z_services_info}, ?HOUR, [z_modules, module_index], Context);
  59. %%
  60. %% All services as authentication values
  61. %%
  62. all(authvalues, Context) ->
  63. All = all(info, Context),
  64. All2 = lists:filter( fun(S) -> proplists:get_value(needauth, S) end, All),
  65. Grouped = grouped(All2, Context),
  66. lists:flatten([ {"*", "Everything"} |
  67. [ [authvalue_module(Module) | [authvalue_service(S) || S <- Services]] || {Module, Services} <- Grouped]]).
  68. authvalue_module(Module) ->
  69. ModuleTitle = proplists:get_value(mod_title, Module:module_info(attributes)),
  70. {module_to_api_prefix(Module) ++ "/*", ModuleTitle}.
  71. authvalue_service(ServiceInfo) ->
  72. ServiceTitle = title(proplists:get_value(service, ServiceInfo)),
  73. {proplists:get_value(method, ServiceInfo), ServiceTitle}.
  74. module_to_api_prefix(ZotonicModule) when is_atom(ZotonicModule) ->
  75. case atom_to_list(ZotonicModule) of
  76. [$m, $o, $d, $_ | Mod] -> Mod;
  77. Site -> Site
  78. end.
  79. api_prefix_to_module(Base) when is_list(Base) ->
  80. case z_utils:ensure_existing_module([$m, $o, $d, $_ | Base]) of
  81. {ok, M} -> M;
  82. {error, _} ->
  83. {ok, M2} = z_utils:ensure_existing_module(Base),
  84. M2
  85. end.
  86. %%
  87. %% All information about a service
  88. %%
  89. serviceinfo(ServiceModule, Context) ->
  90. All = all(info, Context),
  91. case lists:filter(fun(I) -> proplists:get_value(service, I) =:= ServiceModule end, All) of
  92. [Info] -> Info;
  93. _ -> undefined
  94. end.
  95. serviceinfo(Method, ZotonicModule, ServiceModule) ->
  96. ZotonicModuleName = module_to_api_prefix(ZotonicModule),
  97. [ {method, string:join([ZotonicModuleName, atom_to_list(Method)], "/")},
  98. {module, ZotonicModule},
  99. {service, ServiceModule},
  100. {title, title(ServiceModule)},
  101. {needauth, needauth(ServiceModule)},
  102. {http, string:join([atom_to_list(MM) || MM <- http_methods(ServiceModule)],",")}
  103. ].
  104. %%
  105. %% Whether a service needs an authenticated user. Defaults to false.
  106. %%
  107. needauth(Service) ->
  108. module_attr(Service, svc_needauth, false, atom).
  109. %%
  110. %% Title of the service
  111. %%
  112. title(Service) ->
  113. module_attr(Service, svc_title, "(untitled)", list).
  114. %%
  115. %% Which HTTP methods does this API support?
  116. %%
  117. http_methods(Service) ->
  118. F = Service:module_info(functions),
  119. lists:filter(fun (M) -> lists:member(handler(M), F) end, ['GET', 'POST', 'HEAD', 'PUT', 'DELETE']).
  120. %% define the handler mapping for the module.
  121. handler('POST') ->
  122. {process_post, 2};
  123. handler('GET') ->
  124. {process_get, 2};
  125. handler('HEAD') ->
  126. {process_get, 2};
  127. handler('PUT') ->
  128. {process_post, 2};
  129. handler('DELETE') ->
  130. {process_post, 2}.
  131. module_attr(Service, Attr, Default, T) ->
  132. Info = Service:module_info(attributes),
  133. V = proplists:get_value(Attr, Info),
  134. case T of
  135. list ->
  136. case V of
  137. undefined -> Default;
  138. V -> V
  139. end;
  140. atom ->
  141. case V of
  142. [X] -> X;
  143. _ -> Default
  144. end
  145. end.
  146. %%
  147. %% Whether a services applies to a pattern. applies(Pattern, Service).
  148. %%
  149. applies([Pattern|Rest], ServiceMethod) when is_list(Pattern) ->
  150. applies(Pattern, ServiceMethod) orelse applies(Rest, ServiceMethod);
  151. applies(Pattern, ServiceMethod) ->
  152. applies1(string:tokens(Pattern, "/"), string:tokens(ServiceMethod, "/")).
  153. applies1(["*"], _) ->
  154. true;
  155. applies1([], _) ->
  156. false;
  157. applies1([Part], [Part]) ->
  158. true;
  159. applies1([Part|Rest], [Part|Rest2]) ->
  160. applies1(Rest, Rest2);
  161. applies1(_, _) ->
  162. false.