./ct_report/coverage/mongoose_api.COVER.html

1 %%==============================================================================
2 %% Copyright 2014 Erlang Solutions Ltd.
3 %%
4 %% Licensed under the Apache License, Version 2.0 (the "License");
5 %% you may not use this file except in compliance with the License.
6 %% You may obtain a copy of the License at
7 %%
8 %% http://www.apache.org/licenses/LICENSE-2.0
9 %%
10 %% Unless required by applicable law or agreed to in writing, software
11 %% distributed under the License is distributed on an "AS IS" BASIS,
12 %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 %% See the License for the specific language governing permissions and
14 %% limitations under the License.
15 %%==============================================================================
16 -module(mongoose_api).
17
18 -behaviour(cowboy_rest).
19
20 %% ejabberd_cowboy callbacks
21 -export([cowboy_router_paths/2]).
22
23 %% cowboy_rest callbacks
24 -export([init/2,
25 terminate/3,
26 allowed_methods/2,
27 content_types_provided/2,
28 content_types_accepted/2,
29 delete_resource/2]).
30
31 -export([to_xml/2,
32 to_json/2,
33 from_json/2]).
34
35 -ignore_xref([behaviour_info/1, cowboy_router_paths/2, from_json/2, to_json/2, to_xml/2]).
36
37 -record(state, {handler, opts, bindings}).
38
39 -type prefix() :: string().
40 -type route() :: {string(), options()}.
41 -type routes() :: [route()].
42 -type bindings() :: proplists:proplist().
43 -type options() :: [any()].
44 -type method() :: get | post | put | patch | delete.
45 -type methods() :: [method()].
46 -type response() :: ok | {ok, any()} | {error, atom()}.
47 -export_type([prefix/0, routes/0, route/0, bindings/0, options/0, response/0, methods/0]).
48
49 -callback prefix() -> prefix().
50 -callback routes() -> routes().
51 -callback handle_options(bindings(), options()) -> methods().
52 -callback handle_get(bindings(), options()) -> response().
53 -callback handle_post(term(), bindings(), options()) -> response().
54 -callback handle_put(term(), bindings(), options()) -> response().
55 -callback handle_delete(bindings(), options()) -> response().
56
57 %%--------------------------------------------------------------------
58 %% ejabberd_cowboy callbacks
59 %%--------------------------------------------------------------------
60 cowboy_router_paths(Base, Opts) ->
61 120 Handlers = gen_mod:get_opt(handlers, Opts, []),
62 120 lists:flatmap(pa:bind(fun register_handler/2, Base), Handlers).
63
64 register_handler(Base, Handler) ->
65 240 [{[Base, Handler:prefix(), Path], ?MODULE, [{handler, Handler}|Opts]}
66 240 || {Path, Opts} <- Handler:routes()].
67
68 %%--------------------------------------------------------------------
69 %% cowboy_rest callbacks
70 %%--------------------------------------------------------------------
71 init(Req, Opts) ->
72 626 case lists:keytake(handler, 1, Opts) of
73 {value, {handler, Handler}, Opts1} ->
74 626 Bindings = maps:to_list(cowboy_req:bindings(Req)),
75 626 State = #state{handler=Handler, opts=Opts1, bindings=Bindings},
76 626 {cowboy_rest, Req, State}; % upgrade protocol
77 false ->
78
:-(
erlang:throw(no_handler_defined)
79 end.
80
81 terminate(_Reason, _Req, _State) ->
82 626 ok.
83
84 allowed_methods(Req, #state{bindings=Bindings, opts=Opts}=State) ->
85 626 case call(handle_options, [Bindings, Opts], State) of
86 no_call ->
87
:-(
allowed_methods_from_exports(Req, State);
88 Methods ->
89 626 allowed_methods_from_module(Methods, Req, State)
90 end.
91
92 content_types_provided(Req, State) ->
93 624 CTP = [{{<<"application">>, <<"json">>, '*'}, to_json},
94 {{<<"application">>, <<"xml">>, '*'}, to_xml}],
95 624 {CTP, Req, State}.
96
97 content_types_accepted(Req, State) ->
98 32 CTA = [{{<<"application">>, <<"json">>, '*'}, from_json}],
99 32 {CTA, Req, State}.
100
101 delete_resource(Req, State) ->
102 21 handle_delete(Req, State).
103
104 %%--------------------------------------------------------------------
105 %% content_types_provided/2 callbacks
106 %%--------------------------------------------------------------------
107 to_json(Req, State) ->
108 571 handle_get(mongoose_api_json, Req, State).
109
110 to_xml(Req, State) ->
111
:-(
handle_get(mongoose_api_xml, Req, State).
112
113 %%--------------------------------------------------------------------
114 %% content_types_accepted/2 callbacks
115 %%--------------------------------------------------------------------
116 from_json(Req, State) ->
117 32 handle_unsafe(mongoose_api_json, Req, State).
118
119 %%--------------------------------------------------------------------
120 %% HTTP verbs handlers
121 %%--------------------------------------------------------------------
122 handle_get(Serializer, Req, #state{opts=Opts, bindings=Bindings}=State) ->
123 571 Result = call(handle_get, [Bindings, Opts], State),
124 571 handle_result(Result, Serializer, Req, State).
125
126 handle_unsafe(Deserializer, Req, State) ->
127 32 Method = cowboy_req:method(Req),
128 32 {ok, Body, Req1} = cowboy_req:read_body(Req),
129 32 case Deserializer:deserialize(Body) of
130 {ok, Data} ->
131 31 handle_unsafe(Method, Data, Req1, State);
132 {error, _Reason} ->
133 1 error_response(bad_request, Req1, State)
134 end.
135
136 handle_unsafe(Method, Data, Req, #state{opts=Opts, bindings=Bindings}=State) ->
137 31 case method_callback(Method) of
138 not_implemented ->
139
:-(
error_response(not_implemented, Req, State);
140 Callback ->
141 31 Result = call(Callback, [Data, Bindings, Opts], State),
142 31 handle_result(Result, Req, State)
143 end.
144
145 handle_delete(Req, #state{opts=Opts, bindings=Bindings}=State) ->
146 21 Result = call(handle_delete, [Bindings, Opts], State),
147 21 handle_result(Result, Req, State).
148
149 %%--------------------------------------------------------------------
150 %% Helpers
151 %%--------------------------------------------------------------------
152 handle_result({ok, Result}, Serializer, Req, State) ->
153 568 serialize(Result, Serializer, Req, State);
154 handle_result(Other, _Serializer, Req, State) ->
155 3 handle_result(Other, Req, State).
156
157 handle_result(ok, Req, State) ->
158 50 {true, Req, State};
159 handle_result({error, Error}, Req, State) ->
160 5 error_response(Error, Req, State);
161 handle_result(no_call, Req, State) ->
162
:-(
error_response(not_implemented, Req, State).
163
164 allowed_methods_from_module(Methods, Req, State) ->
165 626 Methods1 = case lists:member(get, Methods) of
166 573 true -> [head | Methods];
167 53 false -> Methods
168 end,
169 626 Methods2 = [options | Methods1],
170 626 {methods_to_binary(Methods2), Req, State}.
171
172 allowed_methods_from_exports(Req, #state{handler=Handler}=State) ->
173
:-(
Exports = Handler:module_info(exports),
174
:-(
Methods = lists:foldl(fun collect_allowed_methods/2, [options], Exports),
175
:-(
{methods_to_binary(Methods), Req, State}.
176
177 collect_allowed_methods({handle_get, 2}, Acc) ->
178
:-(
[head, get | Acc];
179 collect_allowed_methods({handle_post, 3}, Acc) ->
180
:-(
[post | Acc];
181 collect_allowed_methods({handle_put, 3}, Acc) ->
182
:-(
[put | Acc];
183 collect_allowed_methods({handle_delete, 2}, Acc) ->
184
:-(
[delete | Acc];
185 collect_allowed_methods(_Other, Acc) ->
186
:-(
Acc.
187
188 serialize(Data, Serializer, Req, State) ->
189 568 {Serializer:serialize(Data), Req, State}.
190
191 call(Function, Args, #state{handler=Handler}) ->
192 1249 try
193 1249 apply(Handler, Function, Args)
194 catch error:undef ->
195
:-(
no_call
196 end.
197
198 %%--------------------------------------------------------------------
199 %% Error responses
200 %%--------------------------------------------------------------------
201 error_response(Code, Req, State) when is_integer(Code) ->
202 6 Req1 = cowboy_req:reply(Code, Req),
203 6 {stop, Req1, State};
204 error_response(Reason, Req, State) ->
205 6 error_response(error_code(Reason), Req, State).
206
207 1 error_code(bad_request) -> 400;
208 4 error_code(not_found) -> 404;
209
:-(
error_code(conflict) -> 409;
210 1 error_code(unprocessable) -> 422;
211
:-(
error_code(not_implemented) -> 501.
212
213 methods_to_binary(Methods) ->
214 626 [method_to_binary(Method) || Method <- Methods].
215
216 573 method_to_binary(get) -> <<"GET">>;
217
:-(
method_to_binary(post) -> <<"POST">>;
218 53 method_to_binary(put) -> <<"PUT">>;
219 53 method_to_binary(delete) -> <<"DELETE">>;
220
:-(
method_to_binary(patch) -> <<"PATCH">>;
221 626 method_to_binary(options) -> <<"OPTIONS">>;
222 573 method_to_binary(head) -> <<"HEAD">>.
223
224
:-(
method_callback(<<"POST">>) -> handle_post;
225 31 method_callback(<<"PUT">>) -> handle_put;
226
:-(
method_callback(_Other) -> not_implemented.
Line Hits Source