221V 1 year ago
parent
commit
6d6d516e05
3 changed files with 271 additions and 150 deletions
  1. 21 18
      README.md
  2. 92 42
      src/rest.erl
  3. 158 90
      src/rest_cowboy.erl

+ 21 - 18
README.md

@@ -17,36 +17,39 @@ Module
 Sample REST service implementation:
 
 ```erlang
--module(users).
--behaviour(rest).
+-module(rest_users).
 -compile({parse_transform, rest}).
--include("users.hrl").
--export([init/0, populate/1, exists/1, get/0, get/1, post/1, delete/1]).
+-export([init/0, populate/1, new/0, exists/1, get/0, get/1, post/1, delete/1]).
+
+-record(user, {id, cn, name, type}).
 -rest_record(user).
 
-init() -> ets:new(users, [public, named_table, {keypos, #user.id}]).
-populate(Users) -> ets:insert(users, Users).
-exists(Id) -> ets:member(users, wf:to_list(Id)).
-get() -> ets:tab2list(users).
-get(Id) -> [User] = ets:lookup(users, wf:to_list(Id)), User.
-delete(Id) -> ets:delete(users, wf:to_list(Id)).
+new()                -> #user{}.
+init()               -> ets:new(users, [public, named_table, {keypos, #user.id}]).
+populate(Users)      -> ets:insert(users, Users).
+exists(Id)           -> X = ets:member(users, erlang:binary_to_list(Id)), io:format("Member: ~p~n", [X]), X.
+get()                -> ets:tab2list(users).
+get(Id)              -> [U] = ets:lookup(users, erlang:binary_to_list(Id)), io:format("User: ~p~n", [U]), U.
+delete(Id)           -> ets:delete(users, erlang:binary_to_list(Id)).
 post(#user{} = User) -> ets:insert(users, User);
-post(Data) -> post(from_json(Data, #user{})).
+post(Data)           -> post(from_json(Data, #user{})).
 ```
 
 Usage
 -----
-
-    curl -i -X POST -d "id=vlad" localhost:8000/rest/users
-    curl -i -X POST -d "id=doxtop" localhost:8000/rest/users
-    curl -i -X GET localhost:8000/rest/users
-    curl -i -X PUT -d "id=5HT" localhost:8000/rest/users/vlad
-    curl -i -X GET localhost:8000/rest/users/5HT
-    curl -i -X DELETE localhost:8000/rest/users/5HT
+```
+curl -i -X POST -d "id=vlad" localhost:8000/rest/users
+curl -i -X POST -d "id=doxtop" localhost:8000/rest/users
+curl -i -X GET localhost:8000/rest/users
+curl -i -X PUT -d "id=5HT" localhost:8000/rest/users/vlad
+curl -i -X GET localhost:8000/rest/users/5HT
+curl -i -X DELETE localhost:8000/rest/users/5HT
+```
 
 Credits
 -------
 
 * Dmitry Bushmelev
+* Maxim Sokhatsky
 
 OM A HUM

+ 92 - 42
src/rest.erl

@@ -1,30 +1,54 @@
 -module(rest).
 -author('Dmitry Bushmelev').
--export([behaviour_info/1, parse_transform/2, generate_to_json/3,
-         generate_from_json/3, from_json/1, to_json/1, to_binary/1]).
+
+-export([behaviour_info/1, parse_transform/2, generate_to_json/3, binarize/1,
+  generate_from_json/3, from_json/2, to_json/1, to_binary/1, parse/1, atomize/1]).
+
 
 behaviour_info(callbacks) -> [{exists, 1}, {get, 0}, {get, 1}, {post, 1}, {delete, 1}, {from_json, 2}, {to_json, 1}];
 behaviour_info(_) -> undefined.
 
 parse_transform(Forms, _Options) ->
-%    io:format("~p~n", [Forms]),
-    RecordName = rest_record(Forms),
-    RecordFields = record_fields(RecordName, Forms),
-    Forms1 = generate({from_json, 2}, RecordName, RecordFields, Forms),
-    Forms2 = generate({to_json, 1}, RecordName, RecordFields, Forms1),
-%    io:format("~p~n", [Forms2]),
-    Forms2.
+  %io:format("~p~n", [Forms]),
+  RecordName = rest_record(Forms),
+  RecordFields = record_fields(RecordName, Forms),
+  Forms1 = generate({from_json, 2}, RecordName, RecordFields, Forms),
+  Forms2 = generate({to_json, 1}, RecordName, RecordFields, Forms1),
+  %io:format("~p~n", [Forms2]),
+  Forms2.
 
 rest_record([]) -> [];
 rest_record([{attribute, _, rest_record, RecordName} | _Forms]) -> RecordName;
 rest_record([_ | Forms]) -> rest_record(Forms).
 
