Skip to content

Commit

Permalink
feat(curl): generate curl cmd for request && example for curl cmd (#794)
Browse files Browse the repository at this point in the history
* feat(curl): generate curl cmd for request && example for curl cmd

* refactor(curl): Simplified code

1. refactor `GetCurlCommand` with the name `GenerateCurlCommand`
2. un-export this method `BuildCurlRequest`
3. remove SetResultCurlCmd

* cicd(test): add "-coverpkg=./..." to measure the test coverage of packages that are imported in different packages
  • Loading branch information
ahuigo authored Jun 28, 2024
1 parent baf7c12 commit 855d418
Show file tree
Hide file tree
Showing 11 changed files with 457 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
run: diff -u <(echo -n) <(go fmt $(go list ./...))

- name: Test
run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic
run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/label-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
cache-dependency-path: go.sum

- name: Test
run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic
run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...

- name: Coverage
run: bash <(curl -s https://codecov.io/bash)
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ _testmain.go
coverage.out
coverage.txt

# Exclude intellij IDE folders
# Exclude IDE folders
.idea/*
.vscode/*
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ client := resty.New()
resp, err := client.R().
EnableTrace().
Get("https://httpbin.org/get")
curlCmdExecuted := resp.Request.GenerateCurlCommand()


// Explore curl command
fmt.Println("Curl Command:\n ", curlCmdExecuted+"\n")

// Explore response object
fmt.Println("Response Info:")
Expand Down Expand Up @@ -160,6 +165,9 @@ fmt.Println(" RequestAttempt:", ti.RequestAttempt)
fmt.Println(" RemoteAddr :", ti.RemoteAddr.String())

/* Output
Curl Command:
curl -X GET -H 'User-Agent: go-resty/2.12.0 (https://github.com/go-resty/resty)' https://httpbin.org/get
Response Info:
Error : <nil>
Status Code: 200
Expand Down
24 changes: 16 additions & 8 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1148,9 +1148,7 @@ func (c *Client) Clone() *Client {
// Client Unexported methods
//_______________________________________________________________________

// Executes method executes the given `Request` object and returns response
// error.
func (c *Client) execute(req *Request) (*Response, error) {
func (c *Client) executeBefore(req *Request) error {
// Lock the user-defined pre-request hooks.
c.udBeforeRequestLock.RLock()
defer c.udBeforeRequestLock.RUnlock()
Expand All @@ -1166,22 +1164,22 @@ func (c *Client) execute(req *Request) (*Response, error) {
// to modify the *resty.Request object
for _, f := range c.udBeforeRequest {
if err = f(c, req); err != nil {
return nil, wrapNoRetryErr(err)
return wrapNoRetryErr(err)
}
}

// If there is a rate limiter set for this client, the Execute call
// will return an error if the rate limit is exceeded.
if req.client.rateLimiter != nil {
if !req.client.rateLimiter.Allow() {
return nil, wrapNoRetryErr(ErrRateLimitExceeded)
return wrapNoRetryErr(ErrRateLimitExceeded)
}
}

// resty middlewares
for _, f := range c.beforeRequest {
if err = f(c, req); err != nil {
return nil, wrapNoRetryErr(err)
return wrapNoRetryErr(err)
}
}

Expand All @@ -1192,15 +1190,24 @@ func (c *Client) execute(req *Request) (*Response, error) {
// call pre-request if defined
if c.preReqHook != nil {
if err = c.preReqHook(c, req.RawRequest); err != nil {
return nil, wrapNoRetryErr(err)
return wrapNoRetryErr(err)
}
}

if err = requestLogger(c, req); err != nil {
return nil, wrapNoRetryErr(err)
return wrapNoRetryErr(err)
}

req.RawRequest.Body = newRequestBodyReleaser(req.RawRequest.Body, req.bodyBuf)
return nil
}

// Executes method executes the given `Request` object and returns response
// error.
func (c *Client) execute(req *Request) (*Response, error) {
if err := c.executeBefore(req); err != nil {
return nil, err
}

req.Time = time.Now()
resp, err := c.httpClient.Do(req.RawRequest)
Expand Down Expand Up @@ -1396,6 +1403,7 @@ func createClient(hc *http.Client) *Client {
parseRequestBody,
createHTTPRequest,
addCredentials,
createCurlCmd,
}

// user defined request middlewares
Expand Down
126 changes: 126 additions & 0 deletions examples/debug_curl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package examples

import (
"io"
"net/http"
"os"
"strings"
"testing"

"github.com/go-resty/resty/v2"
)

// 1. Generate curl for unexecuted request(dry-run)
func TestGenerateUnexcutedCurl(t *testing.T) {
ts := createHttpbinServer(0)
defer ts.Close()

req := resty.New().R().SetBody(map[string]string{
"name": "Alex",
}).SetCookies(
[]*http.Cookie{
{Name: "count", Value: "1"},
},
)

curlCmdUnexecuted := req.GenerateCurlCommand()

if !strings.Contains(curlCmdUnexecuted, "Cookie: count=1") ||
!strings.Contains(curlCmdUnexecuted, "curl -X GET") ||
!strings.Contains(curlCmdUnexecuted, `-d '{"name":"Alex"}'`) {
t.Fatal("Incomplete curl:", curlCmdUnexecuted)
} else {
t.Log("curlCmdUnexecuted: \n", curlCmdUnexecuted)
}

}

// 2. Generate curl for executed request
func TestGenerateExecutedCurl(t *testing.T) {
ts := createHttpbinServer(0)
defer ts.Close()

data := map[string]string{
"name": "Alex",
}
req := resty.New().R().SetBody(data).SetCookies(
[]*http.Cookie{
{Name: "count", Value: "1"},
},
)

url := ts.URL + "/post"
resp, err := req.
EnableTrace().
Post(url)
if err != nil {
t.Fatal(err)
}
curlCmdExecuted := resp.Request.GenerateCurlCommand()
if !strings.Contains(curlCmdExecuted, "Cookie: count=1") ||
!strings.Contains(curlCmdExecuted, "curl -X POST") ||
!strings.Contains(curlCmdExecuted, `-d '{"name":"Alex"}'`) ||
!strings.Contains(curlCmdExecuted, url) {
t.Fatal("Incomplete curl:", curlCmdExecuted)
} else {
t.Log("curlCmdExecuted: \n", curlCmdExecuted)
}
}

// 3. Generate curl in debug mode
func TestDebugModeCurl(t *testing.T) {
ts := createHttpbinServer(0)
defer ts.Close()

// 1. Capture stderr
getOutput, restore := captureStderr()
defer restore()

// 2. Build request
req := resty.New().R().SetBody(map[string]string{
"name": "Alex",
}).SetCookies(
[]*http.Cookie{
{Name: "count", Value: "1"},
},
)

// 3. Execute request: set debug mode
url := ts.URL + "/post"
_, err := req.SetDebug(true).Post(url)
if err != nil {
t.Fatal(err)
}

// 4. test output curl
output := getOutput()
if !strings.Contains(output, "Cookie: count=1") ||
!strings.Contains(output, `-d '{"name":"Alex"}'`) {
t.Fatal("Incomplete debug curl info:", output)
} else {
t.Log("Normal debug curl info: \n", output)
}
}

func captureStderr() (getOutput func() string, restore func()) {
old := os.Stdout
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
os.Stderr = w
getOutput = func() string {
w.Close()
buf := make([]byte, 2048)
n, err := r.Read(buf)
if err != nil && err != io.EOF {
panic(err)
}
return string(buf[:n])
}
restore = func() {
os.Stderr = old
w.Close()
}
return getOutput, restore
}
Loading

0 comments on commit 855d418

Please sign in to comment.