From 0949f421ee5bac2df85ffc01be260cc6e02ef49b Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Sun, 1 Dec 2024 17:33:02 +0300 Subject: [PATCH 01/12] chore: simplify parserRequestBodyFile logic --- client/hooks.go | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/client/hooks.go b/client/hooks.go index ec3987938e..eebf8f6b55 100644 --- a/client/hooks.go +++ b/client/hooks.go @@ -1,7 +1,6 @@ package client import ( - "errors" "fmt" "io" "math/rand" @@ -241,8 +240,8 @@ func parserRequestBodyFile(req *Request) error { return fmt.Errorf("write formdata error: %w", err) } - // add file - b := make([]byte, 512) + // add files + fileBuf := make([]byte, 1<<20) // Allocate 1MB buffer for i, v := range req.files { if v.name == "" && v.path == "" { return ErrFileNoName @@ -273,24 +272,10 @@ func parserRequestBodyFile(req *Request) error { return fmt.Errorf("create file error: %w", err) } - for { - n, err := v.reader.Read(b) - if err != nil && !errors.Is(err, io.EOF) { - return fmt.Errorf("read file error: %w", err) - } - - if errors.Is(err, io.EOF) { - break - } - - _, err = w.Write(b[:n]) - if err != nil { - return fmt.Errorf("write file error: %w", err) - } - } + // Copy the file from reader to multipart writer + io.CopyBuffer(w, v.reader, fileBuf) - err = v.reader.Close() - if err != nil { + if err := v.reader.Close(); err != nil { return fmt.Errorf("close file error: %w", err) } } From 7578ae3b199aa2fff383be12916f758894e570ed Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Mon, 2 Dec 2024 12:04:53 +0300 Subject: [PATCH 02/12] client: add support for go1.23 iterators --- client/request.go | 131 ++++++++++++++++++++++++++ client/request_test.go | 203 ++++++++++++++++++++++++++++++++++++++++ client/response.go | 27 ++++++ client/response_test.go | 79 ++++++++++++++++ 4 files changed, 440 insertions(+) diff --git a/client/request.go b/client/request.go index a86d927c4e..92f1558d53 100644 --- a/client/request.go +++ b/client/request.go @@ -5,8 +5,10 @@ import ( "context" "errors" "io" + "iter" "path/filepath" "reflect" + "slices" "strconv" "sync" "time" @@ -129,6 +131,29 @@ func (r *Request) Header(key string) []string { return r.header.PeekMultiple(key) } +// Headers returns all headers in the request using an iterator. +// You can use maps.Collect() to collect all headers into a map. +// +// The returned value is valid until the request object is released. +// Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) Headers() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.header.PeekKeys() + + for _, key := range keys { + vals := r.header.PeekAll(utils.UnsafeString(key)) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(utils.UnsafeString(key), valsStr) { + return + } + } + } +} + // AddHeader method adds a single header field and its value in the request instance. func (r *Request) AddHeader(key, val string) *Request { r.header.Add(key, val) @@ -168,6 +193,33 @@ func (r *Request) Param(key string) []string { return res } +// Params returns all params in the request using an iterator. +// You can use maps.Collect() to collect all params into a map. +// +// The returned value is valid until the request object is released. +// Any future calls to Params method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) Params() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.params.Keys() + + for _, key := range keys { + if key == "" { + continue + } + + vals := r.params.PeekMulti(key) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(key, valsStr) { + return + } + } + } +} + // AddParam method adds a single param field and its value in the request instance. func (r *Request) AddParam(key, val string) *Request { r.params.Add(key, val) @@ -254,6 +306,18 @@ func (r *Request) Cookie(key string) string { return "" } +// Cookies returns all cookies in the cookies using an iterator. +// You can use maps.Collect() to collect all cookies into a map. +func (r *Request) Cookies() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + r.cookies.VisitAll(func(key, val string) { + if !yield(key, val) { + return + } + }) + } +} + // SetCookie method sets a single cookie field and its value in the request instance. // It will override cookie which set in client instance. func (r *Request) SetCookie(key, val string) *Request { @@ -291,6 +355,18 @@ func (r *Request) PathParam(key string) string { return "" } +// PathParams returns all path params in request instance. +// You can use maps.Collect() to collect all cookies into a map. +func (r *Request) PathParams() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + r.path.VisitAll(func(key, val string) { + if !yield(key, val) { + return + } + }) + } +} + // SetPathParam method sets a single path param field and its value in the request instance. // It will override path param which set in client instance. func (r *Request) SetPathParam(key, val string) *Request { @@ -376,6 +452,33 @@ func (r *Request) FormData(key string) []string { return res } +// FormDatas method returns all form datas in request instance. +// You can use maps.Collect() to collect all cookies into a map. +// +// The returned value is valid until the request object is released. +// Any future calls to FormDatas method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) FormDatas() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.formData.Keys() + + for _, key := range keys { + if key == "" { + continue + } + + vals := r.formData.PeekMulti(key) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(key, valsStr) { + return + } + } + } +} + // AddFormData method adds a single form data field and its value in the request instance. func (r *Request) AddFormData(key, val string) *Request { r.formData.AddData(key, val) @@ -435,6 +538,14 @@ func (r *Request) File(name string) *File { return nil } +// Files method returns all files in request instance. +// +// The returned value is valid until the request object is released. +// Any future calls to Files method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) Files() []*File { + return r.files +} + // FileByPath returns file ptr store in request obj by path. func (r *Request) FileByPath(path string) *File { for _, v := range r.files { @@ -617,6 +728,16 @@ type QueryParam struct { *fasthttp.Args } +// Keys method returns all keys in the query params. +func (f *QueryParam) Keys() []string { + keys := make([]string, f.Len()) + f.VisitAll(func(key, value []byte) { + keys = append(keys, utils.UnsafeString(key)) + }) + + return slices.Compact(keys) +} + // AddParams receive a map and add each value to param. func (p *QueryParam) AddParams(r map[string][]string) { for k, v := range r { @@ -747,6 +868,16 @@ type FormData struct { *fasthttp.Args } +// Keys method returns all keys in the form data. +func (f *FormData) Keys() []string { + keys := make([]string, f.Len()) + f.VisitAll(func(key, value []byte) { + keys = append(keys, utils.UnsafeString(key)) + }) + + return slices.Compact(keys) +} + // AddData method is a wrapper of Args's Add method. func (f *FormData) AddData(key, val string) { f.Add(key, val) diff --git a/client/request_test.go b/client/request_test.go index f62865a342..0b5c866395 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -5,6 +5,7 @@ import ( "context" "errors" "io" + "maps" "mime/multipart" "net" "os" @@ -157,6 +158,40 @@ func Test_Request_Header(t *testing.T) { }) } +func Test_Request_Headers(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddHeaders(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + headers := maps.Collect(req.Headers()) + + require.Contains(t, headers["Foo"], "fiber") + require.Contains(t, headers["Foo"], "bar") + require.Contains(t, headers["Bar"], "foo") + + require.Len(t, headers, 2) +} + +func Benchmark_Request_Headers(b *testing.B) { + req := AcquireRequest() + req.AddHeaders(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.Headers() { + } + } +} + func Test_Request_QueryParam(t *testing.T) { t.Parallel() @@ -282,6 +317,40 @@ func Test_Request_QueryParam(t *testing.T) { }) } +func Test_Request_Params(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddParams(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + pathParams := maps.Collect(req.Params()) + + require.Contains(t, pathParams["foo"], "bar") + require.Contains(t, pathParams["foo"], "fiber") + require.Contains(t, pathParams["bar"], "foo") + + require.Len(t, pathParams, 2) +} + +func Benchmark_Request_Params(b *testing.B) { + req := AcquireRequest() + req.AddParams(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.Params() { + } + } +} + func Test_Request_UA(t *testing.T) { t.Parallel() @@ -364,6 +433,39 @@ func Test_Request_Cookie(t *testing.T) { }) } +func Test_Request_Cookies(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.SetCookies(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + cookies := maps.Collect(req.Cookies()) + + require.Equal(t, "bar", cookies["foo"]) + require.Equal(t, "foo", cookies["bar"]) + + require.Len(t, cookies, 2) +} + +func Benchmark_Request_Cookies(b *testing.B) { + req := AcquireRequest() + req.SetCookies(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.Cookies() { + } + } +} + func Test_Request_PathParam(t *testing.T) { t.Parallel() @@ -441,6 +543,39 @@ func Test_Request_PathParam(t *testing.T) { }) } +func Test_Request_PathParams(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.SetPathParams(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + pathParams := maps.Collect(req.PathParams()) + + require.Equal(t, "bar", pathParams["foo"]) + require.Equal(t, "foo", pathParams["bar"]) + + require.Len(t, pathParams, 2) +} + +func Benchmark_Request_PathParams(b *testing.B) { + req := AcquireRequest() + req.SetPathParams(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.PathParams() { + } + } +} + func Test_Request_FormData(t *testing.T) { t.Parallel() @@ -610,6 +745,40 @@ func Test_Request_File(t *testing.T) { }) } +func Test_Request_Files(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddFile("../.github/index.html") + req.AddFiles(AcquireFile(SetFileName("tmp.txt"))) + + files := req.Files() + + require.Equal(t, "../.github/index.html", files[0].path) + require.Nil(t, files[0].reader) + + require.Equal(t, "tmp.txt", files[1].name) + require.Nil(t, files[1].reader) + + require.Len(t, files, 2) +} + +func Benchmark_Request_Files(b *testing.B) { + req := AcquireRequest() + req.AddFile("../.github/index.html") + req.AddFiles(AcquireFile(SetFileName("tmp.txt"))) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for k, v := range req.Files() { + _ = k + _ = v + } + } +} + func Test_Request_Timeout(t *testing.T) { t.Parallel() @@ -1181,6 +1350,40 @@ func Test_Request_Body_With_Server(t *testing.T) { }) } +func Test_Request_FormDatas(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddFormDatas(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + pathParams := maps.Collect(req.FormDatas()) + + require.Contains(t, pathParams["foo"], "bar") + require.Contains(t, pathParams["foo"], "fiber") + require.Contains(t, pathParams["bar"], "foo") + + require.Len(t, pathParams, 2) +} + +func Benchmark_Request_FormDatas(b *testing.B) { + req := AcquireRequest() + req.AddFormDatas(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.FormDatas() { + } + } +} + func Test_Request_Error_Body_With_Server(t *testing.T) { t.Parallel() t.Run("json error", func(t *testing.T) { diff --git a/client/response.go b/client/response.go index e60c6bd0fb..8d21329774 100644 --- a/client/response.go +++ b/client/response.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/fs" + "iter" "os" "path/filepath" "sync" @@ -55,7 +56,33 @@ func (r *Response) Header(key string) string { return utils.UnsafeString(r.RawResponse.Header.Peek(key)) } +// Headers returns all headers in the response using an iterator. +// You can use maps.Collect() to collect all headers into a map. +// +// The returned value is valid until the response object is released. +// Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Response) Headers() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.RawResponse.Header.PeekKeys() + + for _, key := range keys { + vals := r.RawResponse.Header.PeekAll(utils.UnsafeString(key)) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(utils.UnsafeString(key), valsStr) { + return + } + } + } +} + // Cookies method to access all the response cookies. +// +// The returned value is valid until the response object is released. +// Any future calls to Cookies method will return the modified value. Do not store references to returned value. Make copies instead. func (r *Response) Cookies() []*fasthttp.Cookie { return r.cookie } diff --git a/client/response_test.go b/client/response_test.go index bf12e75161..340f1d8a2f 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/tls" "encoding/xml" + "fmt" "io" "net" "os" @@ -199,6 +200,84 @@ func Test_Response_Header(t *testing.T) { resp.Close() } +func Test_Response_Headers(t *testing.T) { + t.Parallel() + + server := startTestServer(t, func(app *fiber.App) { + app.Get("/", func(c fiber.Ctx) error { + c.Response().Header.Add("foo", "bar") + c.Response().Header.Add("foo", "bar2") + c.Response().Header.Add("foo2", "bar") + + return c.SendString("helo world") + }) + }) + defer server.stop() + + client := New().SetDial(server.dial()) + + resp, err := AcquireRequest(). + SetClient(client). + Get("http://example.com") + + require.NoError(t, err) + + headers := make(map[string][]string) + for k, v := range resp.Headers() { + headers[k] = make([]string, 0) + for _, value := range v { + fmt.Print(string(value)) + headers[k] = append(headers[k], string(value)) + } + } + + require.Contains(t, headers["Foo"], "bar") + require.Contains(t, headers["Foo"], "bar2") + require.Contains(t, headers["Foo2"], "bar") + + resp.Close() +} + +func Benchmark_Headers(b *testing.B) { + server := startTestServer( + b, + func(app *fiber.App) { + app.Get("/", func(c fiber.Ctx) error { + c.Response().Header.Add("foo", "bar") + c.Response().Header.Add("foo", "bar2") + c.Response().Header.Add("foo", "bar3") + + c.Response().Header.Add("foo2", "bar") + c.Response().Header.Add("foo2", "bar2") + c.Response().Header.Add("foo2", "bar3") + + return c.SendString("helo world") + }) + }, + ) + + defer server.stop() + + client := New().SetDial(server.dial()) + + b.ResetTimer() + b.ReportAllocs() + + var err error + var resp *Response + for i := 0; i < b.N; i++ { + resp, err = AcquireRequest(). + SetClient(client). + Get("http://example.com") + + for range resp.Headers() { + } + + resp.Close() + } + require.NoError(b, err) +} + func Test_Response_Cookie(t *testing.T) { t.Parallel() From 26a34399068263a180e18daa0da6cda1f3fcd0e9 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:59:17 -0500 Subject: [PATCH 03/12] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- client/hooks.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/hooks.go b/client/hooks.go index eebf8f6b55..4c36145f2f 100644 --- a/client/hooks.go +++ b/client/hooks.go @@ -273,7 +273,9 @@ func parserRequestBodyFile(req *Request) error { } // Copy the file from reader to multipart writer - io.CopyBuffer(w, v.reader, fileBuf) + if _, err := io.CopyBuffer(w, v.reader, fileBuf); err != nil { + return fmt.Errorf("failed to copy file data: %w", err) + } if err := v.reader.Close(); err != nil { return fmt.Errorf("close file error: %w", err) From b57b7c25719a60ce43e4a3a00de6cff7df4b7276 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 17:05:28 +0300 Subject: [PATCH 04/12] fix linter --- client/request.go | 10 +++++----- client/request_test.go | 20 +++++++++++++++----- client/response_test.go | 11 ++++------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/client/request.go b/client/request.go index 92f1558d53..d1b6d2369a 100644 --- a/client/request.go +++ b/client/request.go @@ -729,9 +729,9 @@ type QueryParam struct { } // Keys method returns all keys in the query params. -func (f *QueryParam) Keys() []string { - keys := make([]string, f.Len()) - f.VisitAll(func(key, value []byte) { +func (p *QueryParam) Keys() []string { + keys := make([]string, 0, p.Len()) + p.VisitAll(func(key, _ []byte) { keys = append(keys, utils.UnsafeString(key)) }) @@ -870,8 +870,8 @@ type FormData struct { // Keys method returns all keys in the form data. func (f *FormData) Keys() []string { - keys := make([]string, f.Len()) - f.VisitAll(func(key, value []byte) { + keys := make([]string, 0, f.Len()) + f.VisitAll(func(key, _ []byte) { keys = append(keys, utils.UnsafeString(key)) }) diff --git a/client/request_test.go b/client/request_test.go index 0b5c866395..c875c5ffd1 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -187,7 +187,9 @@ func Benchmark_Request_Headers(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.Headers() { + for k, v := range req.Headers() { + _ = k + _ = v } } } @@ -346,7 +348,9 @@ func Benchmark_Request_Params(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.Params() { + for k, v := range req.Params() { + _ = k + _ = v } } } @@ -461,7 +465,9 @@ func Benchmark_Request_Cookies(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.Cookies() { + for k, v := range req.Cookies() { + _ = k + _ = v } } } @@ -571,7 +577,9 @@ func Benchmark_Request_PathParams(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.PathParams() { + for k, v := range req.PathParams() { + _ = k + _ = v } } } @@ -1379,7 +1387,9 @@ func Benchmark_Request_FormDatas(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.FormDatas() { + for k, v := range req.FormDatas() { + _ = k + _ = v } } } diff --git a/client/response_test.go b/client/response_test.go index 340f1d8a2f..ac1863c1b4 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/tls" "encoding/xml" - "fmt" "io" "net" "os" @@ -224,11 +223,7 @@ func Test_Response_Headers(t *testing.T) { headers := make(map[string][]string) for k, v := range resp.Headers() { - headers[k] = make([]string, 0) - for _, value := range v { - fmt.Print(string(value)) - headers[k] = append(headers[k], string(value)) - } + headers[k] = append(headers[k], v...) } require.Contains(t, headers["Foo"], "bar") @@ -270,7 +265,9 @@ func Benchmark_Headers(b *testing.B) { SetClient(client). Get("http://example.com") - for range resp.Headers() { + for k, v := range resp.Headers() { + _ = k + _ = v } resp.Close() From b4c7c5489b61fd32fba4580bb3a7b8b854d3bacd Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 17:49:13 +0300 Subject: [PATCH 05/12] fix tests --- client/request.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/request.go b/client/request.go index d1b6d2369a..982a1390d9 100644 --- a/client/request.go +++ b/client/request.go @@ -138,7 +138,9 @@ func (r *Request) Header(key string) []string { // Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. func (r *Request) Headers() iter.Seq2[string, []string] { return func(yield func(string, []string) bool) { - keys := r.header.PeekKeys() + peekKeys := r.header.PeekKeys() + keys := make([][]byte, len(peekKeys)) + copy(keys, peekKeys) // It is necessary to have immutable byte slice. for _, key := range keys { vals := r.header.PeekAll(utils.UnsafeString(key)) From 86e93b176f0b00c289a71dfea4b22b38d08ee64a Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 17:57:02 +0300 Subject: [PATCH 06/12] correct benchmark --- client/response_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/response_test.go b/client/response_test.go index ac1863c1b4..b15a524356 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -251,28 +251,28 @@ func Benchmark_Headers(b *testing.B) { }, ) - defer server.stop() - client := New().SetDial(server.dial()) + resp, err := AcquireRequest(). + SetClient(client). + Get("http://example.com") + require.NoError(b, err) + + b.Cleanup(func() { + resp.Close() + server.stop() + }) + b.ResetTimer() b.ReportAllocs() - var err error - var resp *Response for i := 0; i < b.N; i++ { - resp, err = AcquireRequest(). - SetClient(client). - Get("http://example.com") - for k, v := range resp.Headers() { _ = k _ = v } - resp.Close() } - require.NoError(b, err) } func Test_Response_Cookie(t *testing.T) { From 7488d533402321a504af41019dde23124b7f9f05 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 18:02:10 +0300 Subject: [PATCH 07/12] fix linter --- client/response_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/client/response_test.go b/client/response_test.go index b15a524356..dfc85c5485 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -271,7 +271,6 @@ func Benchmark_Headers(b *testing.B) { _ = k _ = v } - } } From 0343f060844af7d894de023907a05c71f0fd071f Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 18:38:40 +0300 Subject: [PATCH 08/12] create docs --- Makefile | 2 +- client/response_test.go | 6 ++- docs/client/request.md | 90 +++++++++++++++++++++++++++++++++++++++++ docs/client/response.md | 34 ++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 87d4b50db5..4b348cd574 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ coverage: format: go run mvdan.cc/gofumpt@latest -w -l . -## markdown: 🎨 Find markdown format issues (Requires markdownlint-cli) +## markdown: 🎨 Find markdown format issues (Requires markdownlint-cli2) .PHONY: markdown markdown: markdownlint-cli2 "**/*.md" "#vendor" diff --git a/client/response_test.go b/client/response_test.go index dfc85c5485..60b87bd155 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -208,7 +208,7 @@ func Test_Response_Headers(t *testing.T) { c.Response().Header.Add("foo", "bar2") c.Response().Header.Add("foo2", "bar") - return c.SendString("helo world") + return c.SendString("hello world") }) }) defer server.stop() @@ -226,10 +226,14 @@ func Test_Response_Headers(t *testing.T) { headers[k] = append(headers[k], v...) } + require.Equal(t, "hello world", resp.String()) + require.Contains(t, headers["Foo"], "bar") require.Contains(t, headers["Foo"], "bar2") require.Contains(t, headers["Foo2"], "bar") + require.Len(t, headers, 3) // Foo + Foo2 + Date + resp.Close() } diff --git a/docs/client/request.md b/docs/client/request.md index 08a04e65e0..38f0cb760e 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -211,6 +211,38 @@ Header method returns header value via key, this method will visit all field in func (r *Request) Header(key string) []string ``` +### Headers + +Headers returns all headers in the request using an iterator. You can use `maps.Collect()` to collect all headers into a map. + +The returned value is valid until the request object is released. Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Request) Headers() iter.Seq2[string, []string] +``` + +```go title="Example" +req := client.AcquireRequest() + +req.AddHeader("Golang", "Fiber") +req.AddHeader("Test", "123456") +req.AddHeader("Test", "654321") + +for k, v := range req.Headers() { + fmt.Printf("Header Key: %s, Header Value: %v\n", k, v) +} +``` + +
+Click here to see the result + +```sh +Header Key: Golang, Header Value: [Fiber] +Header Key: Test, Header Value: [123456 654321] +``` + +
+ ### AddHeader AddHeader method adds a single header field and its value in the request instance. @@ -320,6 +352,15 @@ Param method returns params value via key, this method will visit all field in t func (r *Request) Param(key string) []string ``` +### Params + +Params returns all params in the request using an iterator. You can use `maps.Collect()` to collect all params into a map. +The returned value is valid until the request object is released. Any future calls to Params method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Request) Params() iter.Seq2[string, []string] +``` + ### AddParam AddParam method adds a single param field and its value in the request instance. @@ -502,6 +543,14 @@ Cookie returns the cookie set in the request instance. If the cookie doesn't exi func (r *Request) Cookie(key string) string ``` +### Cookies + +Cookies returns all cookies in the request using an iterator. You can use `maps.Collect()` to collect all cookies into a map. + +```go title="Signature" +func (r *Request) Cookies() iter.Seq2[string, string] +``` + ### SetCookie SetCookie method sets a single cookie field and its value in the request instance. @@ -575,6 +624,14 @@ PathParam returns the path param set in the request instance. If the path param func (r *Request) PathParam(key string) string ``` +### PathParams + +PathParams returns all path params in the request using an iterator. You can use `maps.Collect()` to collect all path params into a map. + +```go title="Signature" +func (r *Request) PathParams() iter.Seq2[string, string] +``` + ### SetPathParam SetPathParam method sets a single path param field and its value in the request instance. @@ -682,6 +739,14 @@ FormData method returns form data value via key, this method will visit all fiel func (r *Request) FormData(key string) []string ``` +### FormDatas + +FormDatas returns all form data in the request using an iterator. You can use `maps.Collect()` to collect all form data into a map. + +```go title="Signature" +func (r *Request) FormDatas() iter.Seq2[string, []string] +``` + ### AddFormData AddFormData method adds a single form data field and its value in the request instance. @@ -817,6 +882,15 @@ If the name field is empty, it will try to match path. func (r *Request) File(name string) *File ``` +### Files + +Files method returns all files in request instance. +The returned value is valid until the request object is released. Any future calls to Files method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Request) Files() []*File +``` + ### FileByPath FileByPath returns file ptr store in request obj by path. @@ -1063,6 +1137,14 @@ type QueryParam struct { } ``` +### Keys + +Keys method returns all keys in the query params. + +```go title="Signature" +func (p *QueryParam) Keys() []string +``` + ### AddParams AddParams receive a map and add each value to param. @@ -1242,6 +1324,14 @@ type FormData struct { } ``` +### Keys + +Keys method returns all keys in the form data. + +```go title="Signature" +func (f *FormData) Keys() []string +``` + ### AddData AddData method is a wrapper of Args's Add method. diff --git a/docs/client/response.md b/docs/client/response.md index 0dba3c6b48..99b48988d9 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -94,9 +94,43 @@ Header method returns the response headers. func (r *Response) Header(key string) string ``` +## Headers + +Headers returns all headers in the response using an iterator. You can use `maps.Collect()` to collect all headers into a map. +The returned value is valid until the response object is released. Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Response) Headers() iter.Seq2[string, []string] +``` + +```go title="Example" +resp, err := client.Get("https://httpbin.org/get") +if err != nil { + panic(err) +} + +for key, values := range resp.Headers() { + fmt.Printf("%s => %s\n", key, strings.Join(values, ", ")) +} +``` + +
+ +Click here to see the result + +```text +Date => Wed, 04 Dec 2024 15:28:29 GMT +Connection => keep-alive +Access-Control-Allow-Origin => * +Access-Control-Allow-Credentials => true +``` + +
+ ## Cookies Cookies method to access all the response cookies. +The returned value is valid until the response object is released. Any future calls to Cookies method will return the modified value. Do not store references to returned value. Make copies instead. ```go title="Signature" func (r *Response) Cookies() []*fasthttp.Cookie From bb43f285293116cc1e791dad34c17215c03496d6 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 18:50:49 +0300 Subject: [PATCH 09/12] update --- docs/client/request.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/client/request.md b/docs/client/request.md index 38f0cb760e..d2c7091f8e 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -214,7 +214,6 @@ func (r *Request) Header(key string) []string ### Headers Headers returns all headers in the request using an iterator. You can use `maps.Collect()` to collect all headers into a map. - The returned value is valid until the request object is released. Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. ```go title="Signature" From cd9b6c9889a4377e5e3486fe6651fd3323b113ed Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Mon, 9 Dec 2024 10:36:11 +0300 Subject: [PATCH 10/12] rename FormDatas -> AllFormData --- client/request.go | 4 ++-- client/request_test.go | 8 ++++---- docs/client/request.md | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/request.go b/client/request.go index 982a1390d9..f303fb7490 100644 --- a/client/request.go +++ b/client/request.go @@ -454,12 +454,12 @@ func (r *Request) FormData(key string) []string { return res } -// FormDatas method returns all form datas in request instance. +// AllFormData method returns all form datas in request instance. // You can use maps.Collect() to collect all cookies into a map. // // The returned value is valid until the request object is released. // Any future calls to FormDatas method will return the modified value. Do not store references to returned value. Make copies instead. -func (r *Request) FormDatas() iter.Seq2[string, []string] { +func (r *Request) AllFormData() iter.Seq2[string, []string] { return func(yield func(string, []string) bool) { keys := r.formData.Keys() diff --git a/client/request_test.go b/client/request_test.go index c875c5ffd1..e663f2feeb 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -1358,7 +1358,7 @@ func Test_Request_Body_With_Server(t *testing.T) { }) } -func Test_Request_FormDatas(t *testing.T) { +func Test_Request_AllFormData(t *testing.T) { t.Parallel() req := AcquireRequest() @@ -1367,7 +1367,7 @@ func Test_Request_FormDatas(t *testing.T) { "bar": {"foo"}, }) - pathParams := maps.Collect(req.FormDatas()) + pathParams := maps.Collect(req.AllFormData()) require.Contains(t, pathParams["foo"], "bar") require.Contains(t, pathParams["foo"], "fiber") @@ -1376,7 +1376,7 @@ func Test_Request_FormDatas(t *testing.T) { require.Len(t, pathParams, 2) } -func Benchmark_Request_FormDatas(b *testing.B) { +func Benchmark_Request_AllFormData(b *testing.B) { req := AcquireRequest() req.AddFormDatas(map[string][]string{ "foo": {"bar", "fiber"}, @@ -1387,7 +1387,7 @@ func Benchmark_Request_FormDatas(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for k, v := range req.FormDatas() { + for k, v := range req.AllFormData() { _ = k _ = v } diff --git a/docs/client/request.md b/docs/client/request.md index d2c7091f8e..f25a9bfad6 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -738,12 +738,12 @@ FormData method returns form data value via key, this method will visit all fiel func (r *Request) FormData(key string) []string ``` -### FormDatas +### AllFormData -FormDatas returns all form data in the request using an iterator. You can use `maps.Collect()` to collect all form data into a map. +AllFormData returns all form data in the request using an iterator. You can use `maps.Collect()` to collect all form data into a map. ```go title="Signature" -func (r *Request) FormDatas() iter.Seq2[string, []string] +func (r *Request) AllFormData() iter.Seq2[string, []string] ``` ### AddFormData From 0b71163cbf90359fce4b78840e5ff807768aa454 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Mon, 9 Dec 2024 10:54:17 +0300 Subject: [PATCH 11/12] add examples for maps.Collect() --- docs/client/request.md | 23 +++++++++++++++++++++++ docs/client/response.md | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/client/request.md b/docs/client/request.md index f25a9bfad6..beb29d385b 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -242,6 +242,29 @@ Header Key: Test, Header Value: [123456 654321] +```go title="Example with maps.Collect()" +req := client.AcquireRequest() + +req.AddHeader("Golang", "Fiber") +req.AddHeader("Test", "123456") +req.AddHeader("Test", "654321") + +headers := maps.Collect(req.Headers()) // Collect all headers into a map +for k, v := range headers { + fmt.Printf("Header Key: %s, Header Value: %v\n", k, v) +} +``` + +
+Click here to see the result + +```sh +Header Key: Golang, Header Value: [Fiber] +Header Key: Test, Header Value: [123456 654321] +``` + +
+ ### AddHeader AddHeader method adds a single header field and its value in the request instance. diff --git a/docs/client/response.md b/docs/client/response.md index 99b48988d9..c5f04ffad5 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -127,6 +127,31 @@ Access-Control-Allow-Credentials => true +```go title="Example with maps.Collect()" +resp, err := client.Get("https://httpbin.org/get") +if err != nil { + panic(err) +} + +headers := maps.Collect(resp.Headers()) // Collect all headers into a map +for key, values := range headers { + fmt.Printf("%s => %s\n", key, strings.Join(values, ", ")) +} +``` + +
+ +Click here to see the result + +```text +Date => Wed, 04 Dec 2024 15:28:29 GMT +Connection => keep-alive +Access-Control-Allow-Origin => * +Access-Control-Allow-Credentials => true +``` + +
+ ## Cookies Cookies method to access all the response cookies. From fa4c54a4751f4d736ed4094a86b3c30c2f8c24c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Tue, 10 Dec 2024 10:22:58 +0100 Subject: [PATCH 12/12] change request/response markdown examples --- docs/client/request.md | 84 ++++++++++++++++++++--------------------- docs/client/response.md | 32 ++++++++-------- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/docs/client/request.md b/docs/client/request.md index beb29d385b..9789ea0d9a 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -220,6 +220,9 @@ The returned value is valid until the request object is released. Any future cal func (r *Request) Headers() iter.Seq2[string, []string] ``` +
+Example + ```go title="Example" req := client.AcquireRequest() @@ -232,9 +235,6 @@ for k, v := range req.Headers() { } ``` -
-Click here to see the result - ```sh Header Key: Golang, Header Value: [Fiber] Header Key: Test, Header Value: [123456 654321] @@ -242,6 +242,9 @@ Header Key: Test, Header Value: [123456 654321]
+
+Example with maps.Collect() + ```go title="Example with maps.Collect()" req := client.AcquireRequest() @@ -255,9 +258,6 @@ for k, v := range headers { } ``` -
-Click here to see the result - ```sh Header Key: Golang, Header Value: [Fiber] Header Key: Test, Header Value: [123456 654321] @@ -273,6 +273,9 @@ AddHeader method adds a single header field and its value in the request instanc func (r *Request) AddHeader(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -289,9 +292,6 @@ if err != nil { fmt.Println(resp.String()) ``` -
-Click here to see the result - ```json { "headers": { @@ -316,6 +316,9 @@ It will override the header which has been set in the client instance. func (r *Request) SetHeader(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -331,9 +334,6 @@ if err != nil { fmt.Println(resp.String()) ``` -
-Click here to see the result - ```json { "headers": { @@ -391,6 +391,9 @@ AddParam method adds a single param field and its value in the request instance. func (r *Request) AddParam(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -407,9 +410,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "Content-Length": "145", @@ -459,6 +459,9 @@ It will override param, which has been set in client instance. func (r *Request) SetParamsWithStruct(v any) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -482,9 +485,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "Content-Length": "147", @@ -591,6 +591,9 @@ It will override the cookie which is set in the client instance. func (r *Request) SetCookies(m map[string]string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -608,9 +611,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "cookies": { @@ -663,6 +663,9 @@ It will override path param which set in client instance. func (r *Request) SetPathParam(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -677,9 +680,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```plaintext Gofiber ``` @@ -777,6 +777,9 @@ AddFormData method adds a single form data field and its value in the request in func (r *Request) AddFormData(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -793,9 +796,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -822,6 +822,9 @@ SetFormData method sets a single form data field and its value in the request in func (r *Request) SetFormData(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -837,9 +840,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -929,6 +929,9 @@ AddFile method adds a single file field and its value in the request instance vi func (r *Request) AddFile(path string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -943,9 +946,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -968,6 +968,9 @@ AddFileWithReader method adds a single field and its value in the request instan func (r *Request) AddFileWithReader(name string, reader io.ReadCloser) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -983,9 +986,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -1025,6 +1025,9 @@ It will override timeout which set in client instance. func (r *Request) SetTimeout(t time.Duration) *Request ``` +
+Example 1 + ```go title="Example 1" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -1039,9 +1042,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -1054,6 +1054,9 @@ fmt.Println(string(resp.Body()))
+
+Example 2 + ```go title="Example 2" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -1068,9 +1071,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```shell panic: timeout or cancel diff --git a/docs/client/response.md b/docs/client/response.md index c5f04ffad5..135970069c 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -68,6 +68,9 @@ Protocol method returns the HTTP response protocol used for the request. func (r *Response) Protocol() string ``` +
+Example + ```go title="Example" resp, err := client.Get("https://httpbin.org/get") if err != nil { @@ -77,9 +80,6 @@ if err != nil { fmt.Println(resp.Protocol()) ``` -
-Click here to see the result - ```text HTTP/1.1 ``` @@ -103,6 +103,9 @@ The returned value is valid until the response object is released. Any future ca func (r *Response) Headers() iter.Seq2[string, []string] ``` +
+Example + ```go title="Example" resp, err := client.Get("https://httpbin.org/get") if err != nil { @@ -114,10 +117,6 @@ for key, values := range resp.Headers() { } ``` -
- -Click here to see the result - ```text Date => Wed, 04 Dec 2024 15:28:29 GMT Connection => keep-alive @@ -127,6 +126,9 @@ Access-Control-Allow-Credentials => true
+
+Example with maps.Collect() + ```go title="Example with maps.Collect()" resp, err := client.Get("https://httpbin.org/get") if err != nil { @@ -139,10 +141,6 @@ for key, values := range headers { } ``` -
- -Click here to see the result - ```text Date => Wed, 04 Dec 2024 15:28:29 GMT Connection => keep-alive @@ -161,6 +159,9 @@ The returned value is valid until the response object is released. Any future ca func (r *Response) Cookies() []*fasthttp.Cookie ``` +
+Example + ```go title="Example" resp, err := client.Get("https://httpbin.org/cookies/set/go/fiber") if err != nil { @@ -173,9 +174,6 @@ for _, cookie := range cookies { } ``` -
-Click here to see the result - ```text go => fiber ``` @@ -206,6 +204,9 @@ JSON method will unmarshal body to json. func (r *Response) JSON(v any) error ``` +
+Example + ```go title="Example" type Body struct { Slideshow struct { @@ -229,9 +230,6 @@ if err != nil { fmt.Printf("%+v\n", out) ``` -
-Click here to see the result - ```text {Slideshow:{Author:Yours Truly Date:date of publication Title:Sample Slide Show}} ```