Skip to content

Commit 0d50ce8

Browse files
vkdthinkerou
authored andcommitted
refactor(form_mapping.go): mapping ptr, struct and map (#1749)
* refactor(form_mapping.go): mapping ptr, struct and map * fix #1672 correct work with ptr - not create value if field is not set * avoid allocations on strings.Split() - change to strings.Index() * fix #610 tag value "-" is mean ignoring field * struct fields mapped like json.Unmarshal * map fields mapped like json.Unmarshal * fix after @thinkerou review
1 parent 893c6ca commit 0d50ce8

File tree

4 files changed

+314
-123
lines changed

4 files changed

+314
-123
lines changed

README.md

-18
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -1836,24 +1836,6 @@ $ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
1836
{"d":"world","x":{"FieldX":"hello"}}
1836
{"d":"world","x":{"FieldX":"hello"}}
1837
```
1837
```
1838

1838

1839-
**NOTE**: NOT support the follow style struct:
1840-
1841-
```go
1842-
type StructX struct {
1843-
X struct {} `form:"name_x"` // HERE have form
1844-
}
1845-
1846-
type StructY struct {
1847-
Y StructX `form:"name_y"` // HERE have form
1848-
}
1849-
1850-
type StructZ struct {
1851-
Z *StructZ `form:"name_z"` // HERE have form
1852-
}
1853-
```
1854-
1855-
In a word, only support nested custom struct which have no `form` now.
1856-
1857
### Try to bind body into different structs
1839
### Try to bind body into different structs
1858

1840

1859
The normal methods for binding request body consumes `c.Request.Body` and they
1841
The normal methods for binding request body consumes `c.Request.Body` and they

binding/binding_test.go

+115-19
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
12
"mime/multipart"
12
"mime/multipart"
13
"net/http"
13
"net/http"
14
"strconv"
14
"strconv"
15+
"strings"
15
"testing"
16
"testing"
16
"time"
17
"time"
17

18

@@ -57,7 +58,6 @@ type FooStructForTimeTypeFailLocation struct {
57
}
58
}
58

59

59
type FooStructForMapType struct {
60
type FooStructForMapType struct {
60-
// Unknown type: not support map
61
MapFoo map[string]interface{} `form:"map_foo"`
61
MapFoo map[string]interface{} `form:"map_foo"`
62
}
62
}
63

63

@@ -303,7 +303,7 @@ func TestBindingFormInvalidName2(t *testing.T) {
303
func TestBindingFormForType(t *testing.T) {
303
func TestBindingFormForType(t *testing.T) {
304
testFormBindingForType(t, "POST",
304
testFormBindingForType(t, "POST",
305
"/", "/",
305
"/", "/",
306-
"map_foo=", "bar2=1", "Map")
306+
"map_foo={\"bar\":123}", "map_foo=1", "Map")
307

307

308
testFormBindingForType(t, "POST",
308
testFormBindingForType(t, "POST",
309
"/", "/",
309
"/", "/",
@@ -508,20 +508,30 @@ func TestBindingYAMLFail(t *testing.T) {
508
`foo:\nbar`, `bar: foo`)
508
`foo:\nbar`, `bar: foo`)
509
}
509
}
510

510

511-
func createFormPostRequest() *http.Request {
511+
func createFormPostRequest(t *testing.T) *http.Request {
512-
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
512+
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
513+
assert.NoError(t, err)
513
req.Header.Set("Content-Type", MIMEPOSTForm)
514
req.Header.Set("Content-Type", MIMEPOSTForm)
514
return req
515
return req
515
}
516
}
516

517

517-
func createDefaultFormPostRequest() *http.Request {
518+
func createDefaultFormPostRequest(t *testing.T) *http.Request {
518-
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
519+
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
520+
assert.NoError(t, err)
519
req.Header.Set("Content-Type", MIMEPOSTForm)
521
req.Header.Set("Content-Type", MIMEPOSTForm)
520
return req
522
return req
521
}
523
}
522

524

523-
func createFormPostRequestFail() *http.Request {
525+
func createFormPostRequestForMap(t *testing.T) *http.Request {
524-
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar"))
526+
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}"))
527+
assert.NoError(t, err)
528+
req.Header.Set("Content-Type", MIMEPOSTForm)
529+
return req
530+
}
531+
532+
func createFormPostRequestForMapFail(t *testing.T) *http.Request {
533+
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello"))
534+
assert.NoError(t, err)
525
req.Header.Set("Content-Type", MIMEPOSTForm)
535
req.Header.Set("Content-Type", MIMEPOSTForm)
526
return req
536
return req
527
}
537
}
@@ -535,26 +545,42 @@ func createFormMultipartRequest(t *testing.T) *http.Request {
535
assert.NoError(t, mw.SetBoundary(boundary))
545
assert.NoError(t, mw.SetBoundary(boundary))
536
assert.NoError(t, mw.WriteField("foo", "bar"))
546
assert.NoError(t, mw.WriteField("foo", "bar"))
537
assert.NoError(t, mw.WriteField("bar", "foo"))
547
assert.NoError(t, mw.WriteField("bar", "foo"))
538-
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
548+
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
549+
assert.NoError(t, err)
539
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
550
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
540
return req
551
return req
541
}
552
}
542

