Skip to content

Commit a5f720a

Browse files
committed
Feature Request: Support Square Bracket Notation in Multipart Form Data #3224
1 parent 47be681 commit a5f720a

File tree

4 files changed

+94
-46
lines changed

4 files changed

+94
-46
lines changed

ctx.go

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -406,28 +406,30 @@ func (c *Ctx) BodyParser(out interface{}) error {
406406
k := c.app.getString(key)
407407
v := c.app.getString(val)
408408

409-
if strings.Contains(k, "[") {
410-
k, err = parseParamSquareBrackets(k)
411-
}
412-
413-
if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, bodyTag) {
414-
values := strings.Split(v, ",")
415-
for i := 0; i < len(values); i++ {
416-
data[k] = append(data[k], values[i])
417-
}
418-
} else {
419-
data[k] = append(data[k], v)
420-
}
409+
err = formatParserData(out, data, bodyTag, k, v, c.app.config.EnableSplittingOnParsers, true)
421410
})
422411

412+
if err != nil {
413+
return err
414+
}
415+
423416
return c.parseToStruct(bodyTag, out, data)
424417
}
425418
if strings.HasPrefix(ctype, MIMEMultipartForm) {
426-
data, err := c.fasthttp.MultipartForm()
419+
multipartForm, err := c.fasthttp.MultipartForm()
427420
if err != nil {
428421
return err
429422
}
430-
return c.parseToStruct(bodyTag, out, data.Value)
423+
424+
data := make(map[string][]string)
425+
for key, values := range multipartForm.Value {
426+
err = formatParserData(out, data, bodyTag, key, values, c.app.config.EnableSplittingOnParsers, true)
427+
if err != nil {
428+
return err
429+
}
430+
}
431+
432+
return c.parseToStruct(bodyTag, out, data)
431433
}
432434
if strings.HasPrefix(ctype, MIMETextXML) || strings.HasPrefix(ctype, MIMEApplicationXML) {
433435
if err := xml.Unmarshal(c.Body(), out); err != nil {
@@ -531,18 +533,7 @@ func (c *Ctx) CookieParser(out interface{}) error {
531533
k := c.app.getString(key)
532534
v := c.app.getString(val)
533535

534-
if strings.Contains(k, "[") {
535-
k, err = parseParamSquareBrackets(k)
536-
}
537-
538-
if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, cookieTag) {
539-
values := strings.Split(v, ",")
540-
for i := 0; i < len(values); i++ {
541-
data[k] = append(data[k], values[i])
542-
}
543-
} else {
544-
data[k] = append(data[k], v)
545-
}
536+
err = formatParserData(out, data, cookieTag, k, v, c.app.config.EnableSplittingOnParsers, true)
546537
})
547538
if err != nil {
548539
return err
@@ -1283,18 +1274,7 @@ func (c *Ctx) QueryParser(out interface{}) error {
12831274
k := c.app.getString(key)
12841275
v := c.app.getString(val)
12851276

1286-
if strings.Contains(k, "[") {
1287-
k, err = parseParamSquareBrackets(k)
1288-
}
1289-
1290-
if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, queryTag) {
1291-
values := strings.Split(v, ",")
1292-
for i := 0; i < len(values); i++ {
1293-
data[k] = append(data[k], values[i])
1294-
}
1295-
} else {
1296-
data[k] = append(data[k], v)
1297-
}
1277+
err = formatParserData(out, data, queryTag, k, v, c.app.config.EnableSplittingOnParsers, true)
12981278
})
12991279

