Merge branch 'master' into features/form-binding-time-improve

This commit is contained in:
Alexander Lokhman 2018-08-17 10:01:43 +01:00 committed by GitHub
commit 7ea8f81faf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 53 deletions

View File

@ -59,7 +59,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
- [http2 server push](#http2-server-push) - [http2 server push](#http2-server-push)
- [Testing](#testing) - [Testing](#testing)
- [Users](#users--) - [Users](#users)
## Installation ## Installation
@ -100,7 +100,7 @@ $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
```sh ```sh
$ govendor init $ govendor init
$ govendor fetch github.com/gin-gonic/gin@v1.2 $ govendor fetch github.com/gin-gonic/gin@v1.3
``` ```
4. Copy a starting template inside your project 4. Copy a starting template inside your project
@ -198,7 +198,7 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894
## Build with [jsoniter](https://github.com/json-iterator/go) ## Build with [jsoniter](https://github.com/json-iterator/go)
Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.
```sh ```sh
$ go build -tags=jsoniter . $ go build -tags=jsoniter .
@ -534,10 +534,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
Also, Gin provides two sets of methods for binding: Also, Gin provides two sets of methods for binding:
- **Type** - Must bind - **Type** - Must bind
- **Methods** - `Bind`, `BindJSON`, `BindQuery` - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
- **Type** - Should bind - **Type** - Should bind
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery` - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
@ -547,8 +547,8 @@ You can also specify that specific fields are required. If a field is decorated
```go ```go
// Binding from JSON // Binding from JSON
type Login struct { type Login struct {
User string `form:"user" json:"user" binding:"required"` User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"`
} }
func main() { func main() {
@ -557,30 +557,55 @@ func main() {
// Example for binding JSON ({"user": "manu", "password": "123"}) // Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) { router.POST("/loginJSON", func(c *gin.Context) {
var json Login var json Login
if err := c.ShouldBindJSON(&json); err == nil { if err := c.ShouldBindXML(&json); err != nil {
if json.User == "manu" && json.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} }
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding XML (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>user</user>
// <password>123</user>
// </root>)
router.POST("/loginXML", func(c *gin.Context) {
var xml Login
if err := c.ShouldBindXML(&xml); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if xml.User != "manu" || xml.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
}) })
// Example for binding a HTML form (user=manu&password=123) // Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) { router.POST("/loginForm", func(c *gin.Context) {
var form Login var form Login
// This will infer what binder to use depending on the content-type header. // This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err == nil { if err := c.ShouldBind(&form); err != nil {
if form.User == "manu" && form.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} }
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
}) })
// Listen and serve on 0.0.0.0:8080 // Listen and serve on 0.0.0.0:8080
@ -679,7 +704,7 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
``` ```
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more.
### Only Bind Query String ### Only Bind Query String

View File

