rest_kvs.erl 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. -module(rest_kvs).
  2. -include_lib("kvs/include/kvs.hrl").
  3. -compile(export_all).
  4. -export([exists/1, exists/2, new/1, get/1, delete/2, post/2, post/3]).
  5. -export([init/2, resource_exists/2, allowed_methods/2, content_types_provided/2,
  6. to_html/2, to_json/2, content_types_accepted/2, delete_resource/2,
  7. handle_urlencoded_data/2, handle_json_data/2]).
  8. -ifndef(REST_JSON).
  9. -define(REST_JSON, (application:get_env(rest,json,jsone))).
  10. -endif.
  11. c(X) when is_tuple(X) -> Module = hd(tuple_to_list(X)), Module:uri(X);
  12. c(X) -> binary_to_list(X).
  13. % kvs rest api
  14. new(Type) -> Type:new().
  15. exists(Mod) -> {X,_} = kvs:get(writer,c(Mod)), X == ok.
  16. exists(Mod,Id) -> {X,_} = kvs:get(c(Mod),c(Id)), X == ok.
  17. get(Mod) -> kvs:all(Mod).
  18. get(Mod,Id) -> {X,Y} = kvs:get(c(Mod),c(Id)), X == ok, Y.
  19. delete(Mod,Id) -> kvs:delete(Mod,Id).
  20. post(Mod,Resource) when is_tuple(Resource) -> kvs:append(Mod,Resource).
  21. post(Type,Mod,Data) when is_list(Data) -> post(Mod,Type:from_json(new(Type))).
  22. % cowboy rest api
  23. update_req(Req) -> #{ bindings := Bindings, path_info := List } = Req,
  24. Req#{bindings => Bindings#{ resource => list_to_binary("/" ++
  25. string:join(lists:map(fun (X) -> binary_to_list(X) end, List),"/")) }}.
  26. init(#{bindings := #{id := Id}} = Req, State) -> {cowboy_rest, update_req(Req), State};
  27. init(Req, State) -> {cowboy_rest, update_req(Req), State}.
  28. parse_id(Id) ->
  29. List = binary_to_list(Id),
  30. Parsed = case string:tokens(List,",") of
  31. [X] -> Id;
  32. _ -> rest:parse(lists:concat(["{",List,"}"]))
  33. end.
  34. resource_exists(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  35. {rest_kvs:exists(Module,parse_id(Id)), Req, State};
  36. resource_exists(#{bindings := #{resource := Module}} = Req, State) -> {rest_kvs:exists(Module), Req, State};
  37. resource_exists(#{bindings := #{id := _}} = Req, State) -> {true, Req, State}.
  38. allowed_methods(#{bindings := #{resource := _}} = Req, State) -> {[<<"GET">>, <<"POST">>], Req, State};
  39. allowed_methods(#{bindings := #{resource := _, id := _}} = Req, State) -> {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}.
  40. delete_resource(#{bindings := #{resource := Module, id := []}} = Req, State) -> {[], Req, State};
  41. delete_resource(#{bindings := #{resource := Module, id := Id}} = Req, State) -> {rest_kvs:delete(Module,Id), Req, State}.
  42. content_types_provided(#{bindings := #{resource := Module}} = Req, State) ->
  43. {case application:get_env(rest,html,false) of
  44. false -> [{<<"application/json">>, to_json}];
  45. true -> [{<<"text/html">>, to_html},
  46. {<<"application/json">>, to_json}] end, Req, State}.
  47. % TODO: HTML render broken!
  48. to_html(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  49. % Body = case Id of
  50. % Id when Id==[];Id==undefined -> [ rest_kvs:to_html(Module, Resource) || Resource <- rest_kvs:get(Module,Id) ];
  51. % _ -> rest_kvs:to_html(rest_kvs:get(Module,Id)) end,
  52. % Html = case application:get_env(rest,html_layout,false) of
  53. % true -> rest_kvs:html_layout(Module, Req, Body);
  54. % false -> default_html_layout(Body) end,
  55. {<<>>, Req, State}.
  56. default_html_layout(Body) -> [<<"<html><body>">>, Body, <<"</body></html>">>].
  57. % JSON seems fine
  58. to_json(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  59. {ok,Resource} = kvs:get(c(Module),c(parse_id(Id))),
  60. Type = element(1,Resource),
  61. {iolist_to_binary([?REST_JSON:encode(rest:binarize(Type:to_json(Resource))),"\n"]), Req, State};
  62. to_json(#{bindings := #{resource := Module}} = Req, State) ->
  63. Fold = [ begin M = element(1,Resource), rest:binarize(M:to_json(Resource)) end || Resource <- kvs:all(c(Module))],
  64. {iolist_to_binary([?REST_JSON:encode([{Module,Fold}]),"\n"]), Req, State}.
  65. content_types_accepted(Req, State) ->
  66. {[{<<"application/x-www-form-urlencoded">>, handle_urlencoded_data},
  67. {<<"application/json">>, handle_json_data}], Req, State}.
  68. handle_urlencoded_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  69. {ok, Data, Req2} = cowboy_req:read_urlencoded_body(Req),
  70. io:format("FORM: ~p~n",[Data]),
  71. {handle_data(Module, Id, Data, Req), Req2, State};
  72. handle_urlencoded_data(#{bindings := #{resource := Module}} = Req0, State) ->
  73. {ok, Data1, Req} = cowboy_req:read_urlencoded_body(Req0),
  74. io:format("FORM: ~p, Data1: ~p~n",[Module,Data1]),
  75. {handle_data(Module, [], Data1, Req), Req, State}.
  76. handle_json_data(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
  77. {ok, Binary, Req2} = cowboy_req:read_body(Req),
  78. io:format("JSON: ~p~n",[Binary]),
  79. Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
  80. {handle_data(Module, Id, Data, Req), Req2, State};
  81. handle_json_data(#{bindings := #{resource := Module}} = Req, State) ->
  82. {ok, Binary, Req2} = cowboy_req:read_body(Req),
  83. io:format("JSON: ~p~n",[Binary]),
  84. Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
  85. {handle_data(Module, [], Data, Req), Req2, State}.
  86. handle_data(Mod, Id, Data, Req) ->
  87. Type = proplists:get_value(<<"rec">>, Data),
  88. Valid = case application:get_env(rest,validate,false) of
  89. true -> rest_kvs:validate(Mod,Id, Data);
  90. false -> default_validate(Mod, Id, Data, Req) end,
  91. case {Valid, Id} of
  92. {false, _} -> false;
  93. {true, <<"undefined">>} -> rest_kvs:post(Type,Mod,Data);
  94. {true, _} -> case application:get_env(rest,custom_put,false) of
  95. true -> Type:put(Mod, Id, Data);
  96. false -> default_put(Type, Mod, Id, Data, Req) end
  97. end.
  98. validate(_,_,_) -> true.
  99. keys_allowed(_,_) -> true.
  100. default_put(Type, Mod, Id, Data, Req) when is_map(Data) -> default_put(Type, Mod, Id, maps:to_list(Data), Req);
  101. default_put(Type, Mod, Id, Data, Req) ->
  102. NewRes = Type:from_json(Data, rest_kvs:get(Mod,Id)),
  103. NewId = proplists:get_value(id, Type:to_json(NewRes)),
  104. case Id =/= NewId of
  105. true -> rest_kvs:delete(Mod,Id);
  106. false -> true end,
  107. rest_kvs:post(Type,Mod,NewRes).
  108. default_validate(Mod, Id, DataX, Req0) when is_map(DataX) -> default_validate(Mod, Id, maps:to_list(DataX), Req0);
  109. default_validate(Mod, Id, Data, Req0) ->
  110. Allowed = case application:get_env(rest, keys_allowed, false) of
  111. true -> rest_kvs:keys_allowed(c(Mod),proplists:get_keys(Data));
  112. false -> true end,
  113. validate_match(Mod, Id, Allowed, proplists:get_value(<<"id">>, Data)).
  114. validate_match(_Mod, [], true, []) -> false;
  115. validate_match( Mod, [], true, NewId) -> not rest_kvs:exists(Mod,NewId);
  116. validate_match(_Mod, _Id, true, []) -> true;
  117. validate_match(_Mod, Id, true, Id) -> true;
  118. validate_match( Mod, _Id, true, NewId) -> not rest_kvs:exists(Mod,NewId);
  119. validate_match( _, _, _, _) -> false.