Skip to content

Commit 829d6cf

Browse files
committed
🐛 bug: add square bracket notation support to BindMultipart
1 parent 89a0cd3 commit 829d6cf

File tree

2 files changed

+128
-2
lines changed

2 files changed

+128
-2
lines changed

bind_test.go

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10+
"mime/multipart"
1011
"net/http/httptest"
1112
"reflect"
1213
"testing"
@@ -988,6 +989,48 @@ func Test_Bind_Body(t *testing.T) {
988989
Data []Demo `query:"data"`
989990
}
990991

992+
t.Run("MultipartCollectionQueryDotNotation", func(t *testing.T) {
993+
c := app.AcquireCtx(&fasthttp.RequestCtx{})
994+
c.Request().Reset()
995+
996+
buf := &bytes.Buffer{}
997+
writer := multipart.NewWriter(buf)
998+
writer.WriteField("data.0.name", "john")
999+
writer.WriteField("data.1.name", "doe")
1000+
writer.Close()
1001+
1002+
c.Request().Header.SetContentType(writer.FormDataContentType())
1003+
c.Request().SetBody(buf.Bytes())
1004+
c.Request().Header.SetContentLength(len(c.Body()))
1005+
1006+
cq := new(CollectionQuery)
1007+
require.NoError(t, c.Bind().Body(cq))
1008+
require.Len(t, cq.Data, 2)
1009+
require.Equal(t, "john", cq.Data[0].Name)
1010+
require.Equal(t, "doe", cq.Data[1].Name)
1011+
})
1012+
1013+
t.Run("MultipartCollectionQuerySquareBrackets", func(t *testing.T) {
1014+
c := app.AcquireCtx(&fasthttp.RequestCtx{})
1015+
c.Request().Reset()
1016+
1017+
buf := &bytes.Buffer{}
1018+
writer := multipart.NewWriter(buf)
1019+
writer.WriteField("data[0][name]", "john")
1020+
writer.WriteField("data[1][name]", "doe")
1021+
writer.Close()
1022+
1023+
c.Request().Header.SetContentType(writer.FormDataContentType())
1024+
c.Request().SetBody(buf.Bytes())
1025+
c.Request().Header.SetContentLength(len(c.Body()))
1026+
1027+
cq := new(CollectionQuery)
1028+
require.NoError(t, c.Bind().Body(cq))
1029+
require.Len(t, cq.Data, 2)
1030+
require.Equal(t, "john", cq.Data[0].Name)
1031+
require.Equal(t, "doe", cq.Data[1].Name)
1032+
})
1033+
9911034
t.Run("CollectionQuerySquareBrackets", func(t *testing.T) {
9921035
c := app.AcquireCtx(&fasthttp.RequestCtx{})
9931036
c.Request().Reset()
@@ -1180,13 +1223,69 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) {
11801223
app := New()
11811224
c := app.AcquireCtx(&fasthttp.RequestCtx{})
11821225

1226+
type Person struct {
1227+
Name string `form:"name"`
1228+
Age int `form:"age"`
1229+
}
1230+
11831231
type Demo struct {
1232+
Name string `form:"name"`
1233+
Persons []Person `form:"persons"`
1234+
}
1235+
1236+
buf := &bytes.Buffer{}
1237+
writer := multipart.NewWriter(buf)
1238+
writer.WriteField("name", "john")
1239+
1240+
writer.Close()
1241+
body := buf.Bytes()
1242+
1243+
c.Request().SetBody(body)
1244+
c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary())
1245+
c.Request().Header.SetContentLength(len(body))
1246+
d := new(Demo)
1247+
1248+
b.ReportAllocs()
1249+
b.ResetTimer()
1250+
1251+
for n := 0; n < b.N; n++ {
1252+
err = c.Bind().Body(d)
1253+
}
1254+
1255+
require.NoError(b, err)
1256+
require.Equal(b, "john", d.Name)
1257+
}
1258+
1259+
// go test -v -run=^$ -bench=Benchmark_Bind_Body_MultipartForm_Nested -benchmem -count=4
1260+
func Benchmark_Bind_Body_MultipartForm_Nested(b *testing.B) {
1261+
var err error
1262+
1263+
app := New()
1264+
c := app.AcquireCtx(&fasthttp.RequestCtx{})
1265+
1266+
type Person struct {
11841267
Name string `form:"name"`
1268+
Age int `form:"age"`
1269+
}
1270+
1271+
type Demo struct {
1272+
Name string `form:"name"`
1273+
Persons []Person `form:"persons"`
11851274
}
11861275

1187-
body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")
1276+
buf := &bytes.Buffer{}
1277+
writer := multipart.NewWriter(buf)
1278+
writer.WriteField("name", "john")
1279+
writer.WriteField("persons.0.name", "john")
1280+
writer.WriteField("persons[0][age]", "10")
1281+
writer.WriteField("persons[1][name]", "doe")
1282+
writer.WriteField("persons.1.age", "20")
1283+
1284+
writer.Close()
1285+
body := buf.Bytes()
1286+
11881287
c.Request().SetBody(body)
1189-
c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary="b"`)
1288+
c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary=` + writer.Boundary())
11901289
c.Request().Header.SetContentLength(len(body))
11911290
d := new(Demo)
11921291

@@ -1196,8 +1295,13 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) {
11961295
for n := 0; n < b.N; n++ {
11971296
err = c.Bind().Body(d)
11981297
}
1298+
11991299
require.NoError(b, err)
12001300
require.Equal(b, "john", d.Name)
1301+
require.Equal(b, "john", d.Persons[0].Name)
1302+
require.Equal(b, 10, d.Persons[0].Age)
1303+
require.Equal(b, "doe", d.Persons[1].Name)
1304+
require.Equal(b, 20, d.Persons[1].Age)
12011305
}
12021306

12031307
// go test -v -run=^$ -bench=Benchmark_Bind_Body_Form_Map -benchmem -count=4

binder/form.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,27 @@ func (b *formBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error
5757
return err
5858
}
5959

60+
for key, values := range data.Value {
61+
if strings.Contains(key, "[") {
62+
k, err := parseParamSquareBrackets(key)
63+
if err != nil {
64+
return err
65+
}
66+
data.Value[k] = values
67+
delete(data.Value, key) // Remove bracket notation and use dot instead
68+
}
69+
70+
for _, v := range values {
71+
if strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, key) {
72+
delete(data.Value, key)
73+
74+
values := strings.Split(v, ",")
75+
for i := 0; i < len(values); i++ {
76+
data.Value[key] = append(data.Value[key], values[i])
77+
}
78+
}
79+
}
80+
}
81+
6082
return parse(b.Name(), out, data.Value)
6183
}

0 commit comments

Comments
 (0)