-record_field({record_field, _, {atom, _, Field}   }) -> Field;
-record_field({record_field, _, {atom, _, Field}, _}) -> Field;
-record_field({typed_record_field, {record_field, _, {atom, _, Field}, _}, _}) -> Field.
-
+record_field({record_field, _, {atom, _, Field}   }, _RecordName) ->
+  %io:format("Case 1: ~p~n", [Field]),
+  Field;
+record_field({record_field, _, {atom, _, Field}, _Type}, _RecordName) ->
+  %io:format("Case 2: ~p~n", [Field]),
+  Field;
+record_field({typed_record_field, {record_field, _, {atom, _, Field}, _}, Type}, RecordName) ->
+  Rec = allow(Type),
+  erlang:put({RecordName, Field}, Rec),
+  %case Rec of
+  %  undefined -> io:format("Case 3: ~p~n", [Field]);
+  %          _ -> io:format("Case 3: ~p, Link: ~p~n", [Field, Rec])
+  %end,
+  Field.
+
+allow({type, _, union, Components}) -> findType(Components);
+allow(Type) -> findType([Type]).
+
+findType([]) -> undefined;
+findType([{type, _, record, [{atom, _, X}]}|_T]) -> X;
+findType([{remote_type, _, [{atom, _, _}, {atom, _, X}, _]}|_T]) -> X;
+findType([_H|T]) ->
+  %io:format("Unknown Type: ~p~n", [_H]),
+  findType(T).
+
+record_fields(_RecordName, []) -> [];
 record_fields(RecordName, [{attribute, _, record, {RecordName, Fields}} | _Forms]) ->
-    [record_field(Field) || Field <- Fields];
+    [record_field(Field, RecordName) || Field <- Fields];
 record_fields(RecordName, [_ | Forms]) -> record_fields(RecordName, Forms).
 
 last_export_line(Exports) ->
@@ -57,7 +81,10 @@ from_json_coda(Line) ->
     {clause, Line,
      [{cons, Line, {var, Line, '_'}, {var, Line, 'Json'}}, {var, Line, 'Acc'}],
      [],
-     [{call, Line, {atom, Line, from_json}, [{var, Line, 'Json'}, {var, Line, 'Acc'}]}]}.
+     [{call, Line, {atom, Line, from_json}, [
+     %{var, Line, 'Json'}, % here is fix for recursive binarized preprocessing to raw X:from_json
+     {call, Line, {remote, Line, {atom, Line, ?MODULE}, {atom, Line, binarize}}, [{var, Line, 'Json'}]},
+     {var, Line, 'Acc'}]}]}.
 
 from_json_clauses(_, _, []) -> [];
 from_json_clauses(Line, Record, [Field | Fields]) ->
@@ -79,8 +106,10 @@ from_json_clauses(Line, Record, [Field | Fields]) ->
           [{record_field, Line,
             {atom, Line, Field},
             {call, Line,
-             {remote, Line, {atom, Line, ?MODULE}, {atom, Line, from_json}},
-             [{var, Line, field_var(Field)}]}}]}]}]}
+             {remote, Line, {atom, Line, ?MODULE }, {atom, Line, from_json}},
+             [{var, Line, field_var(Field)},
+              {atom, Line, case erlang:get({Record, Field}) of undefined -> Record; FieldType -> FieldType end}]}
+            }]}]}]}
      | from_json_clauses(Line, Record, Fields)].
 
 generate_from_json({eof, Line}, Record, Fields) ->
@@ -114,35 +143,56 @@ generate_to_json({eof, Line}, Record, Fields) ->
 
 generate_to_json(Form, _, _) -> Form.
 
