Skip to content
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
14 changes: 14 additions & 0 deletions .testdata/tmp_dumpfile_req
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
:authority: 127.0.0.1:62895
:method: GET
:path: /
:scheme: https
accept-encoding: gzip
user-agent: req/v3 (https://github.com/imroc/req)

:status: 200
method: GET
content-type: text/plain; charset=utf-8
content-length: 22
date: Thu, 04 Sep 2025 18:21:57 GMT

TestGet: text response
18 changes: 18 additions & 0 deletions .testdata/tmp_test_dump_file
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
:authority: 127.0.0.1:62895
:method: POST
:path: /
:scheme: https
content-type: text/plain; charset=utf-8
content-length: 9
accept-encoding: gzip
user-agent: req/v3 (https://github.com/imroc/req)

test body

:status: 200
method: POST
content-type: text/plain; charset=utf-8
content-length: 23
date: Thu, 04 Sep 2025 18:21:57 GMT

TestPost: text response
27 changes: 27 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"github.com/imroc/req/v3/http2"
"github.com/imroc/req/v3/internal/header"
"github.com/imroc/req/v3/internal/util"

"github.com/google/go-querystring/query"
)

// DefaultClient returns the global default Client.
Expand Down Expand Up @@ -501,6 +503,31 @@ func (c *Client) SetCommonQueryString(query string) *Client {
return c
}

// SetCommonQueryParamsFromValues set URL query parameters from a url.Values map
// for requests fired from the client.
func (c *Client) SetCommonQueryParamsFromValues(params urlpkg.Values) *Client {
if c.QueryParams == nil {
c.QueryParams = make(urlpkg.Values)
}
for p, v := range params {
for _, pv := range v {
c.QueryParams.Add(p, pv)
}
}
return c
}

// SetCommonQueryParamsFromStruct set URL query parameters from a struct using go-querystring
// for requests fired from the client.
func (c *Client) SetCommonQueryParamsFromStruct(v any) *Client {
values, err := query.Values(v)
if err != nil {
c.log.Warnf("failed to convert struct to query parameters: %v", err)
return c
}
return c.SetCommonQueryParamsFromValues(values)
}

// SetCommonCookies set HTTP cookies for requests fired from the client.
func (c *Client) SetCommonCookies(cookies ...*http.Cookie) *Client {
c.Cookies = append(c.Cookies, cookies...)
Expand Down
23 changes: 23 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,29 @@ func TestSetCommonQueryParams(t *testing.T) {
tests.AssertEqual(t, "test=test", resp.String())
}

func TestSetCommonQueryParamsFromValues(t *testing.T) {
values := url.Values{}
values.Add("test", "test")
values.Add("key", "value")
resp, err := tc().SetCommonQueryParamsFromValues(values).R().Get("/query-parameter")
assertSuccess(t, resp, err)
tests.AssertEqual(t, "key=value&test=test", resp.String())
}

func TestSetCommonQueryParamsFromStruct(t *testing.T) {
type QueryParams struct {
Test string `url:"test"`
Key string `url:"key"`
}
params := QueryParams{
Test: "test",
Key: "value",
}
resp, err := tc().SetCommonQueryParamsFromStruct(params).R().Get("/query-parameter")
assertSuccess(t, resp, err)
tests.AssertEqual(t, "key=value&test=test", resp.String())
}

func TestInsecureSkipVerify(t *testing.T) {
c := tc().EnableInsecureSkipVerify()
tests.AssertEqual(t, true, c.TLSClientConfig.InsecureSkipVerify)
Expand Down
12 changes: 12 additions & 0 deletions client_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ func SetCommonQueryString(query string) *Client {
return defaultClient.SetCommonQueryString(query)
}

// SetCommonQueryParamsFromValues is a global wrapper methods which delegated
// to the default client's Client.SetCommonQueryParamsFromValues.
func SetCommonQueryParamsFromValues(params url.Values) *Client {
return defaultClient.SetCommonQueryParamsFromValues(params)
}

// SetCommonQueryParamsFromStruct is a global wrapper methods which delegated
// to the default client's Client.SetCommonQueryParamsFromStruct.
func SetCommonQueryParamsFromStruct(v any) *Client {
return defaultClient.SetCommonQueryParamsFromStruct(v)
}

// SetCommonCookies is a global wrapper methods which delegated
// to the default client's Client.SetCommonCookies.
func SetCommonCookies(cookies ...*http.Cookie) *Client {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.24

require (
github.com/andybalholm/brotli v1.2.0
github.com/google/go-querystring v1.1.0
github.com/icholy/digest v1.1.0
github.com/klauspost/compress v1.18.0
github.com/quic-go/qpack v0.5.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
Expand Down Expand Up @@ -38,6 +41,7 @@ golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
Expand Down
29 changes: 29 additions & 0 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"time"

"github.com/google/go-querystring/query"
"github.com/imroc/req/v3/internal/dump"
"github.com/imroc/req/v3/internal/header"
"github.com/imroc/req/v3/internal/util"
Expand Down Expand Up @@ -231,6 +232,34 @@ func (r *Request) SetQueryString(query string) *Request {
return r
}

// SetQueryParamsFromValues sets query parameters from a url.Values map.
// This method allows direct configuration of query parameters from url.Values,
// which is commonly used with libraries like go-querystring.
func (r *Request) SetQueryParamsFromValues(params urlpkg.Values) *Request {
if r.QueryParams == nil {
r.QueryParams = make(urlpkg.Values)
}
for p, v := range params {
for _, pv := range v {
r.QueryParams.Add(p, pv)
}
}
return r
}

// SetQueryParamsFromStruct sets query parameters from a struct using go-querystring.
// This method provides a higher-level abstraction by allowing users to directly pass
// a struct to configure query parameters. The struct should use `url` tags to specify
// parameter names.
func (r *Request) SetQueryParamsFromStruct(v any) *Request {
values, err := query.Values(v)
if err != nil {
r.client.log.Warnf("failed to convert struct to query parameters: %v", err)
return r
}
return r.SetQueryParamsFromValues(values)
}

// SetFileReader set up a multipart form with a reader to upload file.
func (r *Request) SetFileReader(paramName, filename string, reader io.Reader) *Request {
r.SetFileUpload(FileUpload{
Expand Down
43 changes: 43 additions & 0 deletions request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,49 @@ func testQueryParam(t *testing.T, c *Client) {
Get("/query-parameter")
assertSuccess(t, resp, err)
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())

// SetQueryParamsFromValues
values := url.Values{}
values.Add("key1", "value1")
values.Add("key2", "value2")
values.Add("key3", "value3")
resp, err = c.R().
SetQueryParamsFromValues(values).
Get("/query-parameter")
assertSuccess(t, resp, err)
tests.AssertEqual(t, "key1=value1&key2=value2&key3=value3&key4=client&key5=client&key5=extra", resp.String())

// SetQueryParamsFromStruct
type QueryParams struct {
Key1 string `url:"key1"`
Key2 string `url:"key2"`
Key3 string `url:"key3"`
}
params := QueryParams{
Key1: "value1",
Key2: "value2",
Key3: "value3",
}
resp, err = c.R().
SetQueryParamsFromStruct(params).
Get("/query-parameter")
assertSuccess(t, resp, err)
tests.AssertEqual(t, "key1=value1&key2=value2&key3=value3&key4=client&key5=client&key5=extra", resp.String())

// SetQueryParamsFromStruct with slice
type QueryParamsWithSlice struct {
Key1 string `url:"key1"`
Tags []string `url:"tags"`
}
paramsWithSlice := QueryParamsWithSlice{
Key1: "value1",
Tags: []string{"tag1", "tag2"},
}
resp, err = c.R().
SetQueryParamsFromStruct(paramsWithSlice).
Get("/query-parameter")
assertSuccess(t, resp, err)
tests.AssertEqual(t, "key1=value1&key2=client&key3=client&key4=client&key5=client&key5=extra&tags=tag1&tags=tag2", resp.String())
}

func TestPathParam(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions request_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ func SetQueryString(query string) *Request {
return defaultClient.R().SetQueryString(query)
}

// SetQueryParamsFromValues is a global wrapper methods which delegated
// to the default client, create a request and SetQueryParamsFromValues for request.
func SetQueryParamsFromValues(params url.Values) *Request {
return defaultClient.R().SetQueryParamsFromValues(params)
}

// SetQueryParamsFromStruct is a global wrapper methods which delegated
// to the default client, create a request and SetQueryParamsFromStruct for request.
func SetQueryParamsFromStruct(v any) *Request {
return defaultClient.R().SetQueryParamsFromStruct(v)
}

// SetFileReader is a global wrapper methods which delegated
// to the default client, create a request and SetFileReader for request.
func SetFileReader(paramName, filePath string, reader io.Reader) *Request {
Expand Down