553

543-
func createFormMultipartRequestFail(t *testing.T) *http.Request {
554+
func createFormMultipartRequestForMap(t *testing.T) *http.Request {
544
boundary := "--testboundary"
555
boundary := "--testboundary"
545
body := new(bytes.Buffer)
556
body := new(bytes.Buffer)
546
mw := multipart.NewWriter(body)
557
mw := multipart.NewWriter(body)
547
defer mw.Close()
558
defer mw.Close()
548

559

549
assert.NoError(t, mw.SetBoundary(boundary))
560
assert.NoError(t, mw.SetBoundary(boundary))
550-
assert.NoError(t, mw.WriteField("map_foo", "bar"))
561+
assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}"))
551-
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
562+
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
563+
assert.NoError(t, err)
564+
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
565+
return req
566+
}
567+
568+
func createFormMultipartRequestForMapFail(t *testing.T) *http.Request {
569+
boundary := "--testboundary"
570+
body := new(bytes.Buffer)
571+
mw := multipart.NewWriter(body)
572+
defer mw.Close()
573+
574+
assert.NoError(t, mw.SetBoundary(boundary))
575+
assert.NoError(t, mw.WriteField("map_foo", "3.14"))
576+
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
577+
assert.NoError(t, err)
552
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
578
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
553
return req
579
return req
554
}
580
}
555

581

556
func TestBindingFormPost(t *testing.T) {
582
func TestBindingFormPost(t *testing.T) {
557-
req := createFormPostRequest()
583+
req := createFormPostRequest(t)
558
var obj FooBarStruct
584
var obj FooBarStruct
559
assert.NoError(t, FormPost.Bind(req, &obj))
585
assert.NoError(t, FormPost.Bind(req, &obj))
560

586

@@ -564,16 +590,24 @@ func TestBindingFormPost(t *testing.T) {
564
}
590
}
565

591

566
func TestBindingDefaultValueFormPost(t *testing.T) {
592
func TestBindingDefaultValueFormPost(t *testing.T) {
567-
req := createDefaultFormPostRequest()
593+
req := createDefaultFormPostRequest(t)
568
var obj FooDefaultBarStruct
594
var obj FooDefaultBarStruct
569
assert.NoError(t, FormPost.Bind(req, &obj))
595
assert.NoError(t, FormPost.Bind(req, &obj))
570

596

571
assert.Equal(t, "bar", obj.Foo)
597
assert.Equal(t, "bar", obj.Foo)
572
assert.Equal(t, "hello", obj.Bar)
598
assert.Equal(t, "hello", obj.Bar)
573
}
599
}
574

600

575-
func TestBindingFormPostFail(t *testing.T) {
601+
func TestBindingFormPostForMap(t *testing.T) {
576-
req := createFormPostRequestFail()
602+
req := createFormPostRequestForMap(t)
603+
var obj FooStructForMapType
604+
err := FormPost.Bind(req, &obj)
605+
assert.NoError(t, err)
606+
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
607+
}
608+
609+
func TestBindingFormPostForMapFail(t *testing.T) {
610+
req := createFormPostRequestForMapFail(t)
577
var obj FooStructForMapType
611
var obj FooStructForMapType
578
err := FormPost.Bind(req, &obj)
612
err := FormPost.Bind(req, &obj)
579
assert.Error(t, err)
613
assert.Error(t, err)
@@ -589,8 +623,18 @@ func TestBindingFormMultipart(t *testing.T) {
589
assert.Equal(t, "foo", obj.Bar)
623
assert.Equal(t, "foo", obj.Bar)
590
}
624
}
591

625

592-
func TestBindingFormMultipartFail(t *testing.T) {
626+
func TestBindingFormMultipartForMap(t *testing.T) {
593-
req := createFormMultipartRequestFail(t)
627+
req := createFormMultipartRequestForMap(t)
628+
var obj FooStructForMapType
629+
err := FormMultipart.Bind(req, &obj)
630+
assert.NoError(t, err)
631+
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
632+
assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string))
633+
assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64))
634+
}
635+
636+
func TestBindingFormMultipartForMapFail(t *testing.T) {
637+
req := createFormMultipartRequestForMapFail(t)
594
var obj FooStructForMapType
638
var obj FooStructForMapType
595
err := FormMultipart.Bind(req, &obj)
639
err := FormMultipart.Bind(req, &obj)
596
assert.Error(t, err)
640
assert.Error(t, err)
@@ -773,6 +817,17 @@ func TestFormBindingFail(t *testing.T) {
773
assert.Error(t, err)
817
assert.Error(t, err)
774
}
818
}
775