-from_json(<<Data/binary>>) -> binary_to_list(Data);
-from_json({struct, Props}) -> from_json(Props);
-from_json([{Key, _} | _] = Props) when Key =/= struct -> lists:foldr(fun props_skip/2, [], Props);
-from_json([_|_] = NonEmptyList) -> [from_json(X) || X <- NonEmptyList];
-from_json(Any) -> Any.
-
-props_skip({<<BinaryKey/binary>>, Value}, Acc) ->
-    try Key = list_to_existing_atom(binary_to_list(BinaryKey)),
-        props_skip({Key, Value}, Acc)
-    catch _:_ -> Acc end;
-props_skip({Key, Value}, Acc) -> [{Key, from_json(Value)} | Acc].
-
+from_json(<<Data/binary>>, _) ->
+  erlang:binary_to_list(Data);
+from_json({struct, Props}, X) ->
+  from_json(Props, X);
+from_json([{Key, _}|_] = Props, X) when Key =/= struct ->
+  X:from_json(binarize(Props), X:new());
+from_json(Any, _X) -> Any.
+
+atomize([{Key, _}|_]=Props) when Key =/= struct ->
+  lists:map(fun ({K, V}) when erlang:is_atom(K)   -> {K, V};
+                ({K, V}) when erlang:is_binary(K) -> {erlang:list_to_existing_atom(erlang:binary_to_list(K)), V} end, Props);
+atomize(X) -> X.
+
+binarize([{Key, _}|_]=Props) when Key =/= struct ->
+  lists:map(fun ({K, V}) when erlang:is_atom(K)   -> {erlang:list_to_binary(erlang:atom_to_list(K)), allowed_value(V)};
+                ({K, V}) when erlang:is_binary(K) -> {K, allowed_value(V)} end, Props);
+binarize(X) -> X.
+
+allowed_value(X) when erlang:is_reference(X) -> [];
+allowed_value(X) -> X.
+
+to_json(X) when erlang:is_tuple(X) ->
+  Module = erlang:hd(erlang:tuple_to_list(X)),
+  Module:to_json(X);
 to_json(Data) ->
-    case is_string(Data) of
-        true  -> rest:to_binary(Data);
-        false -> json_match(Data)
-    end.
+  case is_string(Data) of
+    true  -> rest:to_binary(Data);
+    false -> json_match(Data)
+  end.
 
-json_match([{_, _} | _] = Props) -> [{rest:to_binary(Key), to_json(Value)} || {Key, Value} <- Props];
+json_match([{_, _} | _] = Props) ->
+  [{rest:to_binary(Key), to_json(Value)} || {Key, Value} <- Props];
 json_match([_ | _] = NonEmptyList) -> [to_json(X) || X <- NonEmptyList];
 json_match(Any) -> Any.
 
-is_char(C) -> is_integer(C) andalso C >= 0 andalso C =< 255.
+is_char(C) -> erlang:is_integer(C) andalso C >= 0 andalso C =< 255.
+
+is_string([N | _] = PossibleString) when erlang:is_number(N) -> lists:all(fun is_char/1, PossibleString);
+is_string(_)                                                 -> false.
+
+to_binary(A) when erlang:is_atom(A) -> erlang:atom_to_binary(A, latin1);
+to_binary(B) when erlang:is_binary(B) -> B;
+to_binary(I) when erlang:is_integer(I) -> to_binary(erlang:integer_to_list(I));
+to_binary(F) when erlang:is_float(F) -> float_to_binary(F, [{decimals, 9}, compact]);
+to_binary(L) when erlang:is_list(L) ->  erlang:iolist_to_binary(L).
 
-is_string([N | _] = PossibleString) when is_number(N) -> lists:all(fun is_char/1, PossibleString);
-is_string(_)                                          -> false.
+parse(String) ->
+  %{ok, Tokens, _EndLine} = erl_scan:string(String),
+  {ok, Tokens, _EndLine} = erl_scan:string(String ++ "."),
+  {ok, AbsForm} = erl_parse:parse_exprs(Tokens),
+  {value, Value, _Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()),
+  Value.
 
-to_binary(A) when is_atom(A) -> atom_to_binary(A,latin1);
-to_binary(B) when is_binary(B) -> B;
-to_binary(I) when is_integer(I) -> to_binary(integer_to_list(I));
-to_binary(F) when is_float(F) -> float_to_binary(F,[{decimals,9},compact]);
-to_binary(L) when is_list(L) ->  iolist_to_binary(L).

+ 158 - 90
src/rest_cowboy.erl

@@ -1,106 +1,174 @@
 -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}.
+-record(st, {resource_module = undefined :: atom(), resource_id = undefined :: binary()}).
+
+init(Req, Opts) -> {cowboy_rest, Req, Opts}.
 
 -ifndef(REST_JSON).
--define(REST_JSON, (application:get_env(rest,json,jsone))).
+-define(REST_JSON, (application:get_env(rest, json, jsone))).
 -endif.
 
