|
@@ -1,40 +1,46 @@
|
|
|
-module(rest_cowboy).
|
|
|
-author('Dmitry Bushmelev').
|
|
|
-record(st, {resource_module = undefined :: atom(), resource_id = undefined :: binary()}).
|
|
|
--export([init/3, rest_init/2, resource_exists/2, allowed_methods/2, content_types_provided/2,
|
|
|
+-export([init/2, rest_init/2, resource_exists/2, allowed_methods/2, content_types_provided/2,
|
|
|
to_html/2, to_json/2, content_types_accepted/2, delete_resource/2,
|
|
|
handle_urlencoded_data/2, handle_json_data/2]).
|
|
|
|
|
|
-init(_, _, _) -> {upgrade, protocol, cowboy_rest}.
|
|
|
+init(Req, Opts) -> {cowboy_rest, Req, Opts}.
|
|
|
|
|
|
-ifndef(REST_JSON).
|
|
|
-define(REST_JSON, (application:get_env(rest,json,jsone))).
|
|
|
-endif.
|
|
|
|
|
|
+c(X) -> list_to_atom(binary_to_list(X)).
|
|
|
+
|
|
|
rest_init(Req, _Opts) ->
|
|
|
{Resource, Req1} = cowboy_req:binding(resource, Req),
|
|
|
Module = case rest_module(Resource) of {ok, M} -> M; _ -> undefined end,
|
|
|
{Id, Req2} = cowboy_req:binding(id, Req1),
|
|
|
{Origin, Req3} = cowboy_req:header(<<"origin">>, Req2, <<"*">>),
|
|
|
Req4 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Origin">>, Origin, Req3),
|
|
|
+ io:format("REST INIT~p"),
|
|
|
{ok, Req4, #st{resource_module = Module, resource_id = Id}}.
|
|
|
|
|
|
-resource_exists(Req, #st{resource_module = undefined} = State) -> {false, Req, State};
|
|
|
-resource_exists(Req, #st{resource_id = <<"undefined">>} = State) -> {true, Req, State};
|
|
|
-resource_exists(Req, #st{resource_module = M, resource_id = Id} = S) -> {M:exists(Id), Req, S}.
|
|
|
+resource_exists(#{bindings := #{resource := _}} = Req, State) -> {false, Req, State};
|
|
|
+resource_exists(#{bindings := #{id := _}} = Req, State) -> {true, Req, State};
|
|
|
+resource_exists(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
|
|
|
+ M = c(Module),
|
|
|
+ {M:exists(Id), Req, State}.
|
|
|
|
|
|
allowed_methods(Req, #st{resource_id = <<"undefined">>} = State) -> {[<<"GET">>, <<"POST">>], Req, State};
|
|
|
allowed_methods(Req, State) -> {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}.
|
|
|
|
|
|
-content_types_provided(Req, #st{resource_module = M} = State) ->
|
|
|
- {case erlang:function_exported(M, to_html, 1) of
|
|
|
- true -> [{<<"text/html">>, to_html}, {<<"application/json">>, to_json}];
|
|
|
- false -> [{<<"application/json">>, to_json}] end,
|
|
|
+content_types_provided(#{bindings := #{resource := Module}} = Req, State) ->
|
|
|
+ {case erlang:function_exported(c(Module), to_html, 1) of
|
|
|
+ false -> [{<<"application/json">>, to_json}];
|
|
|
+ true -> [{<<"text/html">>, to_html}, {<<"application/json">>, to_json}] end,
|
|
|
Req, State}.
|
|
|
|
|
|
-to_html(Req, #st{resource_module = M, resource_id = Id} = State) ->
|
|
|
+to_html(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
|
|
|
+ M = c(Module),
|
|
|
Body = case Id of
|
|
|
- <<"undefined">> -> [M:to_html(Resource) || Resource <- M:get()];
|
|
|
+ undefined -> [M:to_html(Resource) || Resource <- M:get()];
|
|
|
_ -> M:to_html(M:get(Id)) end,
|
|
|
Html = case erlang:function_exported(M, html_layout, 2) of
|
|
|
true -> M:html_layout(Req, Body);
|
|
@@ -43,37 +49,53 @@ to_html(Req, #st{resource_module = M, resource_id = Id} = State) ->
|
|
|
|
|
|
default_html_layout(Body) -> [<<"<html><body>">>, Body, <<"</body></html>">>].
|
|
|
|
|
|
-to_json(Req, #st{resource_module = M, resource_id = Id} = State) ->
|
|
|
+to_json(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
|
|
|
+ M = c(Module),
|
|
|
Struct = case Id of
|
|
|
- <<"undefined">> -> [{M, [ M:to_json(Resource) || Resource <- M:get() ] } ];
|
|
|
+ undefined -> [{M, [ M:to_json(Resource) || Resource <- M:get() ] } ];
|
|
|
_ -> M:to_json(M:get(Id)) end,
|
|
|
{iolist_to_binary(?REST_JSON:encode(Struct)), Req, State}.
|
|
|
|
|
|
-content_types_accepted(Req, State) -> {[{<<"application/x-www-form-urlencoded">>, handle_urlencoded_data},
|
|
|
- {<<"application/json">>, handle_json_data}], Req, State}.
|
|
|
+content_types_accepted(Req, State) ->
|
|
|
+ {[{<<"application/x-www-form-urlencoded">>, handle_urlencoded_data},
|
|
|
+ {<<"application/json">>, handle_json_data}], Req, State}.
|
|
|
+
|
|
|
+handle_urlencoded_data(#{bindings := #{resource := Module}} = Req0, State) ->
|
|
|
+ {ok, Data1, Req} = cowboy_req:read_urlencoded_body(Req0),
|
|
|
+ io:format("FORM: ~p, Data1: ~p~n",[Module,Data1]),
|
|
|
+ {handle_data(c(Module), [], Data1, Req), Req, State};
|
|
|
|
|
|
-handle_urlencoded_data(Req, #st{resource_module = M, resource_id = Id} = State) ->
|
|
|
- {ok, Data, Req2} = cowboy_req:body_qs(Req),
|
|
|
- {handle_data(M, Id, Data), Req2, State}.
|
|
|
+handle_urlencoded_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
|
|
|
+ {ok, Data, Req2} = cowboy_req:read_urlencoded_body(Req),
|
|
|
+ io:format("FORM: ~p~n",[Data]),
|
|
|
+ {handle_data(c(Module), Id, Data, Req), Req2, State}.
|
|
|
+
|
|
|
+handle_json_data(#{bindings := #{resource := Module}} = Req, State) ->
|
|
|
+ {ok, Binary, Req2} = cowboy_req:read_body(Req),
|
|
|
+ io:format("JSON: ~p~n",[Binary]),
|
|
|
+ Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
|
|
|
+ {handle_data(c(Module), [], Data, Req), Req2, State};
|
|
|
|
|
|
-handle_json_data(Req, #st{resource_module = M, resource_id = Id} = State) ->
|
|
|
- {ok, Binary, Req2} = cowboy_req:body(Req),
|
|
|
+handle_json_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
|
|
|
+ {ok, Binary, Req2} = cowboy_req:read_body(Req),
|
|
|
+ io:format("JSON: ~p~n",[Binary]),
|
|
|
Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
|
|
|
- {handle_data(M, Id, Data), Req2, State}.
|
|
|
+ {handle_data(c(Module), Id, Data, Req), Req2, State}.
|
|
|
|
|
|
-handle_data(Mod, Id, Data) ->
|
|
|
+handle_data(Mod, Id, Data, Req) ->
|
|
|
Valid = case erlang:function_exported(Mod, validate, 2) of
|
|
|
true -> Mod:validate(Id, Data);
|
|
|
- false -> default_validate(Mod, Id, Data) end,
|
|
|
+ false -> default_validate(Mod, Id, Data, Req) end,
|
|
|
case {Valid, Id} of
|
|
|
{false, _} -> false;
|
|
|
{true, <<"undefined">>} -> Mod:post(Data);
|
|
|
{true, _} -> case erlang:function_exported(Mod, put, 2) of
|
|
|
true -> Mod:put(Id, Data);
|
|
|
- false -> default_put(Mod, Id, Data) end
|
|
|
+ false -> default_put(Mod, Id, Data, Req) end
|
|
|
end.
|
|
|
|
|
|
-default_put(Mod, Id, Data) ->
|
|
|
+default_put(Mod, Id, Data, Req) when is_map(Data) -> default_put(Mod, Id, maps:to_list(Data), Req);
|
|
|
+default_put(Mod, Id, Data, Req) ->
|
|
|
NewRes = Mod:from_json(Data, Mod:get(Id)),
|
|
|
NewId = proplists:get_value(id, Mod:to_json(NewRes)),
|
|
|
case Id =/= NewId of
|
|
@@ -81,21 +103,23 @@ default_put(Mod, Id, Data) ->
|
|
|
false -> true end,
|
|
|
Mod:post(NewRes).
|
|
|
|
|
|
-default_validate(Mod, Id, Data) ->
|
|
|
+default_validate(Mod, Id, DataX, Req0) when is_map(DataX) -> default_validate(Mod, Id, maps:to_list(DataX), Req0);
|
|
|
+default_validate(Mod, Id, Data, Req0) ->
|
|
|
Allowed = case erlang:function_exported(Mod, keys_allowed, 1) of
|
|
|
- true -> Mod:keys_allowed(proplists:get_keys(Data));
|
|
|
- false -> true end,
|
|
|
+ true -> Mod:keys_allowed(proplists:get_keys(Data));
|
|
|
+ false -> true end,
|
|
|
validate_match(Mod, Id, Allowed, proplists:get_value(<<"id">>, Data)).
|
|
|
|
|
|
-validate_match(_Mod, <<"undefined">>, true, <<"undefined">>) -> false;
|
|
|
-validate_match(_Mod, <<"undefined">>, true, <<"">>) -> false;
|
|
|
-validate_match( Mod, <<"undefined">>, true, NewId) -> not Mod:exists(NewId);
|
|
|
-validate_match(_Mod, _Id, true, <<"undefined">>) -> true;
|
|
|
-validate_match(_Mod, Id, true, Id) -> true;
|
|
|
-validate_match( Mod, _Id, true, NewId) -> not Mod:exists(NewId);
|
|
|
-validate_match( _, _, _, _) -> false.
|
|
|
+validate_match(_Mod, [], true, []) -> false;
|
|
|
+validate_match( Mod, [], true, NewId) -> not Mod:exists(NewId);
|
|
|
+validate_match(_Mod, _Id, true, []) -> true;
|
|
|
+validate_match(_Mod, Id, true, Id) -> true;
|
|
|
+validate_match( Mod, _Id, true, NewId) -> not Mod:exists(NewId);
|
|
|
+validate_match( _, _, _, _) -> false.
|
|
|
|
|
|
-delete_resource(Req, #st{resource_module = M, resource_id = Id} = State) -> {M:delete(Id), Req, State}.
|
|
|
+delete_resource(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
|
|
|
+ M = c(Module),
|
|
|
+ {M:delete(Id), Req, State}.
|
|
|
|
|
|
rest_module(Module) when is_binary(Module) -> rest_module(binary_to_list(Module));
|
|
|
rest_module(Module) ->
|