Skip to content

Commit

Permalink
Merge pull request #13 from inaka/elbrujohalcon.13.add__patch__to_the…
Browse files Browse the repository at this point in the history
…_allowed_met

Add 'patch' to the allowed methods in atom_to_method
  • Loading branch information
David Cao committed Nov 29, 2015
2 parents a41e9c0 + 0bc5f4b commit a2b18f3
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 23 deletions.
3 changes: 2 additions & 1 deletion src/sr_entities_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ announce_req(Req, _Opts) -> Req.
%%% Auxiliary Functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec atom_to_method(get|put|post|delete) -> binary().
-spec atom_to_method(get|patch|put|post|delete) -> binary().
atom_to_method(get) -> <<"GET">>;
atom_to_method(patch) -> <<"PATCH">>;
atom_to_method(put) -> <<"PUT">>;
atom_to_method(post) -> <<"POST">>;
atom_to_method(delete) -> <<"DELETE">>.
31 changes: 26 additions & 5 deletions src/sr_single_entity_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
, content_types_accepted/2
, handle_get/2
, handle_put/2
, handle_patch/2
, delete_resource/2
]).

Expand Down Expand Up @@ -53,7 +54,9 @@ resource_exists(Req, State) ->
-spec content_types_accepted(cowboy_req:req(), state()) ->
{[{{binary(), binary(), '*'}, atom()}], cowboy_req:req(), state()}.
content_types_accepted(Req, State) ->
{[{{<<"application">>, <<"json">>, '*'}, handle_put}], Req, State}.
{Method, Req1} = cowboy_req:method(Req),
Function = method_function(Method),
{[{{<<"application">>, <<"json">>, '*'}, Function}], Req1, State}.

-spec handle_get(cowboy_req:req(), state()) ->
{iodata(), cowboy_req:req(), state()}.
Expand All @@ -62,14 +65,28 @@ handle_get(Req, State) ->
ResBody = sr_json:encode(Model:to_json(Entity)),
{ResBody, Req, State}.

-spec handle_patch(cowboy_req:req(), state()) ->
{{true, binary()} | false | halt, cowboy_req:req(), state()}.
handle_patch(Req, #{entity := Entity} = State) ->
#{opts := #{model := Model}} = State,
try
{ok, Body, Req1} = cowboy_req:body(Req),
Json = sr_json:decode(Body),
persist(Model:update(Entity, Json), Req1, State)
catch
_:badjson ->
Req3 = cowboy_req:set_resp_body(<<"Malformed JSON request">>, Req),
{false, Req3, State}
end.

-spec handle_put(cowboy_req:req(), state()) ->
{{true, binary()} | false | halt, cowboy_req:req(), state()}.
handle_put(Req, #{entity := Entity} = State) ->
#{opts := #{model := Model}} = State,
try
{ok, Body, Req1} = cowboy_req:body(Req),
Json = sr_json:decode(Body),
handle_put(Model:update(Entity, Json), Req1, State)
persist(Model:update(Entity, Json), Req1, State)
catch
_:badjson ->
Req3 = cowboy_req:set_resp_body(<<"Malformed JSON request">>, Req),
Expand All @@ -80,7 +97,7 @@ handle_put(Req, #{id := Id} = State) ->
try
{ok, Body, Req1} = cowboy_req:body(Req),
Json = sr_json:decode(Body),
handle_put(from_json(Model, Id, Json), Req1, State)
persist(from_json(Model, Id, Json), Req1, State)
catch
_:badjson ->
Req3 = cowboy_req:set_resp_body(<<"Malformed JSON request">>, Req),
Expand All @@ -103,12 +120,16 @@ from_json(Model, Id, Json) ->
_:undef -> Model:from_json(Json)
end.

handle_put({error, Reason}, Req, State) ->
persist({error, Reason}, Req, State) ->
Req1 = cowboy_req:set_resp_body(Reason, Req),
{false, Req1, State};
handle_put({ok, Entity}, Req1, State) ->
persist({ok, Entity}, Req1, State) ->
#{opts := #{model := Model}} = State,
PersistedEntity = sumo:persist(Model, Entity),
ResBody = sr_json:encode(Model:to_json(PersistedEntity)),
Req2 = cowboy_req:set_resp_body(ResBody, Req1),
{true, Req2, State}.

-spec method_function(binary()) -> atom().
method_function(<<"PUT">>) -> handle_put;
method_function(<<"PATCH">>) -> handle_patch.
51 changes: 39 additions & 12 deletions test/sr_elements_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ success_scenario(_Config) ->
sr_test_utils:api_call(
put, "/elements/element1", Headers,
#{ key => <<"element1">>
, value => <<"newval1">>
, value => <<"newval3">>
}),
#{ <<"key">> := <<"element1">>
, <<"value">> := <<"newval3">>
, <<"created_at">> := CreatedAt
, <<"updated_at">> := UpdatedAt
} = Element3 = sr_json:decode(Body3),
Expand All @@ -82,40 +83,57 @@ success_scenario(_Config) ->
sr_test_utils:api_call(get, "/elements"),
[Element3] = sr_json:decode(Body4),

ct:comment("The element value can be changed by PATCH"),
#{status_code := 200, body := Body5} =
sr_test_utils:api_call(
patch, "/elements/element1", Headers, #{value => <<"newval5">>}),
#{ <<"key">> := <<"element1">>
, <<"value">> := <<"newval5">>
, <<"created_at">> := CreatedAt
, <<"updated_at">> := UpdatedAt5
} = Element5 = sr_json:decode(Body5),
true = UpdatedAt5 >= CreatedAt,

ct:comment("Still just one element"),
#{status_code := 200, body := Body6} =
sr_test_utils:api_call(get, "/elements"),
[Element5] = sr_json:decode(Body6),

ct:comment("Elements can be created by PUT"),
#{status_code := 201, body := Body5} =
#{status_code := 201, body := Body7} =
sr_test_utils:api_call(
put, "/elements/element2", Headers,
#{ key => <<"element2">>
, value => <<"val2">>
}),
#{ <<"key">> := <<"element2">>
, <<"created_at">> := CreatedAt5
, <<"updated_at">> := CreatedAt5
} = Element5 = sr_json:decode(Body5),
true = CreatedAt5 >= CreatedAt,
, <<"value">> := <<"val2">>
, <<"created_at">> := CreatedAt7
, <<"updated_at">> := CreatedAt7
} = Element7 = sr_json:decode(Body7),
true = CreatedAt7 >= CreatedAt,