@ -177,7 +177,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error {
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
timeFormat := structField.Tag.Get("time_format") timeFormat := structField.Tag.Get("time_format")
if timeFormat == "" { if timeFormat == "" {
return errors.New("Blank time format") timeFormat = time.RFC3339
} }
if val == "" { if val == "" {

View File

@ -511,6 +511,11 @@ func (c *Context) BindJSON(obj interface{}) error {
return c.MustBindWith(obj, binding.JSON) return c.MustBindWith(obj, binding.JSON)
} }
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
func (c *Context) BindXML(obj interface{}) error {
return c.MustBindWith(obj, binding.XML)
}
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) BindQuery(obj interface{}) error { func (c *Context) BindQuery(obj interface{}) error {
return c.MustBindWith(obj, binding.Query) return c.MustBindWith(obj, binding.Query)
@ -545,6 +550,11 @@ func (c *Context) ShouldBindJSON(obj interface{}) error {
return c.ShouldBindWith(obj, binding.JSON) return c.ShouldBindWith(obj, binding.JSON)
} }
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
func (c *Context) ShouldBindXML(obj interface{}) error {
return c.ShouldBindWith(obj, binding.XML)
}
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj interface{}) error { func (c *Context) ShouldBindQuery(obj interface{}) error {
return c.ShouldBindWith(obj, binding.Query) return c.ShouldBindWith(obj, binding.Query)

View File

@ -1302,6 +1302,26 @@ func TestContextBindWithJSON(t *testing.T) {
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestContextBindWithXML(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>FOO</foo>
<bar>BAR</bar>
</root>`))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
var obj struct {
Foo string `xml:"foo"`
Bar string `xml:"bar"`
}
assert.NoError(t, c.BindXML(&obj))
assert.Equal(t, "FOO", obj.Foo)
assert.Equal(t, "BAR", obj.Bar)
assert.Equal(t, 0, w.Body.Len())
}
func TestContextBindWithQuery(t *testing.T) { func TestContextBindWithQuery(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -1372,6 +1392,27 @@ func TestContextShouldBindWithJSON(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestContextShouldBindWithXML(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>FOO</foo>
<bar>BAR</bar>
</root>`))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type
var obj struct {
Foo string `xml:"foo"`
Bar string `xml:"bar"`
}
assert.NoError(t, c.ShouldBindXML(&obj))
assert.Equal(t, "FOO", obj.Foo)
assert.Equal(t, "BAR", obj.Bar)
assert.Equal(t, 0, w.Body.Len())
}
func TestContextShouldBindWithQuery(t *testing.T) { func TestContextShouldBindWithQuery(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)

94
vendor/vendor.json vendored
View File

@ -1,13 +1,12 @@
{ {
"comment": "v1.2", "comment": "v1.3.0",
"ignore": "test", "ignore": "test",
"package": [ "package": [
{ {
"checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=",
"comment": "v1.1.0",
"path": "github.com/davecgh/go-spew/spew", "path": "github.com/davecgh/go-spew/spew",
"revision": "346938d642f2ec3594ed81d874461961cd0faa76", "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73",
"revisionTime": "2016-10-29T20:57:26Z" "revisionTime": "2018-02-21T22:46:20Z"
}, },
{ {
"checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=",
@ -16,35 +15,62 @@
"revisionTime": "2017-01-09T09:34:21Z" "revisionTime": "2017-01-09T09:34:21Z"
}, },
{ {
"checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=",
"path": "github.com/golang/protobuf/proto", "path": "github.com/golang/protobuf/proto",
"revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265",
"revisionTime": "2017-06-01T23:02:30Z" "revisionTime": "2018-04-30T18:52:41Z",
"version": "v1.1.0",
"versionExact": "v1.1.0"
}, },
{ {
"checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=",
"path": "github.com/json-iterator/go", "path": "github.com/json-iterator/go",
"revision": "36b14963da70d11297d313183d7e6388c8510e1e", "revision": "1624edc4454b8682399def8740d46db5e4362ba4",
"revisionTime": "2017-08-29T15:58:51Z" "revisionTime": "2018-08-06T06:07:27Z",
"version": "1.1.5",
"versionExact": "1.1.5"
}, },
{ {
"checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=",
"path": "github.com/mattn/go-isatty", "path": "github.com/mattn/go-isatty",
"revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", "revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39",
"revisionTime": "2017-03-07T16:30:44Z" "revisionTime": "2017-09-25T05:34:41Z",
"version": "v0.0.3",
"versionExact": "v0.0.3"
}, },
{ {
"checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=",
"comment": "v1.1.4", "path": "github.com/modern-go/concurrent",
"revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94",
"revisionTime": "2018-03-06T01:26:44Z"
},
{
"checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=",
"path": "github.com/modern-go/reflect2",
"revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8",
"revisionTime": "2018-07-18T01:23:57Z"
},
{
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
"path": "github.com/pmezard/go-difflib/difflib",
"revision": "792786c7400a136282c1664665ae0a8db921c6c2",
"revisionTime": "2016-01-10T10:55:54Z"
},
{
"checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=",
"path": "github.com/stretchr/testify/assert", "path": "github.com/stretchr/testify/assert",
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686",
"revisionTime": "2016-09-25T22:06:09Z" "revisionTime": "2018-05-06T18:05:49Z",
"version": "v1.2.2",
"versionExact": "v1.2.2"
}, },
{ {
"checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=",
"path": "github.com/ugorji/go/codec", "path": "github.com/ugorji/go/codec",
"revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab",
"revisionTime": "2017-02-15T20:11:44Z" "revisionTime": "2018-04-07T10:07:33Z",
"version": "v1.1.1",
"versionExact": "v1.1.1"
}, },
{ {
"checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
@ -54,18 +80,26 @@
"revisionTime": "2016-10-18T08:54:36Z" "revisionTime": "2016-10-18T08:54:36Z"
}, },
{ {
"checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=",
"comment": "v8.18.1", "path": "golang.org/x/sys/unix",
"path": "gopkg.in/go-playground/validator.v8", "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded",
"revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c", "revisionTime": "2018-08-15T07:37:39Z"
"revisionTime": "2016-07-18T13:41:25Z"
}, },
{ {
"checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=",
"comment": "v2", "path": "gopkg.in/go-playground/validator.v8",
"revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf",
"revisionTime": "2017-07-30T05:02:35Z",
"version": "v8.18.2",
"versionExact": "v8.18.2"
},
{
"checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=",
"path": "gopkg.in/yaml.v2", "path": "gopkg.in/yaml.v2",
"revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183",
"revisionTime": "2016-09-28T15:37:09Z" "revisionTime": "2018-03-28T19:50:20Z",
"version": "v2.2.1",
"versionExact": "v2.2.1"
} }
], ],
"rootPath": "github.com/gin-gonic/gin" "rootPath": "github.com/gin-gonic/gin"