13001280
if err != nil {
@@ -1342,6 +1322,40 @@ func parseParamSquareBrackets(k string) (string, error) {
13421322
return bb.String(), nil
13431323
}
13441324

1325+
func formatParserData(out interface{}, data map[string][]string, aliasTag, key string, value interface{}, enableSplitting, supportBracketNotation bool) error { //nolint:revive // it's okay
1326+
var err error
1327+
if supportBracketNotation && strings.Contains(key, "[") {
1328+
key, err = parseParamSquareBrackets(key)
1329+
if err != nil {
1330+
return err
1331+
}
1332+
}
1333+
1334+
switch v := value.(type) {
1335+
case string:
1336+
assignBindData(out, data, aliasTag, key, v, enableSplitting)
1337+
case []string:
1338+
for _, val := range v {
1339+
assignBindData(out, data, aliasTag, key, val, enableSplitting)
1340+
}
1341+
default:
1342+
return fmt.Errorf("unsupported value type: %T", value)
1343+
}
1344+
1345+
return err
1346+
}
1347+
1348+
func assignBindData(out interface{}, data map[string][]string, aliasTag, key, value string, enableSplitting bool) { //nolint:revive // it's okay
1349+
if enableSplitting && strings.Contains(value, ",") && equalFieldType(out, reflect.Slice, key, aliasTag) {
1350+
values := strings.Split(value, ",")
1351+
for i := 0; i < len(values); i++ {
1352+
data[key] = append(data[key], values[i])
1353+
}
1354+
} else {
1355+
data[key] = append(data[key], value)
1356+
}
1357+
}
1358+
13451359
// ReqHeaderParser binds the request header strings to a struct.
13461360
func (c *Ctx) ReqHeaderParser(out interface{}) error {
13471361
data := make(map[string][]string)

ctx_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,48 @@ func Test_Ctx_BodyParser(t *testing.T) {
610610
utils.AssertEqual(t, 2, len(cq.Data))
611611
utils.AssertEqual(t, "john", cq.Data[0].Name)
612612
utils.AssertEqual(t, "doe", cq.Data[1].Name)
613+
614+
t.Run("MultipartCollectionQueryDotNotation", func(t *testing.T) {
615+
c := app.AcquireCtx(&fasthttp.RequestCtx{})
616+
c.Request().Reset()
617+
618+
buf := &bytes.Buffer{}
619+
writer := multipart.NewWriter(buf)
620+
utils.AssertEqual(t, nil, writer.WriteField("data.0.name", "john"))
621+
utils.AssertEqual(t, nil, writer.WriteField("data.1.name", "doe"))
622+
utils.AssertEqual(t, nil, writer.Close())
623+
624+
c.Request().Header.SetContentType(writer.FormDataContentType())
625+
c.Request().SetBody(buf.Bytes())
626+
c.Request().Header.SetContentLength(len(c.Body()))
627+
628+
cq := new(CollectionQuery)
629+
utils.AssertEqual(t, nil, c.BodyParser(cq))
630+
utils.AssertEqual(t, len(cq.Data), 2)
631+
utils.AssertEqual(t, "john", cq.Data[0].Name)
632+
utils.AssertEqual(t, "doe", cq.Data[1].Name)
633+
})
634+
635+
t.Run("MultipartCollectionQuerySquareBrackets", func(t *testing.T) {
636+
c := app.AcquireCtx(&fasthttp.RequestCtx{})
637+
c.Request().Reset()
638+
639+
buf := &bytes.Buffer{}
640+
writer := multipart.NewWriter(buf)
641+
utils.AssertEqual(t, nil, writer.WriteField("data[0][name]", "john"))
642+
utils.AssertEqual(t, nil, writer.WriteField("data[1][name]", "doe"))
643+
utils.AssertEqual(t, nil, writer.Close())
644+
645+
c.Request().Header.SetContentType(writer.FormDataContentType())
646+
c.Request().SetBody(buf.Bytes())
647+
c.Request().Header.SetContentLength(len(c.Body()))
648+
649+
cq := new(CollectionQuery)
650+
utils.AssertEqual(t, nil, c.BodyParser(cq))
651+
utils.AssertEqual(t, len(cq.Data), 2)
652+
utils.AssertEqual(t, "john", cq.Data[0].Name)
653+
utils.AssertEqual(t, "doe", cq.Data[1].Name)
654+
})
613655
}
614656

615657
func Test_Ctx_ParamParser(t *testing.T) {

go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,4 @@ require (
1919
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
2020
github.com/rivo/uniseg v0.2.0 // indirect
2121
github.com/valyala/tcplisten v1.0.0 // indirect
22-
golang.org/x/mod v0.18.0 // indirect
23-
golang.org/x/tools v0.22.0 // indirect
2422
)

go.sum

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1Gsh
1515
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
1616
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
1717
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
18-
github.com/tinylib/msgp v1.1.3 h1:3giwAkmtaEDLSV0MdO1lDLuPgklgPzmk8H9+So2BVfA=
19-
github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
2018
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
2119
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
2220
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -25,11 +23,7 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S
2523
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
2624
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
2725
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
28-
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
29-
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
3026
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3127
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3228
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
3329
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
34-
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
35-
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=

0 commit comments

Comments
 (0)