rest_cowboy.erl 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. -module(rest_cowboy).
  2. -author('Dmitry Bushmelev').
  3. -record(st, {resource_module = undefined :: atom(), resource_id = undefined :: binary()}).
  4. -export([init/2, rest_init/2, resource_exists/2, allowed_methods/2, content_types_provided/2,
  5. to_html/2, to_json/2, content_types_accepted/2, delete_resource/2,
  6. handle_urlencoded_data/2, handle_json_data/2]).
  7. init(Req, Opts) -> {cowboy_rest, Req, Opts}.
  8. -ifndef(REST_JSON).
  9. -define(REST_JSON, (application:get_env(rest,json,jsone))).
  10. -endif.
  11. c(X) -> list_to_atom(binary_to_list(X)).
  12. rest_init(Req, _Opts) ->
  13. {Resource, Req1} = cowboy_req:binding(resource, Req),
  14. Module = case rest_module(Resource) of {ok, M} -> M; _ -> undefined end,
  15. {Id, Req2} = cowboy_req:binding(id, Req1),
  16. {Origin, Req3} = cowboy_req:header(<<"origin">>, Req2, <<"*">>),
  17. Req4 = cowboy_req:set_resp_header(<<"Access-Control-Allow-Origin">>, Origin, Req3),
  18. io:format("REST INIT~p"),
  19. {ok, Req4, #st{resource_module = Module, resource_id = Id}}.
  20. resource_exists(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  21. M = c(Module),
  22. io:format("EXISTS: ~p dymamic: ~p~n",[Id,M:exists(Id)]),
  23. {M:exists(Id), Req, State};
  24. resource_exists(#{bindings := #{resource := _}} = Req, State) -> io:format("EXISTS: false~n"), {false, Req, State};
  25. resource_exists(#{bindings := #{id := _}} = Req, State) -> io:format("EXISTS: true~n"), {true, Req, State}.
  26. allowed_methods(#{bindings := #{resource := _}} = Req, State) -> {[<<"GET">>, <<"POST">>], Req, State};
  27. allowed_methods(#{bindings := #{resource := _, id := _}} = Req, State) -> {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}.
  28. content_types_provided(#{bindings := #{resource := Module}} = Req, State) ->
  29. {case erlang:function_exported(c(Module), to_html, 1) of
  30. false -> [{<<"application/json">>, to_json}];
  31. true -> [{<<"text/html">>, to_html}, {<<"application/json">>, to_json}] end,
  32. Req, State}.
  33. to_html(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  34. M = c(Module),
  35. Body = case Id of
  36. undefined -> [M:to_html(Resource) || Resource <- M:get()];
  37. _ -> M:to_html(M:get(Id)) end,
  38. Html = case erlang:function_exported(M, html_layout, 2) of
  39. true -> M:html_layout(Req, Body);
  40. false -> default_html_layout(Body) end,
  41. {Html, Req, State}.
  42. default_html_layout(Body) -> [<<"<html><body>">>, Body, <<"</body></html>">>].
  43. to_json(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  44. M = c(Module),
  45. Struct = case Id of
  46. undefined -> [{M, [ M:to_json(Resource) || Resource <- M:get() ] } ];
  47. _ -> M:to_json(M:get(Id)) end,
  48. {iolist_to_binary(?REST_JSON:encode(Struct)), Req, State}.
  49. content_types_accepted(Req, State) ->
  50. {[{<<"application/x-www-form-urlencoded">>, handle_urlencoded_data},
  51. {<<"application/json">>, handle_json_data}], Req, State}.
  52. handle_urlencoded_data(#{bindings := #{resource := Module}} = Req0, State) ->
  53. {ok, Data1, Req} = cowboy_req:read_urlencoded_body(Req0),
  54. io:format("FORM: ~p, Data1: ~p~n",[Module,Data1]),
  55. {handle_data(c(Module), [], Data1, Req), Req, State};
  56. handle_urlencoded_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  57. {ok, Data, Req2} = cowboy_req:read_urlencoded_body(Req),
  58. io:format("FORM: ~p~n",[Data]),
  59. {handle_data(c(Module), Id, Data, Req), Req2, State}.
  60. handle_json_data(#{bindings := #{resource := Module}} = Req, State) ->
  61. {ok, Binary, Req2} = cowboy_req:read_body(Req),
  62. io:format("JSON: ~p~n",[Binary]),
  63. Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
  64. {handle_data(c(Module), [], Data, Req), Req2, State};
  65. handle_json_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  66. {ok, Binary, Req2} = cowboy_req:read_body(Req),
  67. io:format("JSON: ~p~n",[Binary]),
  68. Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
  69. {handle_data(c(Module), Id, Data, Req), Req2, State}.
  70. handle_data(Mod, Id, Data, Req) ->
  71. Valid = case erlang:function_exported(Mod, validate, 2) of
  72. true -> Mod:validate(Id, Data);
  73. false -> default_validate(Mod, Id, Data, Req) end,
  74. case {Valid, Id} of
  75. {false, _} -> false;
  76. {true, <<"undefined">>} -> Mod:post(Data);
  77. {true, _} -> case erlang:function_exported(Mod, put, 2) of
  78. true -> Mod:put(Id, Data);
  79. false -> default_put(Mod, Id, Data, Req) end
  80. end.
  81. default_put(Mod, Id, Data, Req) when is_map(Data) -> default_put(Mod, Id, maps:to_list(Data), Req);
  82. default_put(Mod, Id, Data, Req) ->
  83. NewRes = Mod:from_json(Data, Mod:get(Id)),
  84. NewId = proplists:get_value(id, Mod:to_json(NewRes)),
  85. io:format("Id ~p NewId ~p~n",[Id,NewId]),
  86. case Id =/= NewId of
  87. true when Id =:= [] -> skip;
  88. true -> Mod:delete(Id);
  89. false -> true end,
  90. Mod:post(NewRes).
  91. default_validate(Mod, Id, DataX, Req0) when is_map(DataX) -> default_validate(Mod, Id, maps:to_list(DataX), Req0);
  92. default_validate(Mod, Id, Data, Req0) ->
  93. Allowed = case erlang:function_exported(Mod, keys_allowed, 1) of
  94. true -> Mod:keys_allowed(proplists:get_keys(Data));
  95. false -> true end,
  96. validate_match(Mod, Id, Allowed, proplists:get_value(<<"id">>, Data)).
  97. validate_match(_Mod, [], true, []) -> false;
  98. validate_match( Mod, [], true, NewId) -> not Mod:exists(NewId);
  99. validate_match(_Mod, _Id, true, []) -> true;
  100. validate_match(_Mod, Id, true, Id) -> true;
  101. validate_match( Mod, _Id, true, NewId) -> not Mod:exists(NewId);
  102. validate_match( _, _, _, _) -> false.
  103. delete_resource(#{bindings := #{resource := Module, id := []}} = Req, State) -> {[], Req, State};
  104. delete_resource(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  105. M = c(Module),
  106. io:format("DELETE: ~p ~p ~p~n",[M,Id,M:delete(Id)]),
  107. {M:delete(Id), Req, State}.
  108. rest_module(Module) when is_binary(Module) -> rest_module(binary_to_list(Module));
  109. rest_module(Module) ->
  110. try M = list_to_existing_atom(Module),
  111. Info = proplists:get_value(attributes, M:module_info()),
  112. true = lists:member(rest, proplists:get_value(behaviour, Info)),
  113. {ok, M}
  114. catch error:Error -> {error, Error} end.