From 829d6cf2e2a97c9b1bd575aba8f6bc7d6c0c9e77 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 6 Dec 2024 15:32:23 +0300 Subject: [PATCH 01/10] :bug: bug: add square bracket notation support to BindMultipart --- bind_test.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++++- binder/form.go | 22 ++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/bind_test.go b/bind_test.go index 55d2dd75e9..55f538a397 100644 --- a/bind_test.go +++ b/bind_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "mime/multipart" "net/http/httptest" "reflect" "testing" @@ -988,6 +989,48 @@ func Test_Bind_Body(t *testing.T) { Data []Demo `query:"data"` } + t.Run("MultipartCollectionQueryDotNotation", func(t *testing.T) { + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + c.Request().Reset() + + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + writer.WriteField("data.0.name", "john") + writer.WriteField("data.1.name", "doe") + writer.Close() + + c.Request().Header.SetContentType(writer.FormDataContentType()) + c.Request().SetBody(buf.Bytes()) + c.Request().Header.SetContentLength(len(c.Body())) + + cq := new(CollectionQuery) + require.NoError(t, c.Bind().Body(cq)) + require.Len(t, cq.Data, 2) + require.Equal(t, "john", cq.Data[0].Name) + require.Equal(t, "doe", cq.Data[1].Name) + }) + + t.Run("MultipartCollectionQuerySquareBrackets", func(t *testing.T) { + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + c.Request().Reset() + + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + writer.WriteField("data[0][name]", "john") + writer.WriteField("data[1][name]", "doe") + writer.Close() + + c.Request().Header.SetContentType(writer.FormDataContentType()) + c.Request().SetBody(buf.Bytes()) + c.Request().Header.SetContentLength(len(c.Body())) + + cq := new(CollectionQuery) + require.NoError(t, c.Bind().Body(cq)) + require.Len(t, cq.Data, 2) + require.Equal(t, "john", cq.Data[0].Name) + require.Equal(t, "doe", cq.Data[1].Name) + }) + t.Run("CollectionQuerySquareBrackets", func(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Reset() @@ -1180,13 +1223,69 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) + type Person struct { + Name string `form:"name"` + Age int `form:"age"` + } + type Demo struct { + Name string `form:"name"` + Persons []Person `form:"persons"` + } + + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + writer.WriteField("name", "john") + + writer.Close() + body := buf.Bytes() + + c.Request().SetBody(body) + c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary()) + c.Request().Header.SetContentLength(len(body)) + d := new(Demo) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + err = c.Bind().Body(d) + } + + require.NoError(b, err) + require.Equal(b, "john", d.Name) +} + +// go test -v -run=^$ -bench=Benchmark_Bind_Body_MultipartForm_Nested -benchmem -count=4 +func Benchmark_Bind_Body_MultipartForm_Nested(b *testing.B) { + var err error + + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + type Person struct { Name string `form:"name"` + Age int `form:"age"` + } + + type Demo struct { + Name string `form:"name"` + Persons []Person `form:"persons"` } - body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--") + buf := &bytes.Buffer{} + writer := multipart.NewWriter(buf) + writer.WriteField("name", "john") + writer.WriteField("persons.0.name", "john") + writer.WriteField("persons[0][age]", "10") + writer.WriteField("persons[1][name]", "doe") + writer.WriteField("persons.1.age", "20") + + writer.Close() + body := buf.Bytes() + c.Request().SetBody(body) - c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary="b"`) + c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary()) c.Request().Header.SetContentLength(len(body)) d := new(Demo) @@ -1196,8 +1295,13 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) { for n := 0; n < b.N; n++ { err = c.Bind().Body(d) } + require.NoError(b, err) require.Equal(b, "john", d.Name) + require.Equal(b, "john", d.Persons[0].Name) + require.Equal(b, 10, d.Persons[0].Age) + require.Equal(b, "doe", d.Persons[1].Name) + require.Equal(b, 20, d.Persons[1].Age) } // go test -v -run=^$ -bench=Benchmark_Bind_Body_Form_Map -benchmem -count=4 diff --git a/binder/form.go b/binder/form.go index e0f1acd302..b8974da5ea 100644 --- a/binder/form.go +++ b/binder/form.go @@ -57,5 +57,27 @@ func (b *formBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error return err } + for key, values := range data.Value { + if strings.Contains(key, "[") { + k, err := parseParamSquareBrackets(key) + if err != nil { + return err + } + data.Value[k] = values + delete(data.Value, key) // Remove bracket notation and use dot instead + } + + for _, v := range values { + if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, key) { + delete(data.Value, key) + + values := strings.Split(v, ",") + for i := 0; i < len(values); i++ { + data.Value[key] = append(data.Value[key], values[i]) + } + } + } + } + return parse(b.Name(), out, data.Value) } From 3c3fa8c2e2d4c61f85a9917145db4904fc1cd233 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:35:07 -0500 Subject: [PATCH 02/10] Fix golangci-lint issues --- bind_test.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/bind_test.go b/bind_test.go index 55f538a397..bd2661a32a 100644 --- a/bind_test.go +++ b/bind_test.go @@ -995,9 +995,9 @@ func Test_Bind_Body(t *testing.T) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - writer.WriteField("data.0.name", "john") - writer.WriteField("data.1.name", "doe") - writer.Close() + require.NoError(t, writer.WriteField("data.0.name", "john")) + require.NoError(t, writer.WriteField("data.1.name", "doe")) + require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) @@ -1016,9 +1016,9 @@ func Test_Bind_Body(t *testing.T) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - writer.WriteField("data[0][name]", "john") - writer.WriteField("data[1][name]", "doe") - writer.Close() + require.NoError(t, writer.WriteField("data[0][name]", "john")) + require.NoError(t, writer.WriteField("data[1][name]", "doe")) + require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) @@ -1235,9 +1235,8 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - writer.WriteField("name", "john") - - writer.Close() + require.NoError(t, writer.WriteField("name", "john")) + require.NoError(t, writer.Close()) body := buf.Bytes() c.Request().SetBody(body) @@ -1275,13 +1274,12 @@ func Benchmark_Bind_Body_MultipartForm_Nested(b *testing.B) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - writer.WriteField("name", "john") - writer.WriteField("persons.0.name", "john") - writer.WriteField("persons[0][age]", "10") - writer.WriteField("persons[1][name]", "doe") - writer.WriteField("persons.1.age", "20") - - writer.Close() + require.NoError(t, writer.WriteField("name", "john")) + require.NoError(t, writer.WriteField("persons.0.name", "john")) + require.NoError(t, writer.WriteField("persons[0][age]", "10")) + require.NoError(t, writer.WriteField("persons[1][name]", "doe")) + require.NoError(t, writer.WriteField("persons.1.age", "20")) + require.NoError(t, writer.Close()) body := buf.Bytes() c.Request().SetBody(body) From 2692923111ff30c4151b90a64d678e14235240e1 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:36:56 -0500 Subject: [PATCH 03/10] Fixing undef variable --- bind_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/bind_test.go b/bind_test.go index bd2661a32a..f589a5da9d 100644 --- a/bind_test.go +++ b/bind_test.go @@ -995,9 +995,9 @@ func Test_Bind_Body(t *testing.T) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - require.NoError(t, writer.WriteField("data.0.name", "john")) - require.NoError(t, writer.WriteField("data.1.name", "doe")) - require.NoError(t, writer.Close()) + require.NoError(b, writer.WriteField("data.0.name", "john")) + require.NoError(b, writer.WriteField("data.1.name", "doe")) + require.NoError(b, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) @@ -1016,9 +1016,9 @@ func Test_Bind_Body(t *testing.T) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - require.NoError(t, writer.WriteField("data[0][name]", "john")) - require.NoError(t, writer.WriteField("data[1][name]", "doe")) - require.NoError(t, writer.Close()) + require.NoError(b, writer.WriteField("data[0][name]", "john")) + require.NoError(b, writer.WriteField("data[1][name]", "doe")) + require.NoError(b, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) @@ -1235,8 +1235,8 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - require.NoError(t, writer.WriteField("name", "john")) - require.NoError(t, writer.Close()) + require.NoError(b, writer.WriteField("name", "john")) + require.NoError(b, writer.Close()) body := buf.Bytes() c.Request().SetBody(body) @@ -1274,12 +1274,12 @@ func Benchmark_Bind_Body_MultipartForm_Nested(b *testing.B) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - require.NoError(t, writer.WriteField("name", "john")) - require.NoError(t, writer.WriteField("persons.0.name", "john")) - require.NoError(t, writer.WriteField("persons[0][age]", "10")) - require.NoError(t, writer.WriteField("persons[1][name]", "doe")) - require.NoError(t, writer.WriteField("persons.1.age", "20")) - require.NoError(t, writer.Close()) + require.NoError(b, writer.WriteField("name", "john")) + require.NoError(b, writer.WriteField("persons.0.name", "john")) + require.NoError(b, writer.WriteField("persons[0][age]", "10")) + require.NoError(b, writer.WriteField("persons[1][name]", "doe")) + require.NoError(b, writer.WriteField("persons.1.age", "20")) + require.NoError(b, writer.Close()) body := buf.Bytes() c.Request().SetBody(body) From df12e644bcef95d8be102d603ab4a4c9d8f8796c Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:39:24 -0500 Subject: [PATCH 04/10] Fix more lint issues --- bind_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bind_test.go b/bind_test.go index f589a5da9d..666c21744f 100644 --- a/bind_test.go +++ b/bind_test.go @@ -995,9 +995,9 @@ func Test_Bind_Body(t *testing.T) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - require.NoError(b, writer.WriteField("data.0.name", "john")) - require.NoError(b, writer.WriteField("data.1.name", "doe")) - require.NoError(b, writer.Close()) + require.NoError(t, writer.WriteField("data.0.name", "john")) + require.NoError(t, writer.WriteField("data.1.name", "doe")) + require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) @@ -1016,9 +1016,9 @@ func Test_Bind_Body(t *testing.T) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) - require.NoError(b, writer.WriteField("data[0][name]", "john")) - require.NoError(b, writer.WriteField("data[1][name]", "doe")) - require.NoError(b, writer.Close()) + require.NoError(t, writer.WriteField("data[0][name]", "john")) + require.NoError(t, writer.WriteField("data[1][name]", "doe")) + require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) c.Request().SetBody(buf.Bytes()) From 18cb91c480d7b45823134fd1ac6527962b1d7b17 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 13 Dec 2024 11:30:17 +0300 Subject: [PATCH 05/10] test --- bind_test.go | 14 ++++++-------- binder/form.go | 2 ++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bind_test.go b/bind_test.go index 666c21744f..4c6e56808b 100644 --- a/bind_test.go +++ b/bind_test.go @@ -879,7 +879,8 @@ func Test_Bind_Body(t *testing.T) { reqBody := []byte(`{"name":"john"}`) type Demo struct { - Name string `json:"name" xml:"name" form:"name" query:"name"` + Name string `json:"name" xml:"name" form:"name" query:"name"` + Names []string `json:"names" xml:"names" form:"names" query:"names"` } // Helper function to test compressed bodies @@ -1018,6 +1019,7 @@ func Test_Bind_Body(t *testing.T) { writer := multipart.NewWriter(buf) require.NoError(t, writer.WriteField("data[0][name]", "john")) require.NoError(t, writer.WriteField("data[1][name]", "doe")) + require.NoError(t, writer.WriteField("data[1][names]", "john,doe")) require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) @@ -1029,6 +1031,8 @@ func Test_Bind_Body(t *testing.T) { require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, "doe", cq.Data[1].Name) + require.Contains(t, cq.Data[1].Names, "john") + require.Contains(t, cq.Data[1].Names, "doe") }) t.Run("CollectionQuerySquareBrackets", func(t *testing.T) { @@ -1223,14 +1227,8 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - type Person struct { - Name string `form:"name"` - Age int `form:"age"` - } - type Demo struct { - Name string `form:"name"` - Persons []Person `form:"persons"` + Name string `form:"name"` } buf := &bytes.Buffer{} diff --git a/binder/form.go b/binder/form.go index b8974da5ea..6676efe072 100644 --- a/binder/form.go +++ b/binder/form.go @@ -65,6 +65,8 @@ func (b *formBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error } data.Value[k] = values delete(data.Value, key) // Remove bracket notation and use dot instead + + key = k // We have to update key in case bracket notation and slice type are used at the same time } for _, v := range values { From fa9acf8f6c5770bc9f92773c2dde15ad0e598f91 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 27 Dec 2024 15:00:13 +0300 Subject: [PATCH 06/10] update1 --- bind_test.go | 3 --- binder/form.go | 14 +++++--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/bind_test.go b/bind_test.go index 4c6e56808b..370b8cd889 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1019,7 +1019,6 @@ func Test_Bind_Body(t *testing.T) { writer := multipart.NewWriter(buf) require.NoError(t, writer.WriteField("data[0][name]", "john")) require.NoError(t, writer.WriteField("data[1][name]", "doe")) - require.NoError(t, writer.WriteField("data[1][names]", "john,doe")) require.NoError(t, writer.Close()) c.Request().Header.SetContentType(writer.FormDataContentType()) @@ -1031,8 +1030,6 @@ func Test_Bind_Body(t *testing.T) { require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, "doe", cq.Data[1].Name) - require.Contains(t, cq.Data[1].Names, "john") - require.Contains(t, cq.Data[1].Names, "doe") }) t.Run("CollectionQuerySquareBrackets", func(t *testing.T) { diff --git a/binder/form.go b/binder/form.go index 6676efe072..99fe56c3b6 100644 --- a/binder/form.go +++ b/binder/form.go @@ -57,29 +57,25 @@ func (b *formBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error return err } + temp := make(map[string][]string) for key, values := range data.Value { if strings.Contains(key, "[") { k, err := parseParamSquareBrackets(key) if err != nil { return err } - data.Value[k] = values - delete(data.Value, key) // Remove bracket notation and use dot instead key = k // We have to update key in case bracket notation and slice type are used at the same time } for _, v := range values { if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, key) { - delete(data.Value, key) - - values := strings.Split(v, ",") - for i := 0; i < len(values); i++ { - data.Value[key] = append(data.Value[key], values[i]) - } + temp[key] = strings.Split(v, ",") + } else { + temp[key] = append(temp[key], v) } } } - return parse(b.Name(), out, data.Value) + return parse(b.Name(), out, temp) } From ddeba8706d0e76741a34a34688890021e3c4270a Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 27 Dec 2024 15:47:43 +0300 Subject: [PATCH 07/10] improve coverage --- binder/form_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/binder/form_test.go b/binder/form_test.go index c3c52c73fd..ff4ca2ac83 100644 --- a/binder/form_test.go +++ b/binder/form_test.go @@ -93,9 +93,14 @@ func Test_FormBinder_BindMultipart(t *testing.T) { } require.Equal(t, "form", b.Name()) + type Post struct { + Title string `form:"title"` + } + type User struct { Name string `form:"name"` Names []string `form:"names"` + Posts []Post `form:"posts"` Age int `form:"age"` } var user User @@ -106,9 +111,13 @@ func Test_FormBinder_BindMultipart(t *testing.T) { mw := multipart.NewWriter(buf) require.NoError(t, mw.WriteField("name", "john")) - require.NoError(t, mw.WriteField("names", "john")) + require.NoError(t, mw.WriteField("names", "john,eric")) require.NoError(t, mw.WriteField("names", "doe")) require.NoError(t, mw.WriteField("age", "42")) + require.NoError(t, mw.WriteField("posts[0][title]", "post1")) + require.NoError(t, mw.WriteField("posts[1][title]", "post2")) + require.NoError(t, mw.WriteField("posts[2][title]", "post3")) + require.NoError(t, mw.Close()) req.Header.SetContentType(mw.FormDataContentType()) @@ -125,6 +134,12 @@ func Test_FormBinder_BindMultipart(t *testing.T) { require.Equal(t, 42, user.Age) require.Contains(t, user.Names, "john") require.Contains(t, user.Names, "doe") + require.Contains(t, user.Names, "eric") + require.Len(t, user.Posts, 3) + require.Equal(t, "post1", user.Posts[0].Title) + require.Equal(t, "post2", user.Posts[1].Title) + require.Equal(t, "post3", user.Posts[2].Title) + } func Benchmark_FormBinder_BindMultipart(b *testing.B) { From 3b4633e474cf3d61956d03e0e82fedc51989cb1a Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 27 Dec 2024 16:01:48 +0300 Subject: [PATCH 08/10] fix linter --- binder/form_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/binder/form_test.go b/binder/form_test.go index ff4ca2ac83..55023cb30f 100644 --- a/binder/form_test.go +++ b/binder/form_test.go @@ -139,7 +139,6 @@ func Test_FormBinder_BindMultipart(t *testing.T) { require.Equal(t, "post1", user.Posts[0].Title) require.Equal(t, "post2", user.Posts[1].Title) require.Equal(t, "post3", user.Posts[2].Title) - } func Benchmark_FormBinder_BindMultipart(b *testing.B) { From 0b02b80659f56869a42634501ba80dcfdf65acad Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Mon, 30 Dec 2024 01:21:08 +0300 Subject: [PATCH 09/10] reduce code duplication --- binder/cookie.go | 13 +------------ binder/form.go | 14 +------------- binder/header.go | 22 ++++++++++------------ binder/mapping.go | 18 ++++++++++++++++++ binder/query.go | 17 +---------------- binder/resp_header.go | 23 +++++++++++------------ 6 files changed, 42 insertions(+), 65 deletions(-) diff --git a/binder/cookie.go b/binder/cookie.go index 230794f45a..5b9ccf1ed3 100644 --- a/binder/cookie.go +++ b/binder/cookie.go @@ -1,9 +1,6 @@ package binder import ( - "reflect" - "strings" - "github.com/gofiber/utils/v2" "github.com/valyala/fasthttp" ) @@ -30,15 +27,7 @@ func (b *CookieBinding) Bind(req *fasthttp.Request, out any) error { k := utils.UnsafeString(key) v := utils.UnsafeString(val) - - if b.EnableSplitting && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) { - values := strings.Split(v, ",") - for i := 0; i < len(values); i++ { - data[k] = append(data[k], values[i]) - } - } else { - data[k] = append(data[k], v) - } + err = formatBindData(out, data, k, v, b.EnableSplitting, false) }) if err != nil { diff --git a/binder/form.go b/binder/form.go index 91b0beec95..1e47cf5f96 100644 --- a/binder/form.go +++ b/binder/form.go @@ -37,19 +37,7 @@ func (b *FormBinding) Bind(req *fasthttp.Request, out any) error { k := utils.UnsafeString(key) v := utils.UnsafeString(val) - - if strings.Contains(k, "[") { - k, err = parseParamSquareBrackets(k) - } - - if b.EnableSplitting && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) { - values := strings.Split(v, ",") - for i := 0; i < len(values); i++ { - data[k] = append(data[k], values[i]) - } - } else { - data[k] = append(data[k], v) - } + err = formatBindData(out, data, k, v, b.EnableSplitting, true) }) if err != nil { diff --git a/binder/header.go b/binder/header.go index b04ce9add3..763be56795 100644 --- a/binder/header.go +++ b/binder/header.go @@ -1,9 +1,6 @@ package binder import ( - "reflect" - "strings" - "github.com/gofiber/utils/v2" "github.com/valyala/fasthttp" ) @@ -21,20 +18,21 @@ func (*HeaderBinding) Name() string { // Bind parses the request header and returns the result. func (b *HeaderBinding) Bind(req *fasthttp.Request, out any) error { data := make(map[string][]string) + var err error req.Header.VisitAll(func(key, val []byte) { + if err != nil { + return + } + k := utils.UnsafeString(key) v := utils.UnsafeString(val) - - if b.EnableSplitting && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) { - values := strings.Split(v, ",") - for i := 0; i < len(values); i++ { - data[k] = append(data[k], values[i]) - } - } else { - data[k] = append(data[k], v) - } + err = formatBindData(out, data, k, v, b.EnableSplitting, false) }) + if err != nil { + return err + } + return parse(b.Name(), out, data) } diff --git a/binder/mapping.go b/binder/mapping.go index d8b692f7e4..527f4741e9 100644 --- a/binder/mapping.go +++ b/binder/mapping.go @@ -249,3 +249,21 @@ func FilterFlags(content string) string { } return content } + +func formatBindData(out any, data map[string][]string, key, value string, enableSplitting, supportBracketNotation bool) error { //nolint:revive // it's okay + var err error + if supportBracketNotation && strings.Contains(key, "[") { + key, err = parseParamSquareBrackets(key) + } + + if enableSplitting && strings.Contains(value, ",") && equalFieldType(out, reflect.Slice, key) { + values := strings.Split(value, ",") + for i := 0; i < len(values); i++ { + data[key] = append(data[key], values[i]) + } + } else { + data[key] = append(data[key], value) + } + + return err +} diff --git a/binder/query.go b/binder/query.go index 9ee500ba63..d2ac309215 100644 --- a/binder/query.go +++ b/binder/query.go @@ -1,9 +1,6 @@ package binder import ( - "reflect" - "strings" - "github.com/gofiber/utils/v2" "github.com/valyala/fasthttp" ) @@ -30,19 +27,7 @@ func (b *QueryBinding) Bind(reqCtx *fasthttp.Request, out any) error { k := utils.UnsafeString(key) v := utils.UnsafeString(val) - - if strings.Contains(k, "[") { - k, err = parseParamSquareBrackets(k) - } - - if b.EnableSplitting && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) { - values := strings.Split(v, ",") - for i := 0; i < len(values); i++ { - data[k] = append(data[k], values[i]) - } - } else { - data[k] = append(data[k], v) - } + err = formatBindData(out, data, k, v, b.EnableSplitting, true) }) if err != nil { diff --git a/binder/resp_header.go b/binder/resp_header.go index fc84d01402..cb29e99d6f 100644 --- a/binder/resp_header.go +++ b/binder/resp_header.go @@ -1,9 +1,6 @@ package binder import ( - "reflect" - "strings" - "github.com/gofiber/utils/v2" "github.com/valyala/fasthttp" ) @@ -21,20 +18,22 @@ func (*RespHeaderBinding) Name() string { // Bind parses the response header and returns the result. func (b *RespHeaderBinding) Bind(resp *fasthttp.Response, out any) error { data := make(map[string][]string) + var err error + resp.Header.VisitAll(func(key, val []byte) { + if err != nil { + return + } + k := utils.UnsafeString(key) v := utils.UnsafeString(val) - - if b.EnableSplitting && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k) { - values := strings.Split(v, ",") - for i := 0; i < len(values); i++ { - data[k] = append(data[k], values[i]) - } - } else { - data[k] = append(data[k], v) - } + err = formatBindData(out, data, k, v, b.EnableSplitting, false) }) + if err != nil { + return err + } + return parse(b.Name(), out, data) } From 8c28b9a4eeb11898a737b9aab49a2cb1e2f08e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Tue, 31 Dec 2024 15:55:31 +0100 Subject: [PATCH 10/10] reduce code duplications in bindMultipart --- binder/form.go | 29 +++++++---------------------- binder/mapping.go | 25 +++++++++++++++++++++---- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/binder/form.go b/binder/form.go index 1e47cf5f96..a8f5b85270 100644 --- a/binder/form.go +++ b/binder/form.go @@ -1,9 +1,6 @@ package binder import ( - "reflect" - "strings" - "github.com/gofiber/utils/v2" "github.com/valyala/fasthttp" ) @@ -49,32 +46,20 @@ func (b *FormBinding) Bind(req *fasthttp.Request, out any) error { // bindMultipart parses the request body and returns the result. func (b *FormBinding) bindMultipart(req *fasthttp.Request, out any) error { - data, err := req.MultipartForm() + multipartForm, err := req.MultipartForm() if err != nil { return err } - temp := make(map[string][]string) - for key, values := range data.Value { - if strings.Contains(key, "[") { - k, err := parseParamSquareBrackets(key) - if err != nil { - return err - } - - key = k // We have to update key in case bracket notation and slice type are used at the same time - } - - for _, v := range values { - if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, key) { - temp[key] = strings.Split(v, ",") - } else { - temp[key] = append(temp[key], v) - } + data := make(map[string][]string) + for key, values := range multipartForm.Value { + err = formatBindData(out, data, key, values, b.EnableSplitting, true) + if err != nil { + return err } } - return parse(b.Name(), out, temp) + return parse(b.Name(), out, data) } // Reset resets the FormBinding binder. diff --git a/binder/mapping.go b/binder/mapping.go index 527f4741e9..29ba2a25b0 100644 --- a/binder/mapping.go +++ b/binder/mapping.go @@ -107,7 +107,6 @@ func parseToStruct(aliasTag string, out any, data map[string][]string) error { func parseToMap(ptr any, data map[string][]string) error { elem := reflect.TypeOf(ptr).Elem() - //nolint:exhaustive // it's not necessary to check all types switch elem.Kind() { case reflect.Slice: newMap, ok := ptr.(map[string][]string) @@ -132,6 +131,8 @@ func parseToMap(ptr any, data map[string][]string) error { newMap[k] = v[len(v)-1] } + default: + return nil // it's not necessary to check all types } return nil @@ -250,12 +251,30 @@ func FilterFlags(content string) string { return content } -func formatBindData(out any, data map[string][]string, key, value string, enableSplitting, supportBracketNotation bool) error { //nolint:revive // it's okay +func formatBindData[T any](out any, data map[string][]string, key string, value T, enableSplitting, supportBracketNotation bool) error { //nolint:revive // it's okay var err error if supportBracketNotation && strings.Contains(key, "[") { key, err = parseParamSquareBrackets(key) + if err != nil { + return err + } } + switch v := any(value).(type) { + case string: + assignBindData(out, data, key, v, enableSplitting) + case []string: + for _, val := range v { + assignBindData(out, data, key, val, enableSplitting) + } + default: + return fmt.Errorf("unsupported value type: %T", value) + } + + return err +} + +func assignBindData(out any, data map[string][]string, key, value string, enableSplitting bool) { //nolint:revive // it's okay if enableSplitting && strings.Contains(value, ",") && equalFieldType(out, reflect.Slice, key) { values := strings.Split(value, ",") for i := 0; i < len(values); i++ { @@ -264,6 +283,4 @@ func formatBindData(out any, data map[string][]string, key, value string, enable } else { data[key] = append(data[key], value) } - - return err }