Browse Source

add patch support to cowboy_rest

Tom Burdick 12 years ago
parent
commit
c4d1ee5547
3 changed files with 80 additions and 1 deletions
  1. 28 1
      src/cowboy_rest.erl
  2. 18 0
      test/http_SUITE.erl
  3. 34 0
      test/rest_patch_resource.erl

+ 28 - 1
src/cowboy_rest.erl

@@ -91,7 +91,8 @@ known_methods(Req, State=#state{method=Method}) ->
 		no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
 				Method =:= <<"POST">>; Method =:= <<"PUT">>;
 				Method =:= <<"DELETE">>; Method =:= <<"TRACE">>;
-				Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">> ->
+				Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">>;
+				Method =:= <<"PATCH">> ->
 			next(Req, State, fun uri_too_long/2);
 		no_call ->
 			next(Req, State, 501);
@@ -644,6 +645,8 @@ method(Req, State=#state{method= <<"POST">>}) ->
 	post_is_create(Req, State);
 method(Req, State=#state{method= <<"PUT">>}) ->
 	is_conflict(Req, State);
+method(Req, State=#state{method= <<"PATCH">>}) ->
+	patch_resource(Req, State);
 method(Req, State=#state{method=Method})
 		when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
 	set_resp_body(Req, State);
@@ -708,6 +711,9 @@ put_resource(Req, State) ->
 %% may be different from the request path, and is stored as request metadata.
 %% It is always defined past this point. It can be retrieved as demonstrated:
 %%     {PutPath, Req2} = cowboy_req:meta(put_path, Req)
+%%
+%%content_types_accepted SHOULD return a different list
+%% for each HTTP method.
 put_resource(Req, State, OnTrue) ->
 	case call(Req, State, content_types_accepted) of
 		no_call ->
@@ -722,6 +728,27 @@ put_resource(Req, State, OnTrue) ->
 			choose_content_type(Req3, State2, OnTrue, ContentType, CTA2)
 	end.
 
+%% content_types_accepted should return a list of media types and their
+%% associated callback functions in the same format as content_types_provided.
+%%
+%% The callback will then be called and is expected to process the content
+%% pushed to the resource in the request body. 
+%%
+%% content_types_accepted SHOULD return a different list
+%% for each HTTP method.
+patch_resource(Req, State) ->
+	case call(Req, State, content_types_accepted) of
+		no_call ->
+			respond(Req, State, 415);
+		{halt, Req2, HandlerState} ->
+			terminate(Req2, State#state{handler_state=HandlerState});
+		{CTM, Req2, HandlerState} ->
+			State2 = State#state{handler_state=HandlerState},
+			{ok, ContentType, Req3}
+				= cowboy_req:parse_header(<<"content-type">>, Req2),
+			choose_content_type(Req3, State2, 204, ContentType, CTM)
+	end.
+
 %% The special content type '*' will always match. It can be used as a
 %% catch-all content type for accepting any kind of request content.
 %% Note that because it will always match, it should be the last of the

+ 18 - 0
test/http_SUITE.erl

@@ -56,6 +56,7 @@
 -export([rest_missing_get_callbacks/1]).
 -export([rest_missing_put_callbacks/1]).
 -export([rest_nodelete/1]).
+-export([rest_patch/1]).
 -export([rest_resource_etags/1]).
 -export([rest_resource_etags_if_none_match/1]).
 -export([set_resp_body/1]).
@@ -116,6 +117,7 @@ groups() ->
 		rest_missing_get_callbacks,
 		rest_missing_put_callbacks,
 		rest_nodelete,
+		rest_patch,
 		rest_resource_etags,
 		rest_resource_etags_if_none_match,
 		set_resp_body,
@@ -329,6 +331,7 @@ init_dispatch(Config) ->
 			{"/missing_get_callbacks", rest_missing_callbacks, []},
 			{"/missing_put_callbacks", rest_missing_callbacks, []},
 			{"/nodelete", rest_nodelete_resource, []},
+			{"/patch", rest_patch_resource, []},
 			{"/resetags", rest_resource_etags, []},
 			{"/rest_expires", rest_expires, []},
 			{"/loop_timeout", http_handler_loop_timeout, []},
@@ -832,6 +835,21 @@ rest_nodelete(Config) ->
 		build_url("/nodelete", Config), Client),
 	{ok, 500, _, _} = cowboy_client:response(Client2).
 
+rest_patch(Config) ->
+	Tests = [
+		{204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>},
+		{500, [{<<"content-type">>, <<"text/plain">>}], <<"false">>},
+		{400, [{<<"content-type">>, <<"text/plain">>}], <<"halt">>},
+		{415, [{<<"content-type">>, <<"application/json">>}], <<"bad_content_type">>}
+	],
+	Client = ?config(client, Config),
+	_ = [begin
+		{ok, Client2} = cowboy_client:request(<<"PATCH">>,
+			build_url("/patch", Config), Headers, Body, Client),
+		{ok, Status, _, _} = cowboy_client:response(Client2),
+		ok
+	end || {Status, Headers, Body} <- Tests].
+
 rest_resource_get_etag(Config, Type) ->
 	rest_resource_get_etag(Config, Type, []).
 

+ 34 - 0
test/rest_patch_resource.erl

@@ -0,0 +1,34 @@
+-module(rest_patch_resource).
+-export([init/3, allowed_methods/2, content_types_provided/2, get_text_plain/2,
+	content_types_accepted/2, patch_text_plain/2]).
+
+init(_Transport, _Req, _Opts) ->
+	{upgrade, protocol, cowboy_rest}.
+
+allowed_methods(Req, State) ->
+	{[<<"HEAD">>, <<"GET">>, <<"PATCH">>], Req, State}.
+
+content_types_provided(Req, State) ->
+	{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
+
+get_text_plain(Req, State) ->
+	{<<"This is REST!">>, Req, State}.
+
+content_types_accepted(Req, State) ->
+	case cowboy_req:method(Req) of
+		{<<"PATCH">>, Req0} ->
+			{[{{<<"text">>, <<"plain">>, []}, patch_text_plain}], Req0, State};
+		{_, Req0} ->
+			{[], Req0, State}
+	end.
+
+patch_text_plain(Req, State) ->
+	case cowboy_req:body(Req) of
+		{ok, <<"halt">>, Req0} ->
+			{ok, Req1} = cowboy_req:reply(400, Req0),
+			{halt, Req1, State};
+		{ok, <<"false">>, Req0} ->
+			{false, Req0, State};
+		{ok, _Body, Req0} ->
+			{true, Req0, State}
+	end.