From c4d1ee554778cc7c90e0a964bfbdddb8823de0c9 Mon Sep 17 00:00:00 2001 From: Tom Burdick Date: Mon, 15 Oct 2012 17:56:55 -0500 Subject: add patch support to cowboy_rest --- src/cowboy_rest.erl | 29 ++++++++++++++++++++++++++++- test/http_SUITE.erl | 18 ++++++++++++++++++ test/rest_patch_resource.erl | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 test/rest_patch_resource.erl diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 6816303..fb7f2e1 100644 --- a/src/cowboy_rest.erl +++ b/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 diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index 25ce595..0029f6f 100644 --- a/test/http_SUITE.erl +++ b/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, []). diff --git a/test/rest_patch_resource.erl b/test/rest_patch_resource.erl new file mode 100644 index 0000000..e265f6f --- /dev/null +++ b/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. -- cgit v1.2.3