Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: option to set URL query params without encoding #885

Merged
merged 4 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ type Client struct {
panicHooks []ErrorHook
rateLimiter RateLimiter
generateCurlOnDebug bool
unescapeQueryParams bool
}

// User type is to hold an username and password information
Expand Down Expand Up @@ -325,6 +326,17 @@ func (c *Client) SetQueryParams(params map[string]string) *Client {
return c
}

// SetUnescapeQueryParams method sets the unescape query parameters choice for request URL.
// To prevent broken URL, resty replaces space (" ") with "+" in the query parameters.
//
// See [Request.SetUnescapeQueryParams]
//
// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
func (c *Client) SetUnescapeQueryParams(unescape bool) *Client {
c.unescapeQueryParams = unescape
return c
}

// SetFormData method sets Form parameters and their values in the client instance.
// It applies only to HTTP methods `POST` and `PUT`, and the request content type would be set as
// `application/x-www-form-urlencoded`. These form data will be added to all the requests raised from
Expand Down Expand Up @@ -446,6 +458,7 @@ func (c *Client) R() *Request {
log: c.log,
responseBodyLimit: c.ResponseBodyLimit,
generateCurlOnDebug: c.generateCurlOnDebug,
unescapeQueryParams: c.unescapeQueryParams,
}
return r
}
Expand Down
9 changes: 9 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ func parseRequestURL(c *Client, r *Request) error {
}
}

// GH#797 Unescape query parameters
if r.unescapeQueryParams && len(reqURL.RawQuery) > 0 {
// at this point, all errors caught up in the above operations
// so ignore the return error on query unescape; I realized
// while writing the unit test
unescapedQuery, _ := url.QueryUnescape(reqURL.RawQuery)
reqURL.RawQuery = strings.ReplaceAll(unescapedQuery, " ", "+") // otherwise request becomes bad request
}

r.URL = reqURL.String()

return nil
Expand Down
40 changes: 40 additions & 0 deletions middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,23 @@ func Test_parseRequestURL(t *testing.T) {
},
expectedURL: "https://example.com/?foo=1&foo=2",
},
{
name: "unescape query params",
init: func(c *Client, r *Request) {
c.SetBaseURL("https://example.com/").
SetUnescapeQueryParams(true). // this line is just code coverage; I will restructure this test in v3 for the client and request the respective init method
SetQueryParam("fromclient", "hey unescape").
SetQueryParam("initone", "cáfe")

r.SetUnescapeQueryParams(true) // this line takes effect
r.SetQueryParams(
map[string]string{
"registry": "nacos://test:6801", // GH #797
},
)
},
expectedURL: "https://example.com?initone=cáfe&fromclient=hey+unescape&registry=nacos://test:6801",
},
} {
t.Run(tt.name, func(t *testing.T) {
c := New()
Expand Down Expand Up @@ -292,6 +309,29 @@ func Test_parseRequestURL(t *testing.T) {
}
}

func TestRequestURL_GH797(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()

c := dc().
SetBaseURL(ts.URL).
SetUnescapeQueryParams(true). // this line is just code coverage; I will restructure this test in v3 for the client and request the respective init method
SetQueryParam("fromclient", "hey unescape").
SetQueryParam("initone", "cáfe")

resp, err := c.R().
SetUnescapeQueryParams(true). // this line takes effect
SetQueryParams(
map[string]string{
"registry": "nacos://test:6801", // GH #797
},
).
Get("/unescape-query-params")

assertError(t, err)
assertEqual(t, "query params looks good", resp.String())
}

func Benchmark_parseRequestURL_PathParams(b *testing.B) {
c := New().SetPathParams(map[string]string{
"foo": "1",
Expand Down
12 changes: 12 additions & 0 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type Request struct {
retryConditions []RetryConditionFunc
responseBodyLimit int
generateCurlOnDebug bool
unescapeQueryParams bool
}

// GenerateCurlCommand method generates the CURL command for the request.
Expand Down Expand Up @@ -210,6 +211,17 @@ func (r *Request) SetQueryParams(params map[string]string) *Request {
return r
}

// SetUnescapeQueryParams method sets the unescape query parameters choice for request URL.
// To prevent broken URL, resty replaces space (" ") with "+" in the query parameters.
//
// This method overrides the value set by [Client.SetUnescapeQueryParams]
//
// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
func (r *Request) SetUnescapeQueryParams(unescape bool) *Request {
r.unescapeQueryParams = unescape
return r
}

// SetQueryParamsFromValues method appends multiple parameters with multi-value
// ([url.Values]) at one go in the current request. It will be formed as
// query string for the request.
Expand Down
8 changes: 8 additions & 0 deletions resty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ func createGetServer(t *testing.T) *httptest.Server {
case "/not-found-no-error":
w.Header().Set(hdrContentTypeKey, "application/json")
w.WriteHeader(http.StatusNotFound)
case "/unescape-query-params":
initOne := r.URL.Query().Get("initone")
fromClient := r.URL.Query().Get("fromclient")
registry := r.URL.Query().Get("registry")
assertEqual(t, "cáfe", initOne)
assertEqual(t, "hey unescape", fromClient)
assertEqual(t, "nacos://test:6801", registry)
_, _ = w.Write([]byte(`query params looks good`))
}

switch {
Expand Down