Repository: gin-gonic/gin
Pull request #1893 (2019-05-10 09:14:12)

Now you can parse the inline lowercase start structure
``` golang
package main

import (
	"github.com/gin-gonic/gin"
)

type appkey struct {
	Appkey string `json:"appkey" form:"appkey"`
}

type Query struct {
	Page int `json:"page" form:"page"`
	Size int `json:"size" form:"size"`
	appkey
}

func main() {

	router := gin.Default()
	router.POST("/login", func(c *gin.Context) {

		var q2 Query

		if c.ShouldBindQuery(&q2) == nil {
			c.JSON(200, &q2)
		}
	})
	router.Run(":8088")
}
```
http client:

old:
``` bash
curl -X POST "127.0.0.1:8088/login?appkey=china&page=1&size=10"
{"page":1,"size":10,"appkey":""}
```
now:
``` bash
curl -X POST "127.0.0.1:8088/login?appkey=china&page=1&size=10"
{"page":1,"size":10,"appkey":"china"}
```





codecov[bot]: # [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1893?src=pr&el=h1) Report
> Merging [#1893](https://codecov.io/gh/gin-gonic/gin/pull/1893?src=pr&el=desc) into [master](https://codecov.io/gh/gin-gonic/gin/commit/04eecb1283dbd84c3babae7b3923ac71b18a56f9?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/gin-gonic/gin/pull/1893/graphs/tree.svg?width=650&token=Qpqxhwery8&height=150&src=pr)](https://codecov.io/gh/gin-gonic/gin/pull/1893?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1893      +/-   ##
==========================================
+ Coverage   98.74%   98.74%   +<.01%     
==========================================
  Files          38       38              
  Lines        2145     2147       +2     
==========================================
+ Hits         2118     2120       +2     
  Misses         15       15              
  Partials       12       12
```


| [Impacted Files](https://codecov.io/gh/gin-gonic/gin/pull/1893?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [binding/form\_mapping.go](https://codecov.io/gh/gin-gonic/gin/pull/1893/diff?src=pr&el=tree#diff-YmluZGluZy9mb3JtX21hcHBpbmcuZ28=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1893?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gin-gonic/gin/pull/1893?src=pr&el=footer). Last update [04eecb1...071baf1](https://codecov.io/gh/gin-gonic/gin/pull/1893?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index aaacf6c..32c5b66 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -70,12 +70,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
 		return isSetted, nil
 	}
 
-	ok, err := tryToSetValue(value, field, setter, tag)
-	if err != nil {
-		return false, err
-	}
-	if ok {
-		return true, nil
+	if vKind != reflect.Struct || !field.Anonymous {
+		ok, err := tryToSetValue(value, field, setter, tag)
+		if err != nil {
+			return false, err
+		}
+		if ok {
+			return true, nil
+		}
 	}
 
 	if vKind == reflect.Struct {
@@ -83,7 +85,8 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
 
 		var isSetted bool
 		for i := 0; i < value.NumField(); i++ {
-			if !value.Field(i).CanSet() {
+			sf := tValue.Field(i)
+			if sf.PkgPath != "" && !sf.Anonymous { // unexported
 				continue
 			}
 			ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)


Test patch:
diff --git a/binding/binding_test.go b/binding/binding_test.go
index 73bb770..6710e42 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -24,6 +24,16 @@ import (
 	"github.com/ugorji/go/codec"
 )
 
+type appkey struct {
+	Appkey string `json:"appkey" form:"appkey"`
+}
+
+type QueryTest struct {
+	Page int `json:"page" form:"page"`
+	Size int `json:"size" form:"size"`
+	appkey
+}
+
 type FooStruct struct {
 	Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
 }
@@ -189,6 +199,18 @@ func TestBindingForm2(t *testing.T) {
 		"", "")
 }
 
+func TestBindingFormEmbeddedStruct(t *testing.T) {
+	testFormBindingEmbeddedStruct(t, "POST",
+		"/", "/",
+		"page=1&size=2&appkey=test-appkey", "bar2=foo")
+}
+
+func TestBindingFormEmbeddedStruct2(t *testing.T) {
+	testFormBindingEmbeddedStruct(t, "GET",
+		"/?page=1&size=2&appkey=test-appkey", "/?bar2=foo",
+		"", "")
+}
+
 func TestBindingFormDefaultValue(t *testing.T) {
 	testFormBindingDefaultValue(t, "POST",
 		"/", "/",
@@ -688,6 +710,23 @@ func TestUriInnerBinding(t *testing.T) {
 	assert.Equal(t, tag.S.Age, expectedAge)
 }
 
+func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := QueryTest{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	assert.Equal(t, 1, obj.Page)
+	assert.Equal(t, 2, obj.Size)
+	assert.Equal(t, "test-appkey", obj.Appkey)
+
+}
+
 func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
 	b := Form
 	assert.Equal(t, "form", b.Name())