Skip to content

Commit e62bc20

Browse files
authored
Merge pull request #462 from gitctrlx/master
feat: add SetQueryParamsFromValues and SetQueryParamsFromStruct methods
2 parents ab8fffd + f5f67ba commit e62bc20

File tree

10 files changed

+183
-0
lines changed

10 files changed

+183
-0
lines changed

.testdata/tmp_dumpfile_req

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
:authority: 127.0.0.1:62895
2+
:method: GET
3+
:path: /
4+
:scheme: https
5+
accept-encoding: gzip
6+
user-agent: req/v3 (https://github.com/imroc/req)
7+
8+
:status: 200
9+
method: GET
10+
content-type: text/plain; charset=utf-8
11+
content-length: 22
12+
date: Thu, 04 Sep 2025 18:21:57 GMT
13+
14+
TestGet: text response

.testdata/tmp_test_dump_file

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
:authority: 127.0.0.1:62895
2+
:method: POST
3+
:path: /
4+
:scheme: https
5+
content-type: text/plain; charset=utf-8
6+
content-length: 9
7+
accept-encoding: gzip
8+
user-agent: req/v3 (https://github.com/imroc/req)
9+
10+
test body
11+
12+
:status: 200
13+
method: POST
14+
content-type: text/plain; charset=utf-8
15+
content-length: 23
16+
date: Thu, 04 Sep 2025 18:21:57 GMT
17+
18+
TestPost: text response

client.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"github.com/imroc/req/v3/http2"
2525
"github.com/imroc/req/v3/internal/header"
2626
"github.com/imroc/req/v3/internal/util"
27+
28+
"github.com/google/go-querystring/query"
2729
)
2830

2931
// DefaultClient returns the global default Client.
@@ -501,6 +503,31 @@ func (c *Client) SetCommonQueryString(query string) *Client {
501503
return c
502504
}
503505

506+
// SetCommonQueryParamsFromValues set URL query parameters from a url.Values map
507+
// for requests fired from the client.
508+
func (c *Client) SetCommonQueryParamsFromValues(params urlpkg.Values) *Client {
509+
if c.QueryParams == nil {
510+
c.QueryParams = make(urlpkg.Values)
511+
}
512+
for p, v := range params {
513+
for _, pv := range v {
514+
c.QueryParams.Add(p, pv)
515+
}
516+
}
517+
return c
518+
}
519+
520+
// SetCommonQueryParamsFromStruct set URL query parameters from a struct using go-querystring
521+
// for requests fired from the client.
522+
func (c *Client) SetCommonQueryParamsFromStruct(v any) *Client {
523+
values, err := query.Values(v)
524+
if err != nil {
525+
c.log.Warnf("failed to convert struct to query parameters: %v", err)
526+
return c
527+
}
528+
return c.SetCommonQueryParamsFromValues(values)
529+
}
530+
504531
// SetCommonCookies set HTTP cookies for requests fired from the client.
505532
func (c *Client) SetCommonCookies(cookies ...*http.Cookie) *Client {
506533
c.Cookies = append(c.Cookies, cookies...)

client_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,29 @@ func TestSetCommonQueryParams(t *testing.T) {
331331
tests.AssertEqual(t, "test=test", resp.String())
332332
}
333333

334+
func TestSetCommonQueryParamsFromValues(t *testing.T) {
335+
values := url.Values{}
336+
values.Add("test", "test")
337+
values.Add("key", "value")
338+
resp, err := tc().SetCommonQueryParamsFromValues(values).R().Get("/query-parameter")
339+
assertSuccess(t, resp, err)
340+
tests.AssertEqual(t, "key=value&test=test", resp.String())
341+
}
342+
343+
func TestSetCommonQueryParamsFromStruct(t *testing.T) {
344+
type QueryParams struct {
345+
Test string `url:"test"`
346+
Key string `url:"key"`
347+
}
348+
params := QueryParams{
349+
Test: "test",
350+
Key: "value",
351+
}
352+
resp, err := tc().SetCommonQueryParamsFromStruct(params).R().Get("/query-parameter")
353+
assertSuccess(t, resp, err)
354+
tests.AssertEqual(t, "key=value&test=test", resp.String())
355+
}
356+
334357
func TestInsecureSkipVerify(t *testing.T) {
335358
c := tc().EnableInsecureSkipVerify()
336359
tests.AssertEqual(t, true, c.TLSClientConfig.InsecureSkipVerify)

client_wrapper.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,18 @@ func SetCommonQueryString(query string) *Client {
195195
return defaultClient.SetCommonQueryString(query)
196196
}
197197

198+
// SetCommonQueryParamsFromValues is a global wrapper methods which delegated
199+
// to the default client's Client.SetCommonQueryParamsFromValues.
200+
func SetCommonQueryParamsFromValues(params url.Values) *Client {
201+
return defaultClient.SetCommonQueryParamsFromValues(params)
202+
}
203+
204+
// SetCommonQueryParamsFromStruct is a global wrapper methods which delegated
205+
// to the default client's Client.SetCommonQueryParamsFromStruct.
206+
func SetCommonQueryParamsFromStruct(v any) *Client {
207+
return defaultClient.SetCommonQueryParamsFromStruct(v)
208+
}
209+
198210
// SetCommonCookies is a global wrapper methods which delegated
199211
// to the default client's Client.SetCommonCookies.
200212
func SetCommonCookies(cookies ...*http.Cookie) *Client {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.24
44

55
require (
66
github.com/andybalholm/brotli v1.2.0
7+
github.com/google/go-querystring v1.1.0
78
github.com/icholy/digest v1.1.0
89
github.com/klauspost/compress v1.18.0
910
github.com/quic-go/qpack v0.5.1

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ
44
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
55
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
66
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
78
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
89
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
10+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
11+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
912
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
1013
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
1114
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -38,6 +41,7 @@ golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
3841
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
3942
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
4043
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
44+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
4145
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4246
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
4347
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=

request.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"strings"
1515
"time"
1616

17+
"github.com/google/go-querystring/query"
1718
"github.com/imroc/req/v3/internal/dump"
1819
"github.com/imroc/req/v3/internal/header"
1920
"github.com/imroc/req/v3/internal/util"
@@ -231,6 +232,34 @@ func (r *Request) SetQueryString(query string) *Request {
231232
return r
232233
}
233234

235+
// SetQueryParamsFromValues sets query parameters from a url.Values map.
236+
// This method allows direct configuration of query parameters from url.Values,
237+
// which is commonly used with libraries like go-querystring.
238+
func (r *Request) SetQueryParamsFromValues(params urlpkg.Values) *Request {
239+
if r.QueryParams == nil {
240+
r.QueryParams = make(urlpkg.Values)
241+
}
242+
for p, v := range params {
243+
for _, pv := range v {
244+
r.QueryParams.Add(p, pv)
245+
}
246+
}
247+
return r
248+
}
249+
250+
// SetQueryParamsFromStruct sets query parameters from a struct using go-querystring.
251+
// This method provides a higher-level abstraction by allowing users to directly pass
252+
// a struct to configure query parameters. The struct should use `url` tags to specify
253+
// parameter names.
254+
func (r *Request) SetQueryParamsFromStruct(v any) *Request {
255+
values, err := query.Values(v)
256+
if err != nil {
257+
r.client.log.Warnf("failed to convert struct to query parameters: %v", err)
258+
return r
259+
}
260+
return r.SetQueryParamsFromValues(values)
261+
}
262+
234263
// SetFileReader set up a multipart form with a reader to upload file.
235264
func (r *Request) SetFileReader(paramName, filename string, reader io.Reader) *Request {
236265
r.SetFileUpload(FileUpload{

request_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,49 @@ func testQueryParam(t *testing.T, c *Client) {
636636
Get("/query-parameter")
637637
assertSuccess(t, resp, err)
638638
tests.AssertEqual(t, "key1=value1&key1=value11&key2=value2&key2=value22&key3=value3&key4=value4&key4=value44&key5=value5&key6=value6&key6=value66", resp.String())
639+
640+
// SetQueryParamsFromValues
641+
values := url.Values{}
642+
values.Add("key1", "value1")
643+
values.Add("key2", "value2")
644+
values.Add("key3", "value3")
645+
resp, err = c.R().
646+
SetQueryParamsFromValues(values).
647+
Get("/query-parameter")
648+
assertSuccess(t, resp, err)
649+
tests.AssertEqual(t, "key1=value1&key2=value2&key3=value3&key4=client&key5=client&key5=extra", resp.String())
650+
651+
// SetQueryParamsFromStruct
652+
type QueryParams struct {
653+
Key1 string `url:"key1"`
654+
Key2 string `url:"key2"`
655+
Key3 string `url:"key3"`
656+
}
657+
params := QueryParams{
658+
Key1: "value1",
659+
Key2: "value2",
660+
Key3: "value3",
661+
}
662+
resp, err = c.R().
663+
SetQueryParamsFromStruct(params).
664+
Get("/query-parameter")
665+
assertSuccess(t, resp, err)
666+
tests.AssertEqual(t, "key1=value1&key2=value2&key3=value3&key4=client&key5=client&key5=extra", resp.String())
667+
668+
// SetQueryParamsFromStruct with slice
669+
type QueryParamsWithSlice struct {
670+
Key1 string `url:"key1"`
671+
Tags []string `url:"tags"`
672+
}
673+
paramsWithSlice := QueryParamsWithSlice{
674+
Key1: "value1",
675+
Tags: []string{"tag1", "tag2"},
676+
}
677+
resp, err = c.R().
678+
SetQueryParamsFromStruct(paramsWithSlice).
679+
Get("/query-parameter")
680+
assertSuccess(t, resp, err)
681+
tests.AssertEqual(t, "key1=value1&key2=client&key3=client&key4=client&key5=client&key5=extra&tags=tag1&tags=tag2", resp.String())
639682
}
640683

641684
func TestPathParam(t *testing.T) {

request_wrapper.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ func SetQueryString(query string) *Request {
5050
return defaultClient.R().SetQueryString(query)
5151
}
5252

53+
// SetQueryParamsFromValues is a global wrapper methods which delegated
54+
// to the default client, create a request and SetQueryParamsFromValues for request.
55+
func SetQueryParamsFromValues(params url.Values) *Request {
56+
return defaultClient.R().SetQueryParamsFromValues(params)
57+
}
58+
59+
// SetQueryParamsFromStruct is a global wrapper methods which delegated
60+
// to the default client, create a request and SetQueryParamsFromStruct for request.
61+
func SetQueryParamsFromStruct(v any) *Request {
62+
return defaultClient.R().SetQueryParamsFromStruct(v)
63+
}
64+
5365
// SetFileReader is a global wrapper methods which delegated
5466
// to the default client, create a request and SetFileReader for request.
5567
func SetFileReader(paramName, filePath string, reader io.Reader) *Request {

0 commit comments

Comments
 (0)