Merge branch 'master' into drop-go1.8-1.9

This commit is contained in:
Bo-Yi Wu 2019-06-28 23:37:18 +08:00 committed by GitHub
commit a39162420a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 5 deletions

View File

@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Only Bind Query String](#only-bind-query-string) - [Only Bind Query String](#only-bind-query-string)
- [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Query String or Post Data](#bind-query-string-or-post-data)
- [Bind Uri](#bind-uri) - [Bind Uri](#bind-uri)
- [Bind Header](#bind-header)
- [Bind HTML checkboxes](#bind-html-checkboxes) - [Bind HTML checkboxes](#bind-html-checkboxes)
- [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
- [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
@ -755,7 +756,7 @@ func bookableDate(
) bool { ) bool {
if date, ok := field.Interface().(time.Time); ok { if date, ok := field.Interface().(time.Time); ok {
today := time.Now() today := time.Now()
if today.Year() > date.Year() || today.YearDay() > date.YearDay() { if today.After(date) {
return false return false
} }
} }
@ -910,6 +911,43 @@ $ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
$ curl -v localhost:8088/thinkerou/not-uuid $ curl -v localhost:8088/thinkerou/not-uuid
``` ```
### Bind Header
```go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
type testHeader struct {
Rate int `header:"Rate"`
Domain string `header:"Domain"`
}
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
h := testHeader{}
if err := c.ShouldBindHeader(&h); err != nil {
c.JSON(200, err)
}
fmt.Printf("%#v\n", h)
c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})
})
r.Run()
// client
// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
// output
// {"Domain":"music","Rate":300}
}
```
### Bind HTML checkboxes ### Bind HTML checkboxes
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
@ -1081,8 +1119,8 @@ Using JSONP to request data from a server in a different domain. Add callback t
func main() { func main() {
r := gin.Default() r := gin.Default()
r.GET("/JSONP?callback=x", func(c *gin.Context) { r.GET("/JSONP", func(c *gin.Context) {
data := map[string]interface{}{ data := gin.H{
"foo": "bar", "foo": "bar",
} }
@ -1093,6 +1131,9 @@ func main() {
// Listen and serve on 0.0.0.0:8080 // Listen and serve on 0.0.0.0:8080
r.Run(":8080") r.Run(":8080")
// client
// curl http://127.0.0.1:8080/JSONP?callback=x
} }
``` ```
@ -1105,7 +1146,7 @@ func main() {
r := gin.Default() r := gin.Default()
r.GET("/someJSON", func(c *gin.Context) { r.GET("/someJSON", func(c *gin.Context) {
data := map[string]interface{}{ data := gin.H{
"lang": "GO语言", "lang": "GO语言",
"tag": "<br>", "tag": "<br>",
} }
@ -1314,7 +1355,7 @@ func main() {
router.LoadHTMLFiles("./testdata/template/raw.tmpl") router.LoadHTMLFiles("./testdata/template/raw.tmpl")
router.GET("/raw", func(c *gin.Context) { router.GET("/raw", func(c *gin.Context) {
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ c.HTML(http.StatusOK, "raw.tmpl", gin.H{
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
}) })
}) })

View File

@ -78,6 +78,7 @@ var (
MsgPack = msgpackBinding{} MsgPack = msgpackBinding{}
YAML = yamlBinding{} YAML = yamlBinding{}
Uri = uriBinding{} Uri = uriBinding{}
Header = headerBinding{}
) )
// Default returns the appropriate Binding instance based on the HTTP method // Default returns the appropriate Binding instance based on the HTTP method

View File

@ -667,6 +667,31 @@ func TestExistsFails(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
} }
func TestHeaderBinding(t *testing.T) {
h := Header
assert.Equal(t, "header", h.Name())
type tHeader struct {
Limit int `header:"limit"`
}
var theader tHeader
req := requestWithBody("GET", "/", "")
req.Header.Add("limit", "1000")
assert.NoError(t, h.Bind(req, &theader))
assert.Equal(t, 1000, theader.Limit)
req = requestWithBody("GET", "/", "")
req.Header.Add("fail", `{fail:fail}`)
type failStruct struct {
Fail map[string]interface{} `header:"fail"`
}
err := h.Bind(req, &failStruct{})
assert.Error(t, err)
}
func TestUriBinding(t *testing.T) { func TestUriBinding(t *testing.T) {
b := Uri b := Uri
assert.Equal(t, "uri", b.Name()) assert.Equal(t, "uri", b.Name())

34
binding/header.go Normal file
View File

@ -0,0 +1,34 @@
package binding
import (
"net/http"
"net/textproto"
"reflect"
)
type headerBinding struct{}
func (headerBinding) Name() string {
return "header"
}
func (headerBinding) Bind(req *http.Request, obj interface{}) error {
if err := mapHeader(obj, req.Header); err != nil {
return err
}
return validate(obj)
}
func mapHeader(ptr interface{}, h map[string][]string) error {
return mappingByPtr(ptr, headerSource(h), "header")
}
type headerSource map[string][]string
var _ setter = headerSource(nil)
func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
}

View File

@ -583,6 +583,11 @@ func (c *Context) BindYAML(obj interface{}) error {
return c.MustBindWith(obj, binding.YAML) return c.MustBindWith(obj, binding.YAML)
} }
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) BindHeader(obj interface{}) error {
return c.MustBindWith(obj, binding.Header)
}
// BindUri binds the passed struct pointer using binding.Uri. // BindUri binds the passed struct pointer using binding.Uri.
// It will abort the request with HTTP 400 if any error occurs. // It will abort the request with HTTP 400 if any error occurs.
func (c *Context) BindUri(obj interface{}) error { func (c *Context) BindUri(obj interface{}) error {
@ -637,6 +642,11 @@ func (c *Context) ShouldBindYAML(obj interface{}) error {
return c.ShouldBindWith(obj, binding.YAML) return c.ShouldBindWith(obj, binding.YAML)
} }
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) ShouldBindHeader(obj interface{}) error {
return c.ShouldBindWith(obj, binding.Header)
}
// ShouldBindUri binds the passed struct pointer using the specified binding engine. // ShouldBindUri binds the passed struct pointer using the specified binding engine.
func (c *Context) ShouldBindUri(obj interface{}) error { func (c *Context) ShouldBindUri(obj interface{}) error {
m := make(map[string][]string) m := make(map[string][]string)

View File

@ -1436,6 +1436,28 @@ func TestContextBindWithXML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestContextBindHeader(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Add("rate", "8000")
c.Request.Header.Add("domain", "music")
c.Request.Header.Add("limit", "1000")
var testHeader struct {
Rate int `header:"Rate"`
Domain string `header:"Domain"`
Limit int `header:"limit"`
}
assert.NoError(t, c.BindHeader(&testHeader))
assert.Equal(t, 8000, testHeader.Rate)
assert.Equal(t, "music", testHeader.Domain)
assert.Equal(t, 1000, testHeader.Limit)
assert.Equal(t, 0, w.Body.Len())
}
func TestContextBindWithQuery(t *testing.T) { func TestContextBindWithQuery(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
@ -1543,6 +1565,28 @@ func TestContextShouldBindWithXML(t *testing.T) {
assert.Equal(t, 0, w.Body.Len()) assert.Equal(t, 0, w.Body.Len())
} }
func TestContextShouldBindHeader(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Add("rate", "8000")
c.Request.Header.Add("domain", "music")
c.Request.Header.Add("limit", "1000")
var testHeader struct {
Rate int `header:"Rate"`
Domain string `header:"Domain"`
Limit int `header:"limit"`
}
assert.NoError(t, c.ShouldBindHeader(&testHeader))
assert.Equal(t, 8000, testHeader.Rate)
assert.Equal(t, "music", testHeader.Domain)
assert.Equal(t, 1000, testHeader.Limit)
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)