Skip to content

Commit a6a489b

Browse files
authored
feat: option to enable URL query params without encoding (#885)
1 parent fc51b33 commit a6a489b

File tree

5 files changed

+82
-0
lines changed

5 files changed

+82
-0
lines changed

client.go

+13
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ type Client struct {
155155
panicHooks []ErrorHook
156156
rateLimiter RateLimiter
157157
generateCurlOnDebug bool
158+
unescapeQueryParams bool
158159
}
159160

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

329+
// SetUnescapeQueryParams method sets the unescape query parameters choice for request URL.
330+
// To prevent broken URL, resty replaces space (" ") with "+" in the query parameters.
331+
//
332+
// See [Request.SetUnescapeQueryParams]
333+
//
334+
// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
335+
func (c *Client) SetUnescapeQueryParams(unescape bool) *Client {
336+
c.unescapeQueryParams = unescape
337+
return c
338+
}
339+
328340
// SetFormData method sets Form parameters and their values in the client instance.
329341
// It applies only to HTTP methods `POST` and `PUT`, and the request content type would be set as
330342
// `application/x-www-form-urlencoded`. These form data will be added to all the requests raised from
@@ -446,6 +458,7 @@ func (c *Client) R() *Request {
446458
log: c.log,
447459
responseBodyLimit: c.ResponseBodyLimit,
448460
generateCurlOnDebug: c.generateCurlOnDebug,
461+
unescapeQueryParams: c.unescapeQueryParams,
449462
}
450463
return r
451464
}

middleware.go

+9
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ func parseRequestURL(c *Client, r *Request) error {
154154
}
155155
}
156156

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

159168
return nil

middleware_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,23 @@ func Test_parseRequestURL(t *testing.T) {
265265
},
266266
expectedURL: "https://example.com/?foo=1&foo=2",
267267
},
268+
{
269+
name: "unescape query params",
270+
init: func(c *Client, r *Request) {
271+
c.SetBaseURL("https://example.com/").
272+
SetUnescapeQueryParams(true). // this line is just code coverage; I will restructure this test in v3 for the client and request the respective init method
273+
SetQueryParam("fromclient", "hey unescape").
274+
SetQueryParam("initone", "cáfe")
275+
276+
r.SetUnescapeQueryParams(true) // this line takes effect
277+
r.SetQueryParams(
278+
map[string]string{
279+
"registry": "nacos://test:6801", // GH #797
280+
},
281+
)
282+
},
283+
expectedURL: "https://example.com?initone=cáfe&fromclient=hey+unescape&registry=nacos://test:6801",
284+
},
268285
} {
269286
t.Run(tt.name, func(t *testing.T) {
270287
c := New()
@@ -292,6 +309,29 @@ func Test_parseRequestURL(t *testing.T) {
292309
}
293310
}
294311

312+
func TestRequestURL_GH797(t *testing.T) {
313+
ts := createGetServer(t)
314+
defer ts.Close()
315+
316+
c := dc().
317+
SetBaseURL(ts.URL).
318+
SetUnescapeQueryParams(true). // this line is just code coverage; I will restructure this test in v3 for the client and request the respective init method
319+
SetQueryParam("fromclient", "hey unescape").
320+
SetQueryParam("initone", "cáfe")
321+
322+
resp, err := c.R().
323+
SetUnescapeQueryParams(true). // this line takes effect
324+
SetQueryParams(
325+
map[string]string{
326+
"registry": "nacos://test:6801", // GH #797
327+
},
328+
).
329+
Get("/unescape-query-params")
330+
331+
assertError(t, err)
332+
assertEqual(t, "query params looks good", resp.String())
333+
}
334+
295335
func Benchmark_parseRequestURL_PathParams(b *testing.B) {
296336
c := New().SetPathParams(map[string]string{
297337
"foo": "1",

request.go

+12
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type Request struct {
7373
retryConditions []RetryConditionFunc
7474
responseBodyLimit int
7575
generateCurlOnDebug bool
76+
unescapeQueryParams bool
7677
}
7778

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

214+
// SetUnescapeQueryParams method sets the unescape query parameters choice for request URL.
215+
// To prevent broken URL, resty replaces space (" ") with "+" in the query parameters.
216+
//
217+
// This method overrides the value set by [Client.SetUnescapeQueryParams]
218+
//
219+
// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters.
220+
func (r *Request) SetUnescapeQueryParams(unescape bool) *Request {
221+
r.unescapeQueryParams = unescape
222+
return r
223+
}
224+
213225
// SetQueryParamsFromValues method appends multiple parameters with multi-value
214226
// ([url.Values]) at one go in the current request. It will be formed as
215227
// query string for the request.

resty_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ func createGetServer(t *testing.T) *httptest.Server {
121121
case "/not-found-no-error":
122122
w.Header().Set(hdrContentTypeKey, "application/json")
123123
w.WriteHeader(http.StatusNotFound)
124+
case "/unescape-query-params":
125+
initOne := r.URL.Query().Get("initone")
126+
fromClient := r.URL.Query().Get("fromclient")
127+
registry := r.URL.Query().Get("registry")
128+
assertEqual(t, "cáfe", initOne)
129+
assertEqual(t, "hey unescape", fromClient)
130+
assertEqual(t, "nacos://test:6801", registry)
131+
_, _ = w.Write([]byte(`query params looks good`))
124132
}
125133

126134
switch {

0 commit comments

Comments
 (0)