+c(X) -> erlang:list_to_atom(erlang: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),
-    {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}.
-
-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,
-     Req, State}.
-
-to_html(Req, #st{resource_module = M, resource_id = Id} = State) ->
-    Body = case Id of
-               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);
-               false -> default_html_layout(Body) end,
-    {Html, Req, State}.
+  {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~n"),
+  {ok, Req4, #st{resource_module = Module, resource_id = Id}}.
+
+resource_exists(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+  M = c(Module),
+  io:format("EXISTS: ~p dymamic: ~p~n", [Id, M:exists(Id)]),
+  {M:exists(Id), Req, State};
+resource_exists(#{bindings := #{resource := Module}} = Req, State) ->
+  io:format("resource ~p: no-id~n", [Module]),
+  {true, Req, State};
+resource_exists(#{bindings := #{id := _}} = Req, State) ->
+  io:format("EXISTS id: true~n"),
+  {true, Req, State}.
+
+allowed_methods(#{bindings := #{resource := _}} = Req, State) -> {[<<"GET">>, <<"POST">>], Req, State};
+allowed_methods(#{bindings := #{resource := _, id := _}} = Req, State) -> {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}.
+
+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(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+  M = c(Module),
+  Body = case Id of
+    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);
+    false -> default_html_layout(Body)
+  end,
+  {Html, Req, 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) ->
+    io:format("~p ~p ~p~n", [?FUNCTION_NAME, Module, Id]),
+    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}.
-
-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_json_data(Req, #st{resource_module = M, resource_id = Id} = State) ->
-    {ok, Binary, Req2} = cowboy_req:body(Req),
-    Data = case ?REST_JSON:decode(Binary) of {struct, Struct} -> Struct; S -> S end,
-    {handle_data(M, Id, Data), Req2, State}.
-
-handle_data(Mod, Id, Data) ->
-    Valid = case erlang:function_exported(Mod, validate, 2) of
-                true  -> Mod:validate(Id, Data);
-                false -> default_validate(Mod, Id, Data) 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
-    end.
-
-default_put(Mod, Id, Data) ->
-    NewRes = Mod:from_json(Data, Mod:get(Id)),
-    NewId = proplists:get_value(id, Mod:to_json(NewRes)),
-    case Id =/= NewId of
-        true  -> Mod:delete(Id);
-        false -> true end,
-    Mod:post(NewRes).
-
-default_validate(Mod, Id, Data) ->
-    Allowed = case erlang:function_exported(Mod, keys_allowed, 1) of
-                  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.
-
-delete_resource(Req,  #st{resource_module = M, resource_id = Id} = State) -> {M:delete(Id), Req, State}.
-
-rest_module(Module) when is_binary(Module) -> rest_module(binary_to_list(Module));
+    {erlang:iolist_to_binary(?REST_JSON:encode(Struct)), Req, State};
+
+to_json(#{bindings := #{resource := Module}} = Req, State) ->
+    io:format("~p ~p~n", [?FUNCTION_NAME, Module]),
+    M = c(Module),
+    Struct = [{M, [ M:to_json(Resource) || Resource <- M:get() ]}],
+    {erlang: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}.
+
+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(#{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(#{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(c(Module), Id, Data, Req), Req2, State}.
+
+handle_data(Mod, Id, Data, Req) ->
+  io:format("handle_data(~p)~n", [{Mod, Id, Data, Req}]),
+  Valid = case erlang:function_exported(Mod, validate, 2) of
+    true  -> Mod:validate(Id, Data);
+    false -> default_validate(Mod, Id, Data, Req)
+  end,
+  io:format("Valid ~p Id ~p~n", [Valid, Id]),
+  case {Valid, Id} of
+    {false, _}              -> false;
+    {true,  []}             -> Mod:post(Data);
+    {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, Req)
+      end
+  end.
+
+default_put(Mod, Id, Data, Req) when erlang: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)),
+  io:format("Id ~p NewId ~p~n", [Id, NewId]),
+  case Id =/= NewId of
+    true when Id =:= [] -> skip;
+    true -> Mod:delete(Id);
+    false -> true
+  end,
+  Mod:post(NewRes).
+
+default_validate(Mod, Id, DataX, Req0) when erlang: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,
+  validate_match(Mod, Id, Allowed, proplists:get_value(<<"id">>, Data)).
+
+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(#{bindings := #{resource := _Module, id := []}} = Req, State) ->
+  {[], Req, State};
+delete_resource(#{bindings := #{resource := Module, id := Id}} = Req, State) ->
+  M = c(Module),
+  io:format("DELETE: ~p ~p ~p~n", [M, Id, M:delete(Id)]),
+  {M:delete(Id), Req, State}.
+
+rest_module(Module) when erlang:is_binary(Module) ->
+  rest_module(erlang:binary_to_list(Module));
 rest_module(Module) ->
-    try M = list_to_existing_atom(Module),
-        Info = proplists:get_value(attributes, M:module_info()),
-        true = lists:member(rest, proplists:get_value(behaviour, Info)),
-        {ok, M}
-    catch error:Error -> {error, Error} end.
+  try
+    M = erlang:list_to_existing_atom(Module),
+    Info = proplists:get_value(attributes, M:module_info()),
+    true = lists:member(rest, proplists:get_value(behaviour, Info)),
+    {ok, M}
+  catch
+    error:Error -> {error, Error}
+  end.