Skip to content

Commit 855d418

Browse files
authored
feat(curl): generate curl cmd for request && example for curl cmd (#794)
* 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
1 parent baf7c12 commit 855d418

File tree

11 files changed

+457
-11
lines changed

11 files changed

+457
-11
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
run: diff -u <(echo -n) <(go fmt $(go list ./...))
4444

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

4848
- name: Upload coverage to Codecov
4949
uses: codecov/codecov-action@v4

.github/workflows/label-actions.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
cache-dependency-path: go.sum
3030

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

3434
- name: Coverage
3535
run: bash <(curl -s https://codecov.io/bash)

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ _testmain.go
2626
coverage.out
2727
coverage.txt
2828

29-
# Exclude intellij IDE folders
29+
# Exclude IDE folders
3030
.idea/*
31+
.vscode/*

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ client := resty.New()
131131
resp, err := client.R().
132132
EnableTrace().
133133
Get("https://httpbin.org/get")
134+
curlCmdExecuted := resp.Request.GenerateCurlCommand()
135+
136+
137+
// Explore curl command
138+
fmt.Println("Curl Command:\n ", curlCmdExecuted+"\n")
134139

135140
// Explore response object
136141
fmt.Println("Response Info:")
@@ -160,6 +165,9 @@ fmt.Println(" RequestAttempt:", ti.RequestAttempt)
160165
fmt.Println(" RemoteAddr :", ti.RemoteAddr.String())
161166

162167
/* Output
168+
Curl Command:
169+
curl -X GET -H 'User-Agent: go-resty/2.12.0 (https://github.com/go-resty/resty)' https://httpbin.org/get
170+
163171
Response Info:
164172
Error : <nil>
165173
Status Code: 200

client.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -1148,9 +1148,7 @@ func (c *Client) Clone() *Client {
11481148
// Client Unexported methods
11491149
//_______________________________________________________________________
11501150

1151-
// Executes method executes the given `Request` object and returns response
1152-
// error.
1153-
func (c *Client) execute(req *Request) (*Response, error) {
1151+
func (c *Client) executeBefore(req *Request) error {
11541152
// Lock the user-defined pre-request hooks.
11551153
c.udBeforeRequestLock.RLock()
11561154
defer c.udBeforeRequestLock.RUnlock()
@@ -1166,22 +1164,22 @@ func (c *Client) execute(req *Request) (*Response, error) {
11661164
// to modify the *resty.Request object
11671165
for _, f := range c.udBeforeRequest {
11681166
if err = f(c, req); err != nil {
1169-
return nil, wrapNoRetryErr(err)
1167+
return wrapNoRetryErr(err)
11701168
}
11711169
}
11721170

11731171
// If there is a rate limiter set for this client, the Execute call
11741172
// will return an error if the rate limit is exceeded.
11751173
if req.client.rateLimiter != nil {
11761174
if !req.client.rateLimiter.Allow() {
1177-
return nil, wrapNoRetryErr(ErrRateLimitExceeded)
1175+
return wrapNoRetryErr(ErrRateLimitExceeded)
11781176
}
11791177
}
11801178

11811179
// resty middlewares
11821180
for _, f := range c.beforeRequest {
11831181
if err = f(c, req); err != nil {
1184-
return nil, wrapNoRetryErr(err)
1182+
return wrapNoRetryErr(err)
11851183
}
11861184
}
11871185

@@ -1192,15 +1190,24 @@ func (c *Client) execute(req *Request) (*Response, error) {
11921190
// call pre-request if defined
11931191
if c.preReqHook != nil {
11941192
if err = c.preReqHook(c, req.RawRequest); err != nil {
1195-
return nil, wrapNoRetryErr(err)
1193+
return wrapNoRetryErr(err)
11961194
}
11971195
}
11981196

11991197
if err = requestLogger(c, req); err != nil {
1200-
return nil, wrapNoRetryErr(err)
1198+
return wrapNoRetryErr(err)
12011199
}
12021200

12031201
req.RawRequest.Body = newRequestBodyReleaser(req.RawRequest.Body, req.bodyBuf)
1202+
return nil
1203+
}
1204+
1205+
// Executes method executes the given `Request` object and returns response
1206+
// error.
1207+
func (c *Client) execute(req *Request) (*Response, error) {
1208+
if err := c.executeBefore(req); err != nil {
1209+
return nil, err
1210+
}
12041211

12051212
req.Time = time.Now()
12061213
resp, err := c.httpClient.Do(req.RawRequest)
@@ -1396,6 +1403,7 @@ func createClient(hc *http.Client) *Client {
13961403
parseRequestBody,
13971404
createHTTPRequest,
13981405
addCredentials,
1406+
createCurlCmd,
13991407
}
14001408

14011409
// user defined request middlewares

examples/debug_curl_test.go

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package examples
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
"github.com/go-resty/resty/v2"
11+
)
12+
13+
// 1. Generate curl for unexecuted request(dry-run)
14+
func TestGenerateUnexcutedCurl(t *testing.T) {
15+
ts := createHttpbinServer(0)
16+
defer ts.Close()
17+
18+
req := resty.New().R().SetBody(map[string]string{
19+
"name": "Alex",
20+
}).SetCookies(
21+
[]*http.Cookie{
22+
{Name: "count", Value: "1"},
23+
},
24+
)
25+
26+
curlCmdUnexecuted := req.GenerateCurlCommand()
27+
28+
if !strings.Contains(curlCmdUnexecuted, "Cookie: count=1") ||
29+
!strings.Contains(curlCmdUnexecuted, "curl -X GET") ||
30+
!strings.Contains(curlCmdUnexecuted, `-d '{"name":"Alex"}'`) {
31+
t.Fatal("Incomplete curl:", curlCmdUnexecuted)
32+
} else {
33+
t.Log("curlCmdUnexecuted: \n", curlCmdUnexecuted)
34+
}
35+
36+
}
37+
38+
// 2. Generate curl for executed request
39+
func TestGenerateExecutedCurl(t *testing.T) {
40+
ts := createHttpbinServer(0)
41+
defer ts.Close()
42+
43+
data := map[string]string{
44+
"name": "Alex",
45+
}
46+
req := resty.New().R().SetBody(data).SetCookies(
47+
[]*http.Cookie{
48+
{Name: "count", Value: "1"},
49+
},
50+
)
51+
52+
url := ts.URL + "/post"
53+
resp, err := req.
54+
EnableTrace().
55+
Post(url)
56+
if err != nil {
57+
t.Fatal(err)
58+
}
59+
curlCmdExecuted := resp.Request.GenerateCurlCommand()
60+
if !strings.Contains(curlCmdExecuted, "Cookie: count=1") ||
61+
!strings.Contains(curlCmdExecuted, "curl -X POST") ||
62+
!strings.Contains(curlCmdExecuted, `-d '{"name":"Alex"}'`) ||
63+
!strings.Contains(curlCmdExecuted, url) {
64+
t.Fatal("Incomplete curl:", curlCmdExecuted)
65+
} else {
66+
t.Log("curlCmdExecuted: \n", curlCmdExecuted)
67+
}
68+
}
69+
70+
// 3. Generate curl in debug mode
71+
func TestDebugModeCurl(t *testing.T) {
72+
ts := createHttpbinServer(0)
73+
defer ts.Close()
74+
75+
// 1. Capture stderr
76+
getOutput, restore := captureStderr()
77+
defer restore()
78+
79+
// 2. Build request
80+
req := resty.New().R().SetBody(map[string]string{
81+
"name": "Alex",
82+
}).SetCookies(
83+
[]*http.Cookie{
84+
{Name: "count", Value: "1"},
85+
},
86+
)
87+
88+
// 3. Execute request: set debug mode
89+
url := ts.URL + "/post"
90+
_, err := req.SetDebug(true).Post(url)
91+
if err != nil {
92+
t.Fatal(err)
93+
}
94+
95+
// 4. test output curl
96+
output := getOutput()
97+
if !strings.Contains(output, "Cookie: count=1") ||
98+
!strings.Contains(output, `-d '{"name":"Alex"}'`) {
99+
t.Fatal("Incomplete debug curl info:", output)
100+
} else {
101+
t.Log("Normal debug curl info: \n", output)
102+
}
103+
}
104+
105+
func captureStderr() (getOutput func() string, restore func()) {
106+
old := os.Stdout
107+
r, w, err := os.Pipe()
108+
if err != nil {
109+
panic(err)
110+
}
111+
os.Stderr = w
112+
getOutput = func() string {
113+
w.Close()
114+
buf := make([]byte, 2048)
115+
n, err := r.Read(buf)
116+
if err != nil && err != io.EOF {
117+
panic(err)
118+
}
119+
return string(buf[:n])
120+
}
121+
restore = func() {
122+
os.Stderr = old
123+
w.Close()
124+
}
125+
return getOutput, restore
126+
}

0 commit comments

Comments
 (0)