819

820+
func TestFormBindingMultipartFail(t *testing.T) {
821+
obj := FooBarStruct{}
822+
req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar"))
823+
assert.NoError(t, err)
824+
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary")
825+
_, err = req.MultipartReader()
826+
assert.NoError(t, err)
827+
err = Form.Bind(req, &obj)
828+
assert.Error(t, err)
829+
}
830+
776
func TestFormPostBindingFail(t *testing.T) {
831
func TestFormPostBindingFail(t *testing.T) {
777
b := FormPost
832
b := FormPost
778
assert.Equal(t, "form-urlencoded", b.Name())
833
assert.Equal(t, "form-urlencoded", b.Name())
@@ -1109,7 +1164,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
1109
case "Map":
1164
case "Map":
1110
obj := FooStructForMapType{}
1165
obj := FooStructForMapType{}
1111
err := b.Bind(req, &obj)
1166
err := b.Bind(req, &obj)
1112-
assert.Error(t, err)
1167+
assert.NoError(t, err)
1168+
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
1113
case "SliceMap":
1169
case "SliceMap":
1114
obj := FooStructForSliceMapType{}
1170
obj := FooStructForSliceMapType{}
1115
err := b.Bind(req, &obj)
1171
err := b.Bind(req, &obj)
@@ -1317,3 +1373,43 @@ func TestCanSet(t *testing.T) {
1317
var c CanSetStruct
1373
var c CanSetStruct
1318
assert.Nil(t, mapForm(&c, nil))
1374
assert.Nil(t, mapForm(&c, nil))
1319
}
1375
}
1376+
1377+
func formPostRequest(path, body string) *http.Request {
1378+
req := requestWithBody("POST", path, body)
1379+
req.Header.Add("Content-Type", MIMEPOSTForm)
1380+
return req
1381+
}
1382+
1383+
func TestBindingSliceDefault(t *testing.T) {
1384+
var s struct {
1385+
Friends []string `form:"friends,default=mike"`
1386+
}
1387+
req := formPostRequest("", "")
1388+
err := Form.Bind(req, &s)
1389+
assert.NoError(t, err)
1390+
1391+
assert.Len(t, s.Friends, 1)
1392+
assert.Equal(t, "mike", s.Friends[0])
1393+
}
1394+
1395+
func TestBindingStructField(t *testing.T) {
1396+
var s struct {
1397+
Opts struct {
1398+
Port int
1399+
} `form:"opts"`
1400+
}
1401+
req := formPostRequest("", `opts={"Port": 8000}`)
1402+
err := Form.Bind(req, &s)
1403+
assert.NoError(t, err)
1404+
assert.Equal(t, 8000, s.Opts.Port)
1405+
}
1406+
1407+
func TestBindingUnknownTypeChan(t *testing.T) {
1408+
var s struct {
1409+
Stop chan bool `form:"stop"`
1410+
}
1411+
req := formPostRequest("", "stop=true")
1412+
err := Form.Bind(req, &s)
1413+
assert.Error(t, err)
1414+
assert.Equal(t, errUnknownType, err)
1415+
}

0 commit comments

Comments
 (0)