From 667a9d062108e0fd8ab36def092cbfac7afdaf92 Mon Sep 17 00:00:00 2001 From: Eric Marden Date: Wed, 15 Mar 2017 15:53:12 -0500 Subject: [PATCH] adds methods to parse params - currently focuses on `GET` requests (punting on deseralizing bodies until we have #44 figured out, though `BodyParams` was added as a stand in) - param source precedence was implemented, with each subsequent source overwriting keys in ones proceeding it: `query`,` body`, `path` - params are parsed into `url.Values`, for consistency. #43 will go further and cast this into the appropriate types. - currently, nothing is calling `Params()`, the main entry point for parsing params from all sources -- this will change as all of the tickets that implment #3 come together fixes #41 --- hyperdrive_test.go | 3 +++ params.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++ params_test.go | 52 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 params.go create mode 100644 params_test.go diff --git a/hyperdrive_test.go b/hyperdrive_test.go index 1f5415a..7b5392c 100644 --- a/hyperdrive_test.go +++ b/hyperdrive_test.go @@ -2,6 +2,7 @@ package hyperdrive import ( "net/http" + "net/http/httptest" "testing" "github.com/stretchr/testify/suite" @@ -14,6 +15,7 @@ type HyperdriveTestSuite struct { TestHandler http.Handler TestRoot *RootResource TestEndpointResource EndpointResource + TestGetRequest *http.Request } func (suite *HyperdriveTestSuite) SetupTest() { @@ -22,6 +24,7 @@ func (suite *HyperdriveTestSuite) SetupTest() { suite.TestHandler = NewMethodHandler(suite.TestEndpoint) suite.TestRoot = NewRootResource(suite.TestAPI) suite.TestEndpointResource = NewEndpointResource(suite.TestEndpoint) + suite.TestGetRequest = httptest.NewRequest("GET", "/test/2?id=1&a=b", nil) } func (suite *HyperdriveTestSuite) TestNewAPI() { diff --git a/params.go b/params.go new file mode 100644 index 0000000..237a767 --- /dev/null +++ b/params.go @@ -0,0 +1,59 @@ +package hyperdrive + +import ( + "net/http" + "net/url" + + "github.com/gorilla/mux" +) + +// QueryParams extracts the values from the request QueryString. It returns +// a url.Values object (essentially map[string][]string). If the +// request method is not GET, an empty url.Values is returned. +func QueryParams(r *http.Request) url.Values { + if r.Method == "GET" { + r.ParseForm() + return r.Form + } + return url.Values{} +} + +// BodyParams deserializes the input, and extracts the values from the request +// body. It returns a url.Values object (essentially map[string][]string). If +// the request method is GET, an empty url.Values is returned. +func BodyParams(r *http.Request) url.Values { + if r.Method != "GET" { + return url.Values{} + } + return url.Values{} +} + +// PathParams extracts the values from the request path which match named +// params in the route. They are returned as url.Values for consistincey +// with http.Request.Form's behaviour. +func PathParams(r *http.Request) url.Values { + var params = url.Values{} + for k, v := range mux.Vars(r) { + params.Add(k, v) + } + return params +} + +// Params extracts the param values from all sources: query, body, and path -- in +// that order. Each subsequent source will overwrite values with the same key, to +// ensure API client intent is maintained in a consistent way. +func Params(r *http.Request) url.Values { + var params = QueryParams(r) + + for k, values := range BodyParams(r) { + for _, v := range values { + params.Set(k, v) + } + } + + for k, v := range mux.Vars(r) { + params.Set(k, v) + } + + return params +} diff --git a/params_test.go b/params_test.go new file mode 100644 index 0000000..d8fafcf --- /dev/null +++ b/params_test.go @@ -0,0 +1,52 @@ +package hyperdrive + +import ( + "net/http" + "net/http/httptest" + "net/url" +) + +func (suite *HyperdriveTestSuite) TestQueryParamsGet() { + suite.IsType(url.Values{}, QueryParams(suite.TestGetRequest), "expects an instance of url.Values") +} + +func (suite *HyperdriveTestSuite) TestQueryParamsGetValues() { + suite.Equal(url.Values{"id": []string{"1"}, "a": []string{"b"}}, QueryParams(suite.TestGetRequest), "returns populated url.Values") +} + +func (suite *HyperdriveTestSuite) TestBodyParamsGet() { + suite.IsType(url.Values{}, BodyParams(suite.TestGetRequest), "expects an instance of url.Values") +} + +func (suite *HyperdriveTestSuite) TestBodyParamsGetValues() { + suite.Equal(url.Values{}, BodyParams(suite.TestGetRequest), "returns populated url.Values") +} + +func (suite *HyperdriveTestSuite) TestPathParamsGet() { + suite.TestAPI.Router.Handle("/test/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.IsType(url.Values{}, PathParams(suite.TestGetRequest), "expects an instance of url.Values") + })) + suite.TestAPI.Router.ServeHTTP(httptest.NewRecorder(), suite.TestGetRequest) +} + +func (suite *HyperdriveTestSuite) TestPathParamsGetValues() { + suite.TestAPI.Router.Handle("/test/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.Equal(url.Values{"id": []string{"2"}}, PathParams(r), "returns populated url.Values") + })) + suite.TestAPI.Router.ServeHTTP(httptest.NewRecorder(), suite.TestGetRequest) +} + +func (suite *HyperdriveTestSuite) TestParamsGet() { + suite.TestAPI.Router.Handle("/test/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.IsType(url.Values{}, Params(suite.TestGetRequest), "expects an instance of url.Values") + })) + suite.TestAPI.Router.ServeHTTP(httptest.NewRecorder(), suite.TestGetRequest) + +} + +func (suite *HyperdriveTestSuite) TestParamsGetValues() { + suite.TestAPI.Router.Handle("/test/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + suite.Equal(url.Values{"id": []string{"1"}, "a": []string{"b"}}, Params(suite.TestGetRequest), "returns populated url.Values") + })) + suite.TestAPI.Router.ServeHTTP(httptest.NewRecorder(), suite.TestGetRequest) +}