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

🔥 feat: Add support for iterator methods to Fiber client #3228

Merged
merged 15 commits into from
Dec 10, 2024
25 changes: 5 additions & 20 deletions client/hooks.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package client

import (
"errors"
"fmt"
"io"
"math/rand"
Expand Down Expand Up @@ -241,8 +240,8 @@
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
Expand Down Expand Up @@ -273,24 +272,10 @@
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)

Check failure on line 276 in client/hooks.go

View workflow job for this annotation

GitHub Actions / lint

unhandled-error: Unhandled error in call to function io.CopyBuffer (revive)

Check failure on line 276 in client/hooks.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `io.CopyBuffer` is not checked (errcheck)
gaby marked this conversation as resolved.
Show resolved Hide resolved

err = v.reader.Close()
if err != nil {
if err := v.reader.Close(); err != nil {
return fmt.Errorf("close file error: %w", err)
}
}
Expand Down
131 changes: 131 additions & 0 deletions client/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
"context"
"errors"
"io"
"iter"
"path/filepath"
"reflect"
"slices"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -129,6 +131,29 @@
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)
Expand Down Expand Up @@ -168,6 +193,33 @@
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)
Expand Down Expand Up @@ -254,6 +306,18 @@
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VisitAll in its current form does not support early returns. If the caller of the iterator stops iterating early (e.g. by break in for range), yield would be called again after it returned false, and this causes a runtime panic.

Please update VisitAll to support early returns, and add a test to verify that stopping the iterator early works.

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 {
Expand Down Expand Up @@ -291,6 +355,18 @@
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the above.

Thanks for the report. Will fix it

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 {
Expand Down Expand Up @@ -376,6 +452,33 @@
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] {
efectn marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand Down Expand Up @@ -435,6 +538,14 @@
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 {
Expand Down Expand Up @@ -617,8 +728,18 @@
*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) {

Check failure on line 734 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'value' seems to be unused, consider removing or renaming it as _ (revive)
keys = append(keys, utils.UnsafeString(key))

Check failure on line 735 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

append to slice `keys` with non-zero initialized length (makezero)
})

return slices.Compact(keys)
}
efectn marked this conversation as resolved.
Show resolved Hide resolved

// AddParams receive a map and add each value to param.
func (p *QueryParam) AddParams(r map[string][]string) {

Check failure on line 742 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

receiver-naming: receiver name p should be consistent with previous receiver name f for QueryParam (revive)

Check failure on line 742 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

ST1016: methods on the same type should have the same receiver name (seen 1x "f", 3x "p") (stylecheck)
for k, v := range r {
for _, vv := range v {
p.Add(k, vv)
Expand All @@ -627,7 +748,7 @@
}

// SetParams will override all params.
func (p *QueryParam) SetParams(r map[string]string) {

Check failure on line 751 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

receiver-naming: receiver name p should be consistent with previous receiver name f for QueryParam (revive)
for k, v := range r {
p.Set(k, v)
}
Expand All @@ -635,7 +756,7 @@

// SetParamsWithStruct will override all params with struct or pointer of struct.
// Now nested structs are not currently supported.
func (p *QueryParam) SetParamsWithStruct(v any) {

Check failure on line 759 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

receiver-naming: receiver name p should be consistent with previous receiver name f for QueryParam (revive)
SetValWithStruct(p, "param", v)
}

Expand Down Expand Up @@ -747,6 +868,16 @@
*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) {

Check failure on line 874 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'value' seems to be unused, consider removing or renaming it as _ (revive)
keys = append(keys, utils.UnsafeString(key))

Check failure on line 875 in client/request.go

View workflow job for this annotation

GitHub Actions / lint

append to slice `keys` with non-zero initialized length (makezero)
})

return slices.Compact(keys)
}
efectn marked this conversation as resolved.
Show resolved Hide resolved

// AddData method is a wrapper of Args's Add method.
func (f *FormData) AddData(key, val string) {
f.Add(key, val)
Expand Down
Loading
Loading