Skip to content

Commit

Permalink
🐛 bug: add square bracket notation support to BindMultipart
Browse files Browse the repository at this point in the history
  • Loading branch information
efectn committed Dec 6, 2024
1 parent 89a0cd3 commit 829d6cf
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 2 deletions.
108 changes: 106 additions & 2 deletions bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"fmt"
"mime/multipart"
"net/http/httptest"
"reflect"
"testing"
Expand Down Expand Up @@ -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")

Check failure on line 998 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.WriteField` is not checked (errcheck)
writer.WriteField("data.1.name", "doe")

Check failure on line 999 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.WriteField` is not checked (errcheck)
writer.Close()

Check failure on line 1000 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.Close` is not checked (errcheck)

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")

Check failure on line 1019 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.WriteField` is not checked (errcheck)
writer.WriteField("data[1][name]", "doe")

Check failure on line 1020 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.WriteField` is not checked (errcheck)
writer.Close()

Check failure on line 1021 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.Close` is not checked (errcheck)

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()
Expand Down Expand Up @@ -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")

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

unhandled-error: Unhandled error in call to function mime/multipart.Writer.WriteField (revive)

Check failure on line 1238 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.WriteField` is not checked (errcheck)

writer.Close()

Check failure on line 1240 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

unhandled-error: Unhandled error in call to function mime/multipart.Writer.Close (revive)

Check failure on line 1240 in bind_test.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `writer.Close` is not checked (errcheck)
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)

Expand All @@ -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
Expand Down
22 changes: 22 additions & 0 deletions binder/form.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Check warning on line 67 in binder/form.go

View check run for this annotation

Codecov / codecov/patch

binder/form.go#L60-L67

Added lines #L60 - L67 were not covered by tests
}

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])
}

Check warning on line 77 in binder/form.go

View check run for this annotation

Codecov / codecov/patch

binder/form.go#L70-L77

Added lines #L70 - L77 were not covered by tests
}
}
}

return parse(b.Name(), out, data.Value)
}

0 comments on commit 829d6cf

Please sign in to comment.