Skip to content

Commit

Permalink
refactor(form_mapping.go): mapping ptr, struct and map (#1749)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
vkd authored and thinkerou committed Mar 3, 2019
1 parent 893c6ca commit 0d50ce8
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 123 deletions.
18 changes: 0 additions & 18 deletions README.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1836,24 +1836,6 @@ $ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
{"d":"world","x":{"FieldX":"hello"}} {"d":"world","x":{"FieldX":"hello"}}
``` ```


**NOTE**: NOT support the follow style struct:

```go
type StructX struct {
X struct {} `form:"name_x"` // HERE have form
}

type StructY struct {
Y StructX `form:"name_y"` // HERE have form
}

type StructZ struct {
Z *StructZ `form:"name_z"` // HERE have form
}
```

In a word, only support nested custom struct which have no `form` now.

### Try to bind body into different structs ### Try to bind body into different structs


The normal methods for binding request body consumes `c.Request.Body` and they The normal methods for binding request body consumes `c.Request.Body` and they
Expand Down
134 changes: 115 additions & 19 deletions binding/binding_test.go
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"testing" "testing"
"time" "time"


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


type FooStructForMapType struct { type FooStructForMapType struct {
// Unknown type: not support map
MapFoo map[string]interface{} `form:"map_foo"` MapFoo map[string]interface{} `form:"map_foo"`
} }


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


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


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


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


func createFormPostRequestFail() *http.Request { func createFormPostRequestForMap(t *testing.T) *http.Request {
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}"))
assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEPOSTForm)
return req
}

func createFormPostRequestForMapFail(t *testing.T) *http.Request {
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello"))
assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEPOSTForm) req.Header.Set("Content-Type", MIMEPOSTForm)
return req return req
} }
Expand All @@ -535,26 +545,42 @@ func createFormMultipartRequest(t *testing.T) *http.Request {
assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.SetBoundary(boundary))
assert.NoError(t, mw.WriteField("foo", "bar")) assert.NoError(t, mw.WriteField("foo", "bar"))
assert.NoError(t, mw.WriteField("bar", "foo")) assert.NoError(t, mw.WriteField("bar", "foo"))
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }


func createFormMultipartRequestFail(t *testing.T) *http.Request { func createFormMultipartRequestForMap(t *testing.T) *http.Request {
boundary := "--testboundary" boundary := "--testboundary"
body := new(bytes.Buffer) body := new(bytes.Buffer)
mw := multipart.NewWriter(body) mw := multipart.NewWriter(body)
defer mw.Close() defer mw.Close()


assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.SetBoundary(boundary))
assert.NoError(t, mw.WriteField("map_foo", "bar")) assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}"))
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req
}

func createFormMultipartRequestForMapFail(t *testing.T) *http.Request {
boundary := "--testboundary"
body := new(bytes.Buffer)
mw := multipart.NewWriter(body)
defer mw.Close()

assert.NoError(t, mw.SetBoundary(boundary))
assert.NoError(t, mw.WriteField("map_foo", "3.14"))
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }


func TestBindingFormPost(t *testing.T) { func TestBindingFormPost(t *testing.T) {
req := createFormPostRequest() req := createFormPostRequest(t)
var obj FooBarStruct var obj FooBarStruct
assert.NoError(t, FormPost.Bind(req, &obj)) assert.NoError(t, FormPost.Bind(req, &obj))


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


func TestBindingDefaultValueFormPost(t *testing.T) { func TestBindingDefaultValueFormPost(t *testing.T) {
req := createDefaultFormPostRequest() req := createDefaultFormPostRequest(t)
var obj FooDefaultBarStruct var obj FooDefaultBarStruct
assert.NoError(t, FormPost.Bind(req, &obj)) assert.NoError(t, FormPost.Bind(req, &obj))


assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, "hello", obj.Bar) assert.Equal(t, "hello", obj.Bar)
} }


func TestBindingFormPostFail(t *testing.T) { func TestBindingFormPostForMap(t *testing.T) {
req := createFormPostRequestFail() req := createFormPostRequestForMap(t)
var obj FooStructForMapType
err := FormPost.Bind(req, &obj)
assert.NoError(t, err)
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
}

func TestBindingFormPostForMapFail(t *testing.T) {
req := createFormPostRequestForMapFail(t)
var obj FooStructForMapType var obj FooStructForMapType
err := FormPost.Bind(req, &obj) err := FormPost.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
Expand All @@ -589,8 +623,18 @@ func TestBindingFormMultipart(t *testing.T) {
assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "foo", obj.Bar)
} }


func TestBindingFormMultipartFail(t *testing.T) { func TestBindingFormMultipartForMap(t *testing.T) {
req := createFormMultipartRequestFail(t) req := createFormMultipartRequestForMap(t)
var obj FooStructForMapType
err := FormMultipart.Bind(req, &obj)
assert.NoError(t, err)
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string))
assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64))
}

func TestBindingFormMultipartForMapFail(t *testing.T) {
req := createFormMultipartRequestForMapFail(t)
var obj FooStructForMapType var obj FooStructForMapType
err := FormMultipart.Bind(req, &obj) err := FormMultipart.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
Expand Down Expand Up @@ -773,6 +817,17 @@ func TestFormBindingFail(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }


func TestFormBindingMultipartFail(t *testing.T) {
obj := FooBarStruct{}
req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar"))
assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary")
_, err = req.MultipartReader()
assert.NoError(t, err)
err = Form.Bind(req, &obj)
assert.Error(t, err)
}

func TestFormPostBindingFail(t *testing.T) { func TestFormPostBindingFail(t *testing.T) {
b := FormPost b := FormPost
assert.Equal(t, "form-urlencoded", b.Name()) assert.Equal(t, "form-urlencoded", b.Name())
Expand Down Expand Up @@ -1109,7 +1164,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
case "Map": case "Map":
obj := FooStructForMapType{} obj := FooStructForMapType{}
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.NoError(t, err)
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
case "SliceMap": case "SliceMap":
obj := FooStructForSliceMapType{} obj := FooStructForSliceMapType{}
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
Expand Down Expand Up @@ -1317,3 +1373,43 @@ func TestCanSet(t *testing.T) {
var c CanSetStruct var c CanSetStruct
assert.Nil(t, mapForm(&c, nil)) assert.Nil(t, mapForm(&c, nil))
} }

func formPostRequest(path, body string) *http.Request {
req := requestWithBody("POST", path, body)
req.Header.Add("Content-Type", MIMEPOSTForm)
return req
}

func TestBindingSliceDefault(t *testing.T) {
var s struct {
Friends []string `form:"friends,default=mike"`
}
req := formPostRequest("", "")
err := Form.Bind(req, &s)
assert.NoError(t, err)

assert.Len(t, s.Friends, 1)
assert.Equal(t, "mike", s.Friends[0])
}

func TestBindingStructField(t *testing.T) {
var s struct {
Opts struct {
Port int
} `form:"opts"`
}
req := formPostRequest("", `opts={"Port": 8000}`)
err := Form.Bind(req, &s)
assert.NoError(t, err)
assert.Equal(t, 8000, s.Opts.Port)
}

func TestBindingUnknownTypeChan(t *testing.T) {
var s struct {
Stop chan bool `form:"stop"`
}
req := formPostRequest("", "stop=true")
err := Form.Bind(req, &s)
assert.Error(t, err)
assert.Equal(t, errUnknownType, err)
}
Loading

0 comments on commit 0d50ce8

Please sign in to comment.