mirror of https://github.com/gin-gonic/gin.git
Merge branch 'master' into feature/multiple-binding
This commit is contained in:
commit
1e0e4a8954
93
README.md
93
README.md
|
@ -51,6 +51,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||||
- [Run multiple service using Gin](#run-multiple-service-using-gin)
|
- [Run multiple service using Gin](#run-multiple-service-using-gin)
|
||||||
- [Graceful restart or stop](#graceful-restart-or-stop)
|
- [Graceful restart or stop](#graceful-restart-or-stop)
|
||||||
- [Build a single binary with templates](#build-a-single-binary-with-templates)
|
- [Build a single binary with templates](#build-a-single-binary-with-templates)
|
||||||
|
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
- [Users](#users--)
|
- [Users](#users--)
|
||||||
|
|
||||||
|
@ -1461,6 +1462,98 @@ func loadTemplate() (*template.Template, error) {
|
||||||
|
|
||||||
See a complete example in the `examples/assets-in-binary` directory.
|
See a complete example in the `examples/assets-in-binary` directory.
|
||||||
|
|
||||||
|
### Bind form-data request with custom struct
|
||||||
|
|
||||||
|
The follow example using custom struct:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type StructA struct {
|
||||||
|
FieldA string `form:"field_a"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructB struct {
|
||||||
|
NestedStruct StructA
|
||||||
|
FieldB string `form:"field_b"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructC struct {
|
||||||
|
NestedStructPointer *StructA
|
||||||
|
FieldC string `form:"field_c"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructD struct {
|
||||||
|
NestedAnonyStruct struct {
|
||||||
|
FieldX string `form:"field_x"`
|
||||||
|
}
|
||||||
|
FieldD string `form:"field_d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDataB(c *gin.Context) {
|
||||||
|
var b StructB
|
||||||
|
c.Bind(&b)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"a": b.NestedStruct,
|
||||||
|
"b": b.FieldB,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDataC(c *gin.Context) {
|
||||||
|
var b StructC
|
||||||
|
c.Bind(&b)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"a": b.NestedStructPointer,
|
||||||
|
"c": b.FieldC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDataD(c *gin.Context) {
|
||||||
|
var b StructD
|
||||||
|
c.Bind(&b)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"x": b.NestedAnonyStruct,
|
||||||
|
"d": b.FieldD,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.GET("/getb", GetDataB)
|
||||||
|
r.GET("/getc", GetDataC)
|
||||||
|
r.GET("/getd", GetDataD)
|
||||||
|
|
||||||
|
r.Run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the command `curl` command result:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
|
||||||
|
{"a":{"FieldA":"hello"},"b":"world"}
|
||||||
|
$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
|
||||||
|
{"a":{"FieldA":"hello"},"c":"world"}
|
||||||
|
$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
|
||||||
|
{"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 hava form
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructZ struct {
|
||||||
|
Z *StructZ `form:"name_z"` // HERE hava form
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In a word, only support nested custom struct which have no `form` now.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
The `net/http/httptest` package is preferable way for HTTP testing.
|
The `net/http/httptest` package is preferable way for HTTP testing.
|
||||||
|
|
|
@ -74,6 +74,18 @@ type FooStructForSliceType struct {
|
||||||
SliceFoo []int `form:"slice_foo"`
|
SliceFoo []int `form:"slice_foo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FooStructForStructType struct {
|
||||||
|
StructFoo struct {
|
||||||
|
Idx int `form:"idx"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FooStructForStructPointerType struct {
|
||||||
|
StructPointerFoo *struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type FooStructForSliceMapType struct {
|
type FooStructForSliceMapType struct {
|
||||||
// Unknown type: not support map
|
// Unknown type: not support map
|
||||||
SliceMapFoo []map[string]interface{} `form:"slice_map_foo"`
|
SliceMapFoo []map[string]interface{} `form:"slice_map_foo"`
|
||||||
|
@ -395,6 +407,22 @@ func TestBindingFormForType(t *testing.T) {
|
||||||
testFormBindingForType(t, "GET",
|
testFormBindingForType(t, "GET",
|
||||||
"/?ptr_bar=test", "/?bar2=test",
|
"/?ptr_bar=test", "/?bar2=test",
|
||||||
"", "", "Ptr")
|
"", "", "Ptr")
|
||||||
|
|
||||||
|
testFormBindingForType(t, "POST",
|
||||||
|
"/", "/",
|
||||||
|
"idx=123", "id1=1", "Struct")
|
||||||
|
|
||||||
|
testFormBindingForType(t, "GET",
|
||||||
|
"/?idx=123", "/?id1=1",
|
||||||
|
"", "", "Struct")
|
||||||
|
|
||||||
|
testFormBindingForType(t, "POST",
|
||||||
|
"/", "/",
|
||||||
|
"name=thinkerou", "name1=ou", "StructPointer")
|
||||||
|
|
||||||
|
testFormBindingForType(t, "GET",
|
||||||
|
"/?name=thinkerou", "/?name1=ou",
|
||||||
|
"", "", "StructPointer")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingQuery(t *testing.T) {
|
func TestBindingQuery(t *testing.T) {
|
||||||
|
@ -953,6 +981,28 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
|
||||||
req = requestWithBody(method, badPath, badBody)
|
req = requestWithBody(method, badPath, badBody)
|
||||||
err = JSON.Bind(req, &obj)
|
err = JSON.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
case "Struct":
|
||||||
|
obj := FooStructForStructType{}
|
||||||
|
err := b.Bind(req, &obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
struct {
|
||||||
|
Idx int "form:\"idx\""
|
||||||
|
}(struct {
|
||||||
|
Idx int "form:\"idx\""
|
||||||
|
}{Idx: 123}),
|
||||||
|
obj.StructFoo)
|
||||||
|
case "StructPointer":
|
||||||
|
obj := FooStructForStructPointerType{}
|
||||||
|
err := b.Bind(req, &obj)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t,
|
||||||
|
struct {
|
||||||
|
Name string "form:\"name\""
|
||||||
|
}(struct {
|
||||||
|
Name string "form:\"name\""
|
||||||
|
}{Name: "thinkerou"}),
|
||||||
|
*obj.StructPointerFoo)
|
||||||
case "Map":
|
case "Map":
|
||||||
obj := FooStructForMapType{}
|
obj := FooStructForMapType{}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
|
|
|
@ -36,9 +36,16 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
||||||
if inputFieldName == "" {
|
if inputFieldName == "" {
|
||||||
inputFieldName = typeField.Name
|
inputFieldName = typeField.Name
|
||||||
|
|
||||||
// if "form" tag is nil, we inspect if the field is a struct.
|
// if "form" tag is nil, we inspect if the field is a struct or struct pointer.
|
||||||
// this would not make sense for JSON parsing but it does for a form
|
// this would not make sense for JSON parsing but it does for a form
|
||||||
// since data is flatten
|
// since data is flatten
|
||||||
|
if structFieldKind == reflect.Ptr {
|
||||||
|
if !structField.Elem().IsValid() {
|
||||||
|
structField.Set(reflect.New(structField.Type().Elem()))
|
||||||
|
}
|
||||||
|
structField = structField.Elem()
|
||||||
|
structFieldKind = structField.Kind()
|
||||||
|
}
|
||||||
if structFieldKind == reflect.Struct {
|
if structFieldKind == reflect.Struct {
|
||||||
err := mapForm(structField.Addr().Interface(), form)
|
err := mapForm(structField.Addr().Interface(), form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue