mirror of https://github.com/gin-gonic/gin.git
parent
2282be059b
commit
bd4f73af67
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)
|
||||
- [Graceful restart or stop](#graceful-restart-or-stop)
|
||||
- [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)
|
||||
- [Users](#users--)
|
||||
|
||||
|
@ -1461,6 +1462,98 @@ func loadTemplate() (*template.Template, error) {
|
|||
|
||||
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
|
||||
|
||||
The `net/http/httptest` package is preferable way for HTTP testing.
|
||||
|
|
|
@ -74,6 +74,18 @@ type FooStructForSliceType struct {
|
|||
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 {
|
||||
// Unknown type: not support map
|
||||
SliceMapFoo []map[string]interface{} `form:"slice_map_foo"`
|
||||
|
@ -395,6 +407,22 @@ func TestBindingFormForType(t *testing.T) {
|
|||
testFormBindingForType(t, "GET",
|
||||
"/?ptr_bar=test", "/?bar2=test",
|
||||
"", "", "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) {
|
||||
|
@ -953,6 +981,28 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
|
|||
req = requestWithBody(method, badPath, badBody)
|
||||
err = JSON.Bind(req, &obj)
|
||||
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":
|
||||
obj := FooStructForMapType{}
|
||||
err := b.Bind(req, &obj)
|
||||
|
|
|
@ -36,9 +36,16 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||
if inputFieldName == "" {
|
||||
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
|
||||
// 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 {
|
||||
err := mapForm(structField.Addr().Interface(), form)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue