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

Support RawPathParams without escaping #664

Merged
merged 1 commit into from Sep 17, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 60 additions & 4 deletions client.go
Expand Up @@ -103,6 +103,7 @@ type Client struct {
QueryParam url.Values
FormData url.Values
PathParams map[string]string
RawPathParams map[string]string
Header http.Header
UserInfo *User
Token string
Expand Down Expand Up @@ -441,6 +442,7 @@ func (c *Client) R() *Request {
multipartFiles: []*File{},
multipartFields: []*MultipartField{},
PathParams: map[string]string{},
RawPathParams: map[string]string{},
jsonEscapeHTML: true,
log: c.log,
}
Expand Down Expand Up @@ -964,6 +966,7 @@ func (c *Client) SetDoNotParseResponse(parse bool) *Client {
// Composed URL - /v1/users/sample@sample.com/details
//
// It replaces the value of the key while composing the request URL.
// The value will be escaped using `url.PathEscape` function.
//
// Also it can be overridden at request level Path Params options,
// see `Request.SetPathParam` or `Request.SetPathParams`.
Expand All @@ -976,15 +979,17 @@ func (c *Client) SetPathParam(param, value string) *Client {
// Resty client instance.
//
// client.SetPathParams(map[string]string{
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// "path": "groups/developers",
// })
//
// Result:
// URL - /v1/users/{userId}/{subAccountId}/details
// Composed URL - /v1/users/sample@sample.com/100002/details
// URL - /v1/users/{userId}/{subAccountId}/{path}/details
// Composed URL - /v1/users/sample@sample.com/100002/groups%2Fdevelopers/details
//
// It replaces the value of the key while composing the request URL.
// The values will be escaped using `url.PathEscape` function.
//
// Also it can be overridden at request level Path Params options,
// see `Request.SetPathParam` or `Request.SetPathParams`.
Expand All @@ -995,6 +1000,56 @@ func (c *Client) SetPathParams(params map[string]string) *Client {
return c
}

// SetRawPathParam method sets single URL path key-value pair in the
// Resty client instance.
//
// client.SetPathParam("userId", "sample@sample.com")
//
// Result:
// URL - /v1/users/{userId}/details
// Composed URL - /v1/users/sample@sample.com/details
//
// client.SetPathParam("path", "groups/developers")
//
// Result:
// URL - /v1/users/{userId}/details
// Composed URL - /v1/users/groups%2Fdevelopers/details
//
// It replaces the value of the key while composing the request URL.
// The value will be used as it is and will not be escaped.
//
// Also it can be overridden at request level Path Params options,
// see `Request.SetPathParam` or `Request.SetPathParams`.
func (c *Client) SetRawPathParam(param, value string) *Client {
c.RawPathParams[param] = value
return c
}

// SetRawPathParams method sets multiple URL path key-value pairs at one go in the
// Resty client instance.
//
// client.SetPathParams(map[string]string{
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// "path": "groups/developers",
// })
//
// Result:
// URL - /v1/users/{userId}/{subAccountId}/{path}/details
// Composed URL - /v1/users/sample@sample.com/100002/groups/developers/details
//
// It replaces the value of the key while composing the request URL.
// The values will be used as they are and will not be escaped.
//
// Also it can be overridden at request level Path Params options,
// see `Request.SetPathParam` or `Request.SetPathParams`.
func (c *Client) SetRawPathParams(params map[string]string) *Client {
for p, v := range params {
c.SetRawPathParam(p, v)
}
return c
}

// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.
//
// Note: This option only applicable to standard JSON Marshaller.
Expand Down Expand Up @@ -1257,6 +1312,7 @@ func createClient(hc *http.Client) *Client {
RetryWaitTime: defaultWaitTime,
RetryMaxWaitTime: defaultMaxWaitTime,
PathParams: make(map[string]string),
RawPathParams: make(map[string]string),
JSONMarshal: json.Marshal,
JSONUnmarshal: json.Unmarshal,
XMLMarshal: xml.Marshal,
Expand Down
12 changes: 12 additions & 0 deletions middleware.go
Expand Up @@ -38,6 +38,18 @@ func parseRequestURL(c *Client, r *Request) error {
}
}

// GitHub #663 Raw Path Params
if len(r.RawPathParams) > 0 {
for p, v := range r.RawPathParams {
r.URL = strings.Replace(r.URL, "{"+p+"}", v, -1)
}
}
if len(c.RawPathParams) > 0 {
for p, v := range c.RawPathParams {
r.URL = strings.Replace(r.URL, "{"+p+"}", v, -1)
}
}

// Parsing request URL
reqURL, err := url.Parse(r.URL)
if err != nil {
Expand Down
112 changes: 88 additions & 24 deletions request.go
Expand Up @@ -27,22 +27,23 @@ import (
// resty client. Request provides an options to override client level
// settings and also an options for the request composition.
type Request struct {
URL string
Method string
Token string
AuthScheme string
QueryParam url.Values
FormData url.Values
PathParams map[string]string
Header http.Header
Time time.Time
Body interface{}
Result interface{}
Error interface{}
RawRequest *http.Request
SRV *SRVRecord
UserInfo *User
Cookies []*http.Cookie
URL string
Method string
Token string
AuthScheme string
QueryParam url.Values
FormData url.Values
PathParams map[string]string
RawPathParams map[string]string
Header http.Header
Time time.Time
Body interface{}
Result interface{}
Error interface{}
RawRequest *http.Request
SRV *SRVRecord
UserInfo *User
Cookies []*http.Cookie

// Attempt is to represent the request attempt made during a Resty
// request execution flow, including retry count.
Expand Down Expand Up @@ -578,8 +579,17 @@ func (r *Request) SetDoNotParseResponse(parse bool) *Request {
// URL - /v1/users/{userId}/details
// Composed URL - /v1/users/sample@sample.com/details
//
// It replaces the value of the key while composing the request URL. Also you can
// override Path Params value, which was set at client instance level.
// client.R().SetPathParam("path", "groups/developers")
//
// Result:
// URL - /v1/users/{userId}/details
// Composed URL - /v1/users/groups%2Fdevelopers/details
//
// It replaces the value of the key while composing the request URL.
// The values will be escaped using `url.PathEscape` function.
//
// Also you can override Path Params value, which was set at client instance
// level.
func (r *Request) SetPathParam(param, value string) *Request {
r.PathParams[param] = value
return r
Expand All @@ -589,23 +599,77 @@ func (r *Request) SetPathParam(param, value string) *Request {
// Resty current request instance.
//
// client.R().SetPathParams(map[string]string{
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// "path": "groups/developers",
// })
//
// Result:
// URL - /v1/users/{userId}/{subAccountId}/details
// Composed URL - /v1/users/sample@sample.com/100002/details
// URL - /v1/users/{userId}/{subAccountId}/{path}/details
// Composed URL - /v1/users/sample@sample.com/100002/groups%2Fdevelopers/details
//
// It replaces the value of the key while composing request URL. Also you can
// override Path Params value, which was set at client instance level.
// It replaces the value of the key while composing request URL.
// The value will be used as it is and will not be escaped.
//
// Also you can override Path Params value, which was set at client instance
// level.
func (r *Request) SetPathParams(params map[string]string) *Request {
for p, v := range params {
r.SetPathParam(p, v)
}
return r
}

// SetRawPathParam method sets single URL path key-value pair in the
// Resty current request instance.
//
// client.R().SetPathParam("userId", "sample@sample.com")
//
// Result:
// URL - /v1/users/{userId}/details
// Composed URL - /v1/users/sample@sample.com/details
//
// client.R().SetPathParam("path", "groups/developers")
//
// Result:
// URL - /v1/users/{userId}/details
// Composed URL - /v1/users/groups/developers/details
//
// It replaces the value of the key while composing the request URL.
// The value will be used as it is and will not be escaped.
//
// Also you can override Path Params value, which was set at client instance
// level.
func (r *Request) SetRawPathParam(param, value string) *Request {
r.RawPathParams[param] = value
return r
}

// SetRawPathParams method sets multiple URL path key-value pairs at one go in the
// Resty current request instance.
//
// client.R().SetPathParams(map[string]string{
// "userId": "sample@sample.com",
// "subAccountId": "100002",
// "path": "groups/developers",
// })
//
// Result:
// URL - /v1/users/{userId}/{subAccountId}/{path}/details
// Composed URL - /v1/users/sample@sample.com/100002/groups/developers/details
//
// It replaces the value of the key while composing request URL.
// The values will be used as they are and will not be escaped.
//
// Also you can override Path Params value, which was set at client instance
// level.
func (r *Request) SetRawPathParams(params map[string]string) *Request {
for p, v := range params {
r.SetRawPathParam(p, v)
}
return r
}

// ExpectContentType method allows to provide fallback `Content-Type` for automatic unmarshalling
// when `Content-Type` response header is unavailable.
func (r *Request) ExpectContentType(contentType string) *Request {
Expand Down
34 changes: 30 additions & 4 deletions request_test.go
Expand Up @@ -1636,7 +1636,7 @@ func TestGetPathParamAndPathParams(t *testing.T) {
defer ts.Close()

c := dc().
SetHostURL(ts.URL).
SetBaseURL(ts.URL).
SetPathParam("userId", "sample@sample.com")

resp, err := c.R().SetPathParam("subAccountId", "100002").
Expand Down Expand Up @@ -1749,21 +1749,47 @@ func TestPathParamURLInput(t *testing.T) {
defer ts.Close()

c := dc().SetDebug(true).
SetHostURL(ts.URL).
SetBaseURL(ts.URL).
SetPathParams(map[string]string{
"userId": "sample@sample.com",
"path": "users/developers",
})

resp, err := c.R().
SetPathParams(map[string]string{
"subAccountId": "100002",
"website": "https://example.com",
}).Get("/v1/users/{userId}/{subAccountId}/{website}")
}).Get("/v1/users/{userId}/{subAccountId}/{path}/{website}")

assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
assertEqual(t, true, strings.Contains(resp.String(), "TestPathParamURLInput: text response"))
assertEqual(t, true, strings.Contains(resp.String(), "/v1/users/sample@sample.com/100002/https:%2F%2Fexample.com"))
assertEqual(t, true, strings.Contains(resp.String(), "/v1/users/sample@sample.com/100002/users%2Fdevelopers/https:%2F%2Fexample.com"))

logResponse(t, resp)
}

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

c := dc().SetDebug(true).
SetBaseURL(ts.URL).
SetRawPathParams(map[string]string{
"userId": "sample@sample.com",
"path": "users/developers",
})

resp, err := c.R().
SetRawPathParams(map[string]string{
"subAccountId": "100002",
"website": "https://example.com",
}).Get("/v1/users/{userId}/{subAccountId}/{path}/{website}")

assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
assertEqual(t, true, strings.Contains(resp.String(), "TestPathParamURLInput: text response"))
assertEqual(t, true, strings.Contains(resp.String(), "/v1/users/sample@sample.com/100002/users/developers/https://example.com"))

logResponse(t, resp)
}
Expand Down