ct:comment("There are two elements now"),
#{status_code := 200, body := Body6} =
#{status_code := 200, body := Body8} =
sr_test_utils:api_call(get, "/elements"),
[Element5] = sr_json:decode(Body6) -- [Element3],
[Element7] = sr_json:decode(Body8) -- [Element5],

ct:comment("Element1 is deleted"),
#{status_code := 204} = sr_test_utils:api_call(delete, "/elements/element1"),

ct:comment("One element again"),
#{status_code := 200, body := Body7} =
#{status_code := 200, body := Body9} =
sr_test_utils:api_call(get, "/elements"),
[Element5] = sr_json:decode(Body7),
[Element7] = sr_json:decode(Body9),

ct:comment("DELETE is not idempotent"),
#{status_code := 204} = sr_test_utils:api_call(delete, "/elements/element2"),
#{status_code := 404} = sr_test_utils:api_call(delete, "/elements/element2"),

ct:comment("There are no elements"),
#{status_code := 200, body := Body8} =
#{status_code := 200, body := Body10} =
sr_test_utils:api_call(get, "/elements"),
[] = sr_json:decode(Body8),
[] = sr_json:decode(Body10),

{comment, ""}.

Expand Down Expand Up @@ -181,12 +199,16 @@ invalid_parameters(_Config) ->
sr_test_utils:api_call(put, "/elements/nobody", Headers, <<>>),
#{status_code := 400} =
sr_test_utils:api_call(put, "/elements/key", Headers, <<>>),
#{status_code := 400} =
sr_test_utils:api_call(patch, "/elements/key", Headers, <<>>),
#{status_code := 400} =
sr_test_utils:api_call(post, "/elements", Headers, <<"{">>),
#{status_code := 400} =
sr_test_utils:api_call(put, "/elements/broken", Headers, <<"{">>),
#{status_code := 400} =
sr_test_utils:api_call(put, "/elements/key", Headers, <<"{">>),
#{status_code := 400} =
sr_test_utils:api_call(patch, "/elements/key", Headers, <<"{">>),

ct:comment("Missing parameters are reported"),
None = #{},
Expand All @@ -196,6 +218,8 @@ invalid_parameters(_Config) ->
sr_test_utils:api_call(put, "/elements/none", Headers, None),
#{status_code := 400} =
sr_test_utils:api_call(put, "/elements/key", Headers, None),
#{status_code := 400} =
sr_test_utils:api_call(patch, "/elements/key", Headers, None),

NoVal = #{key => <<"noval">>},
#{status_code := 400} =
Expand All @@ -204,12 +228,15 @@ invalid_parameters(_Config) ->
sr_test_utils:api_call(put, "/elements/noval", Headers, NoVal),
#{status_code := 400} =
sr_test_utils:api_call(put, "/elements/key", Headers, NoVal),
#{status_code := 400} =
sr_test_utils:api_call(patch, "/elements/key", Headers, NoVal),

{comment, ""}.

-spec not_found(sr_test_utils:config()) -> {comment, string()}.
not_found(_Config) ->
ct:comment("Not existing element is not found"),
#{status_code := 404} = sr_test_utils:api_call(get, "/elements/notfound"),
#{status_code := 404} = sr_test_utils:api_call(patch, "/elements/notfound"),
#{status_code := 404} = sr_test_utils:api_call(delete, "/elements/notfound"),
{comment, ""}.
13 changes: 8 additions & 5 deletions test/sr_test/sr_elements.erl
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,14 @@ from_json(Json) ->
-spec update(element(), sumo_rest_doc:json()) ->
{ok, element()} | {error, iodata()}.
update(Element, Json) ->
case from_json(Json) of
{error, Reason} -> {error, Reason};
{ok, Updates} ->
UpdatedElement = maps:merge(Element, Updates),
{ok, UpdatedElement#{updated_at => calendar:universal_time()}}
try
NewValue = maps:get(<<"value">>, Json),
UpdatedElement =
Element#{value := NewValue, updated_at := calendar:universal_time()},
{ok, UpdatedElement}
catch
_:{badkey, Key} ->
{error, <<"missing field: ", Key/binary>>}
end.

-spec uri_path(element()) -> binary().
Expand Down
8 changes: 8 additions & 0 deletions test/sr_test/sr_single_element_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
, content_types_provided/2
, handle_get/2
, handle_put/2
, handle_patch/2
, delete_resource/2
]
}]).
Expand Down Expand Up @@ -42,6 +43,13 @@ trails() ->
, produces => ["application/json"]
, parameters => [Id]
}
, patch =>
#{ tags => ["elements"]
, description => "Updates an element"
, consumes => ["application/json"]
, produces => ["application/json"]
, parameters => [RequestBody, Id]
}
, put =>
#{ tags => ["elements"]
, description => "Updates or creates a new element"
Expand Down

0 comments on commit a2b18f3

Please sign in to comment.