ls 5 years ago
parent
commit
05482cff8f

+ 52 - 9
binding/LICENSE

@@ -10,6 +10,7 @@ import (
 	//validator "gopkg.in/go-playground/validator.v8"
 )
 
+// Content-Type MIME of the most common data formats.
 const (
 	MIMEJSON              = "application/json"
 	MIMEHTML              = "text/html"
@@ -21,16 +22,36 @@ const (
 	MIMEPROTOBUF          = "application/x-protobuf"
 	MIMEMSGPACK           = "application/x-msgpack"
 	MIMEMSGPACK2          = "application/msgpack"
+	MIMEYAML              = "application/x-yaml"
 )
 
-// Binding bind http request params to struct
+// Binding describes the interface which needs to be implemented for binding the
+// data present in the request such as JSON request body, query parameters or
+// the form POST.
 type Binding interface {
 	Name() string
 	Bind(*http.Request, interface{}) error
 }
 
+// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
+// but it reads the body from supplied bytes instead of req.Body.
+type BindingBody interface {
+	Binding
+	BindBody([]byte, interface{}) error
+}
+
+// BindingURI adds BindUri method to Binding. BindUri is similar with Bind,
+// but it read the Params.
+type BindingURI interface {
+	Name() string
+	BindURI(map[string][]string, interface{}) error
+}
+
 /*
-// StructValidator Validator
+// StructValidator is the minimal interface which needs to be implemented in
+// order for it to be used as the validator engine for ensuring the correctness
+// of the request. Gin provides a default implementation for this using
+// https://github.com/go-playground/validator/tree/v8.18.2.
 type StructValidator interface {
 	// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
 	// If the received type is not a struct, any validation should be skipped and nil must be returned.
@@ -39,14 +60,19 @@ type StructValidator interface {
 	// Otherwise nil must be returned.
 	ValidateStruct(interface{}) error
 
-	// RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key
-	// NOTE: if the key already exists, the previous validation function will be replaced.
-	// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
-	RegisterValidation(string, validator.Func) error
+	// Engine returns the underlying validator engine which powers the
+	// StructValidator implementation.
+	Engine() interface{}
 }
 
+// Validator is the default validator which implements the StructValidator
+// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
+// under the hood.
 var Validator StructValidator = &defaultValidator{}
 // */
+
+// These implement the Binding interface and can be used to bind the data
+// present in the request to struct instances.
 var (
 	JSON          = jsonBinding{}
 	XML           = xmlBinding{}
@@ -56,10 +82,13 @@ var (
 	FormMultipart = formMultipartBinding{}
 	ProtoBuf      = protobufBinding{}
 	MsgPack       = msgpackBinding{}
+	YAML          = yamlBinding{}
+	URI           = uriBinding{}
 )
 
-// BindDefault default binding
-func BindDefault(method, contentType string) Binding {
+// Default returns the appropriate Binding instance based on the HTTP method
+// and the content type.
+func Default(method, contentType string) Binding {
 	if method == "GET" {
 		return Form
 	}
@@ -73,6 +102,8 @@ func BindDefault(method, contentType string) Binding {
 		return ProtoBuf
 	case MIMEMSGPACK, MIMEMSGPACK2:
 		return MsgPack
+	case MIMEYAML:
+		return YAML
 	default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
 		return Form
 	}
@@ -89,15 +120,27 @@ func validate(obj interface{}) error {
 		// */
 }
 
+// Bind checks the Content-Type to select a binding engine automatically,
+// Depending the "Content-Type" header different bindings are used:
+//     "application/json" --> JSON binding
+//     "application/xml"  --> XML binding
+// otherwise --> returns an error.
+// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
+// It decodes the json payload into the struct specified as a pointer.
+// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
 func Bind(req *http.Request, obj interface{}) error {
-	b := BindDefault(req.Method, ContentType(req))
+	b := Default(req.Method, ContentType(req))
 	return MustBindWith(req, obj, b)
 }
 
+// MustBindWith binds the passed struct pointer using the specified binding engine.
+// It will abort the request with HTTP 400 if any error occurs.
+// See the binding package.
 func MustBindWith(req *http.Request, obj interface{}, b Binding) (err error) {
 	return b.Bind(req, obj)
 }
 
+// ContentType returns the Content-Type header of the request.
 func ContentType(req *http.Request) string {
 	return filterFlags(req.Header.Get("Content-Type"))
 }

+ 63 - 0
binding/binding_body_test.go

@@ -0,0 +1,63 @@
+package binding
+
+/*
+func TestBindingBody(t *testing.T) {
+	for _, tt := range []struct {
+		name    string
+		binding BindingBody
+		body    string
+		want    string
+	}{
+		{
+			name:    "JSON binding",
+			binding: JSON,
+			body:    `{"foo":"FOO"}`,
+		},
+		{
+			name:    "XML binding",
+			binding: XML,
+			body: `<?xml version="1.0" encoding="UTF-8"?>
+<root>
+   <foo>FOO</foo>
+</root>`,
+		},
+		{
+			name:    "MsgPack binding",
+			binding: MsgPack,
+			body:    msgPackBody(t),
+		},
+		{
+			name:    "YAML binding",
+			binding: YAML,
+			body:    `foo: FOO`,
+		},
+	} {
+		t.Logf("testing: %s", tt.name)
+		req := requestWithBody("POST", "/", tt.body)
+		form := FooStruct{}
+		body, _ := ioutil.ReadAll(req.Body)
+		assert.NoError(t, tt.binding.BindBody(body, &form))
+		assert.Equal(t, FooStruct{"FOO"}, form)
+	}
+}
+
+func msgPackBody(t *testing.T) string {
+	test := FooStruct{"FOO"}
+	h := new(codec.MsgpackHandle)
+	buf := bytes.NewBuffer(nil)
+	assert.NoError(t, codec.NewEncoder(buf, h).Encode(test))
+	return buf.String()
+}
+
+func TestBindingBodyProto(t *testing.T) {
+	test := protoexample.Test{
+		Label: proto.String("FOO"),
+	}
+	data, _ := proto.Marshal(&test)
+	req := requestWithBody("POST", "/", string(data))
+	form := protoexample.Test{}
+	body, _ := ioutil.ReadAll(req.Body)
+	assert.NoError(t, ProtoBuf.BindBody(body, &form))
+	assert.Equal(t, test, form)
+}
+// */

+ 1296 - 0
binding/binding_test.go

@@ -0,0 +1,1296 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"mime/multipart"
+	"net/http"
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/gin-gonic/gin/testdata/protoexample"
+	"github.com/golang/protobuf/proto"
+	"github.com/stretchr/testify/assert"
+	"github.com/ugorji/go/codec"
+)
+
+type FooStruct struct {
+	Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
+}
+
+type FooBarStruct struct {
+	FooStruct
+	Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"`
+}
+
+type FooDefaultBarStruct struct {
+	FooStruct
+	Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"`
+}
+
+type FooStructUseNumber struct {
+	Foo interface{} `json:"foo" binding:"required"`
+}
+
+type FooBarStructForTimeType struct {
+	TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"`
+	TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
+}
+
+type FooStructForTimeTypeNotFormat struct {
+	TimeFoo time.Time `form:"time_foo"`
+}
+
+type FooStructForTimeTypeFailFormat struct {
+	TimeFoo time.Time `form:"time_foo" time_format:"2017-11-15"`
+}
+
+type FooStructForTimeTypeFailLocation struct {
+	TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_location:"/asia/chongqing"`
+}
+
+type FooStructForMapType struct {
+	// Unknown type: not support map
+	MapFoo map[string]interface{} `form:"map_foo"`
+}
+
+type InvalidNameType struct {
+	TestName string `invalid_name:"test_name"`
+}
+
+type InvalidNameMapType struct {
+	TestName struct {
+		MapFoo map[string]interface{} `form:"map_foo"`
+	}
+}
+
+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"`
+}
+
+type FooStructForBoolType struct {
+	BoolFoo bool `form:"bool_foo"`
+}
+
+type FooBarStructForIntType struct {
+	IntFoo int `form:"int_foo"`
+	IntBar int `form:"int_bar" binding:"required"`
+}
+
+type FooBarStructForInt8Type struct {
+	Int8Foo int8 `form:"int8_foo"`
+	Int8Bar int8 `form:"int8_bar" binding:"required"`
+}
+
+type FooBarStructForInt16Type struct {
+	Int16Foo int16 `form:"int16_foo"`
+	Int16Bar int16 `form:"int16_bar" binding:"required"`
+}
+
+type FooBarStructForInt32Type struct {
+	Int32Foo int32 `form:"int32_foo"`
+	Int32Bar int32 `form:"int32_bar" binding:"required"`
+}
+
+type FooBarStructForInt64Type struct {
+	Int64Foo int64 `form:"int64_foo"`
+	Int64Bar int64 `form:"int64_bar" binding:"required"`
+}
+
+type FooBarStructForUintType struct {
+	UintFoo uint `form:"uint_foo"`
+	UintBar uint `form:"uint_bar" binding:"required"`
+}
+
+type FooBarStructForUint8Type struct {
+	Uint8Foo uint8 `form:"uint8_foo"`
+	Uint8Bar uint8 `form:"uint8_bar" binding:"required"`
+}
+
+type FooBarStructForUint16Type struct {
+	Uint16Foo uint16 `form:"uint16_foo"`
+	Uint16Bar uint16 `form:"uint16_bar" binding:"required"`
+}
+
+type FooBarStructForUint32Type struct {
+	Uint32Foo uint32 `form:"uint32_foo"`
+	Uint32Bar uint32 `form:"uint32_bar" binding:"required"`
+}
+
+type FooBarStructForUint64Type struct {
+	Uint64Foo uint64 `form:"uint64_foo"`
+	Uint64Bar uint64 `form:"uint64_bar" binding:"required"`
+}
+
+type FooBarStructForBoolType struct {
+	BoolFoo bool `form:"bool_foo"`
+	BoolBar bool `form:"bool_bar" binding:"required"`
+}
+
+type FooBarStructForFloat32Type struct {
+	Float32Foo float32 `form:"float32_foo"`
+	Float32Bar float32 `form:"float32_bar" binding:"required"`
+}
+
+type FooBarStructForFloat64Type struct {
+	Float64Foo float64 `form:"float64_foo"`
+	Float64Bar float64 `form:"float64_bar" binding:"required"`
+}
+
+type FooStructForStringPtrType struct {
+	PtrFoo *string `form:"ptr_foo"`
+	PtrBar *string `form:"ptr_bar" binding:"required"`
+}
+
+type FooStructForMapPtrType struct {
+	PtrBar *map[string]interface{} `form:"ptr_bar"`
+}
+
+func TestBindingDefault(t *testing.T) {
+	assert.Equal(t, Form, Default("GET", ""))
+	assert.Equal(t, Form, Default("GET", MIMEJSON))
+
+	assert.Equal(t, JSON, Default("POST", MIMEJSON))
+	assert.Equal(t, JSON, Default("PUT", MIMEJSON))
+
+	assert.Equal(t, XML, Default("POST", MIMEXML))
+	assert.Equal(t, XML, Default("PUT", MIMEXML2))
+
+	assert.Equal(t, Form, Default("POST", MIMEPOSTForm))
+	assert.Equal(t, Form, Default("PUT", MIMEPOSTForm))
+
+	assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm))
+	assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm))
+
+	assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF))
+	assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF))
+
+	assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK))
+	assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2))
+
+	assert.Equal(t, YAML, Default("POST", MIMEYAML))
+	assert.Equal(t, YAML, Default("PUT", MIMEYAML))
+}
+
+func TestBindingJSONNilBody(t *testing.T) {
+	var obj FooStruct
+	req, _ := http.NewRequest(http.MethodPost, "/", nil)
+	err := JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestBindingJSON(t *testing.T) {
+	testBodyBinding(t,
+		JSON, "json",
+		"/", "/",
+		`{"foo": "bar"}`, `{"bar": "foo"}`)
+}
+
+func TestBindingJSONUseNumber(t *testing.T) {
+	testBodyBindingUseNumber(t,
+		JSON, "json",
+		"/", "/",
+		`{"foo": 123}`, `{"bar": "foo"}`)
+}
+
+func TestBindingJSONUseNumber2(t *testing.T) {
+	testBodyBindingUseNumber2(t,
+		JSON, "json",
+		"/", "/",
+		`{"foo": 123}`, `{"bar": "foo"}`)
+}
+
+func TestBindingForm(t *testing.T) {
+	testFormBinding(t, "POST",
+		"/", "/",
+		"foo=bar&bar=foo", "bar2=foo")
+}
+
+func TestBindingForm2(t *testing.T) {
+	testFormBinding(t, "GET",
+		"/?foo=bar&bar=foo", "/?bar2=foo",
+		"", "")
+}
+
+func TestBindingFormDefaultValue(t *testing.T) {
+	testFormBindingDefaultValue(t, "POST",
+		"/", "/",
+		"foo=bar", "bar2=foo")
+}
+
+func TestBindingFormDefaultValue2(t *testing.T) {
+	testFormBindingDefaultValue(t, "GET",
+		"/?foo=bar", "/?bar2=foo",
+		"", "")
+}
+
+func TestBindingFormForTime(t *testing.T) {
+	testFormBindingForTime(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15&time_bar=", "bar2=foo")
+	testFormBindingForTimeNotFormat(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15", "bar2=foo")
+	testFormBindingForTimeFailFormat(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15", "bar2=foo")
+	testFormBindingForTimeFailLocation(t, "POST",
+		"/", "/",
+		"time_foo=2017-11-15", "bar2=foo")
+}
+
+func TestBindingFormForTime2(t *testing.T) {
+	testFormBindingForTime(t, "GET",
+		"/?time_foo=2017-11-15&time_bar=", "/?bar2=foo",
+		"", "")
+	testFormBindingForTimeNotFormat(t, "GET",
+		"/?time_foo=2017-11-15", "/?bar2=foo",
+		"", "")
+	testFormBindingForTimeFailFormat(t, "GET",
+		"/?time_foo=2017-11-15", "/?bar2=foo",
+		"", "")
+	testFormBindingForTimeFailLocation(t, "GET",
+		"/?time_foo=2017-11-15", "/?bar2=foo",
+		"", "")
+}
+
+func TestBindingFormInvalidName(t *testing.T) {
+	testFormBindingInvalidName(t, "POST",
+		"/", "/",
+		"test_name=bar", "bar2=foo")
+}
+
+func TestBindingFormInvalidName2(t *testing.T) {
+	testFormBindingInvalidName2(t, "POST",
+		"/", "/",
+		"map_foo=bar", "bar2=foo")
+}
+
+func TestBindingFormForType(t *testing.T) {
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"map_foo=", "bar2=1", "Map")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice")
+
+	testFormBindingForType(t, "GET",
+		"/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2",
+		"", "", "Slice")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap")
+
+	testFormBindingForType(t, "GET",
+		"/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2",
+		"", "", "SliceMap")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int_foo=&int_bar=-12", "bar2=-123", "Int")
+
+	testFormBindingForType(t, "GET",
+		"/?int_foo=&int_bar=-12", "/?bar2=-123",
+		"", "", "Int")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int8_foo=&int8_bar=-12", "bar2=-123", "Int8")
+
+	testFormBindingForType(t, "GET",
+		"/?int8_foo=&int8_bar=-12", "/?bar2=-123",
+		"", "", "Int8")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int16_foo=&int16_bar=-12", "bar2=-123", "Int16")
+
+	testFormBindingForType(t, "GET",
+		"/?int16_foo=&int16_bar=-12", "/?bar2=-123",
+		"", "", "Int16")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int32_foo=&int32_bar=-12", "bar2=-123", "Int32")
+
+	testFormBindingForType(t, "GET",
+		"/?int32_foo=&int32_bar=-12", "/?bar2=-123",
+		"", "", "Int32")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"int64_foo=&int64_bar=-12", "bar2=-123", "Int64")
+
+	testFormBindingForType(t, "GET",
+		"/?int64_foo=&int64_bar=-12", "/?bar2=-123",
+		"", "", "Int64")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint_foo=&uint_bar=12", "bar2=123", "Uint")
+
+	testFormBindingForType(t, "GET",
+		"/?uint_foo=&uint_bar=12", "/?bar2=123",
+		"", "", "Uint")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint8_foo=&uint8_bar=12", "bar2=123", "Uint8")
+
+	testFormBindingForType(t, "GET",
+		"/?uint8_foo=&uint8_bar=12", "/?bar2=123",
+		"", "", "Uint8")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint16_foo=&uint16_bar=12", "bar2=123", "Uint16")
+
+	testFormBindingForType(t, "GET",
+		"/?uint16_foo=&uint16_bar=12", "/?bar2=123",
+		"", "", "Uint16")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint32_foo=&uint32_bar=12", "bar2=123", "Uint32")
+
+	testFormBindingForType(t, "GET",
+		"/?uint32_foo=&uint32_bar=12", "/?bar2=123",
+		"", "", "Uint32")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"uint64_foo=&uint64_bar=12", "bar2=123", "Uint64")
+
+	testFormBindingForType(t, "GET",
+		"/?uint64_foo=&uint64_bar=12", "/?bar2=123",
+		"", "", "Uint64")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"bool_foo=&bool_bar=true", "bar2=true", "Bool")
+
+	testFormBindingForType(t, "GET",
+		"/?bool_foo=&bool_bar=true", "/?bar2=true",
+		"", "", "Bool")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32")
+
+	testFormBindingForType(t, "GET",
+		"/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3",
+		"", "", "Float32")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64")
+
+	testFormBindingForType(t, "GET",
+		"/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3",
+		"", "", "Float64")
+
+	testFormBindingForType(t, "POST",
+		"/", "/",
+		"ptr_bar=test", "bar2=test", "Ptr")
+
+	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) {
+	testQueryBinding(t, "POST",
+		"/?foo=bar&bar=foo", "/",
+		"foo=unused", "bar2=foo")
+}
+
+func TestBindingQuery2(t *testing.T) {
+	testQueryBinding(t, "GET",
+		"/?foo=bar&bar=foo", "/?bar2=foo",
+		"foo=unused", "")
+}
+
+func TestBindingQueryFail(t *testing.T) {
+	testQueryBindingFail(t, "POST",
+		"/?map_foo=", "/",
+		"map_foo=unused", "bar2=foo")
+}
+
+func TestBindingQueryFail2(t *testing.T) {
+	testQueryBindingFail(t, "GET",
+		"/?map_foo=", "/?bar2=foo",
+		"map_foo=unused", "")
+}
+
+func TestBindingQueryBoolFail(t *testing.T) {
+	testQueryBindingBoolFail(t, "GET",
+		"/?bool_foo=fasl", "/?bar2=foo",
+		"bool_foo=unused", "")
+}
+
+func TestBindingXML(t *testing.T) {
+	testBodyBinding(t,
+		XML, "xml",
+		"/", "/",
+		"<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>")
+}
+
+func TestBindingXMLFail(t *testing.T) {
+	testBodyBindingFail(t,
+		XML, "xml",
+		"/", "/",
+		"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
+}
+
+func TestBindingYAML(t *testing.T) {
+	testBodyBinding(t,
+		YAML, "yaml",
+		"/", "/",
+		`foo: bar`, `bar: foo`)
+}
+
+func TestBindingYAMLFail(t *testing.T) {
+	testBodyBindingFail(t,
+		YAML, "yaml",
+		"/", "/",
+		`foo:\nbar`, `bar: foo`)
+}
+
+func createFormPostRequest() *http.Request {
+	req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
+	req.Header.Set("Content-Type", MIMEPOSTForm)
+	return req
+}
+
+func createDefaultFormPostRequest() *http.Request {
+	req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
+	req.Header.Set("Content-Type", MIMEPOSTForm)
+	return req
+}
+
+func createFormPostRequestFail() *http.Request {
+	req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar"))
+	req.Header.Set("Content-Type", MIMEPOSTForm)
+	return req
+}
+
+func createFormMultipartRequest() *http.Request {
+	boundary := "--testboundary"
+	body := new(bytes.Buffer)
+	mw := multipart.NewWriter(body)
+	defer mw.Close()
+
+	mw.SetBoundary(boundary)
+	mw.WriteField("foo", "bar")
+	mw.WriteField("bar", "foo")
+	req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
+	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
+	return req
+}
+
+func createFormMultipartRequestFail() *http.Request {
+	boundary := "--testboundary"
+	body := new(bytes.Buffer)
+	mw := multipart.NewWriter(body)
+	defer mw.Close()
+
+	mw.SetBoundary(boundary)
+	mw.WriteField("map_foo", "bar")
+	req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
+	req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
+	return req
+}
+
+func TestBindingFormPost(t *testing.T) {
+	req := createFormPostRequest()
+	var obj FooBarStruct
+	FormPost.Bind(req, &obj)
+
+	assert.Equal(t, "form-urlencoded", FormPost.Name())
+	assert.Equal(t, "bar", obj.Foo)
+	assert.Equal(t, "foo", obj.Bar)
+}
+
+func TestBindingDefaultValueFormPost(t *testing.T) {
+	req := createDefaultFormPostRequest()
+	var obj FooDefaultBarStruct
+	FormPost.Bind(req, &obj)
+
+	assert.Equal(t, "bar", obj.Foo)
+	assert.Equal(t, "hello", obj.Bar)
+}
+
+func TestBindingFormPostFail(t *testing.T) {
+	req := createFormPostRequestFail()
+	var obj FooStructForMapType
+	err := FormPost.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestBindingFormMultipart(t *testing.T) {
+	req := createFormMultipartRequest()
+	var obj FooBarStruct
+	FormMultipart.Bind(req, &obj)
+
+	assert.Equal(t, "multipart/form-data", FormMultipart.Name())
+	assert.Equal(t, "bar", obj.Foo)
+	assert.Equal(t, "foo", obj.Bar)
+}
+
+func TestBindingFormMultipartFail(t *testing.T) {
+	req := createFormMultipartRequestFail()
+	var obj FooStructForMapType
+	err := FormMultipart.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestBindingProtoBuf(t *testing.T) {
+	test := &protoexample.Test{
+		Label: proto.String("yes"),
+	}
+	data, _ := proto.Marshal(test)
+
+	testProtoBodyBinding(t,
+		ProtoBuf, "protobuf",
+		"/", "/",
+		string(data), string(data[1:]))
+}
+
+func TestBindingProtoBufFail(t *testing.T) {
+	test := &protoexample.Test{
+		Label: proto.String("yes"),
+	}
+	data, _ := proto.Marshal(test)
+
+	testProtoBodyBindingFail(t,
+		ProtoBuf, "protobuf",
+		"/", "/",
+		string(data), string(data[1:]))
+}
+
+func TestBindingMsgPack(t *testing.T) {
+	test := FooStruct{
+		Foo: "bar",
+	}
+
+	h := new(codec.MsgpackHandle)
+	assert.NotNil(t, h)
+	buf := bytes.NewBuffer([]byte{})
+	assert.NotNil(t, buf)
+	err := codec.NewEncoder(buf, h).Encode(test)
+	assert.NoError(t, err)
+
+	data := buf.Bytes()
+
+	testMsgPackBodyBinding(t,
+		MsgPack, "msgpack",
+		"/", "/",
+		string(data), string(data[1:]))
+}
+
+func TestValidationFails(t *testing.T) {
+	var obj FooStruct
+	req := requestWithBody("POST", "/", `{"bar": "foo"}`)
+	err := JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestValidationDisabled(t *testing.T) {
+	/*
+		backup := Validator
+		Validator = nil
+		defer func() { Validator = backup }()
+
+		var obj FooStruct
+		req := requestWithBody("POST", "/", `{"bar": "foo"}`)
+		err := JSON.Bind(req, &obj)
+		assert.NoError(t, err)
+		// */
+}
+
+func TestExistsSucceeds(t *testing.T) {
+	type HogeStruct struct {
+		Hoge *int `json:"hoge" binding:"exists"`
+	}
+
+	var obj HogeStruct
+	req := requestWithBody("POST", "/", `{"hoge": 0}`)
+	err := JSON.Bind(req, &obj)
+	assert.NoError(t, err)
+}
+
+func TestExistsFails(t *testing.T) {
+	type HogeStruct struct {
+		Hoge *int `json:"foo" binding:"exists"`
+	}
+
+	var obj HogeStruct
+	req := requestWithBody("POST", "/", `{"boen": 0}`)
+	err := JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestUriBinding(t *testing.T) {
+	b := URI
+	assert.Equal(t, "uri", b.Name())
+
+	type Tag struct {
+		Name string `uri:"name"`
+	}
+	var tag Tag
+	m := make(map[string][]string)
+	m["name"] = []string{"thinkerou"}
+	assert.NoError(t, b.BindURI(m, &tag))
+	assert.Equal(t, "thinkerou", tag.Name)
+
+	type NotSupportStruct struct {
+		Name map[string]interface{} `uri:"name"`
+	}
+	var not NotSupportStruct
+	assert.Error(t, b.BindURI(m, &not))
+	assert.Equal(t, map[string]interface{}(nil), not.Name)
+}
+
+func TestUriInnerBinding(t *testing.T) {
+	type Tag struct {
+		Name string `uri:"name"`
+		S    struct {
+			Age int `uri:"age"`
+		}
+	}
+
+	expectedName := "mike"
+	expectedAge := 25
+
+	m := map[string][]string{
+		"name": {expectedName},
+		"age":  {strconv.Itoa(expectedAge)},
+	}
+
+	var tag Tag
+	assert.NoError(t, URI.BindURI(m, &tag))
+	assert.Equal(t, tag.Name, expectedName)
+	assert.Equal(t, tag.S.Age, expectedAge)
+}
+
+func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooBarStruct{}
+	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, "bar", obj.Foo)
+	assert.Equal(t, "foo", obj.Bar)
+
+	obj = FooBarStruct{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooDefaultBarStruct{}
+	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, "bar", obj.Foo)
+	assert.Equal(t, "hello", obj.Bar)
+
+	obj = FooDefaultBarStruct{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestFormBindingFail(t *testing.T) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooBarStruct{}
+	req, _ := http.NewRequest("POST", "/", nil)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestFormPostBindingFail(t *testing.T) {
+	b := FormPost
+	assert.Equal(t, "form-urlencoded", b.Name())
+
+	obj := FooBarStruct{}
+	req, _ := http.NewRequest("POST", "/", nil)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func TestFormMultipartBindingFail(t *testing.T) {
+	b := FormMultipart
+	assert.Equal(t, "multipart/form-data", b.Name())
+
+	obj := FooBarStruct{}
+	req, _ := http.NewRequest("POST", "/", nil)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooBarStructForTimeType{}
+	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, int64(1510675200), obj.TimeFoo.Unix())
+	assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String())
+	assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix())
+	assert.Equal(t, "UTC", obj.TimeBar.Location().String())
+
+	obj = FooBarStructForTimeType{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooStructForTimeTypeNotFormat{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = FooStructForTimeTypeNotFormat{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooStructForTimeTypeFailFormat{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = FooStructForTimeTypeFailFormat{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := FooStructForTimeTypeFailLocation{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = FooStructForTimeTypeFailLocation{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := InvalidNameType{}
+	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, "", obj.TestName)
+
+	obj = InvalidNameType{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	obj := InvalidNameMapType{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = InvalidNameMapType{}
+	req = requestWithBody(method, badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) {
+	b := Form
+	assert.Equal(t, "form", b.Name())
+
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	switch typ {
+	case "Int":
+		obj := FooBarStructForIntType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, int(0), obj.IntFoo)
+		assert.Equal(t, int(-12), obj.IntBar)
+
+		obj = FooBarStructForIntType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int8":
+		obj := FooBarStructForInt8Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, int8(0), obj.Int8Foo)
+		assert.Equal(t, int8(-12), obj.Int8Bar)
+
+		obj = FooBarStructForInt8Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int16":
+		obj := FooBarStructForInt16Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, int16(0), obj.Int16Foo)
+		assert.Equal(t, int16(-12), obj.Int16Bar)
+
+		obj = FooBarStructForInt16Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int32":
+		obj := FooBarStructForInt32Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, int32(0), obj.Int32Foo)
+		assert.Equal(t, int32(-12), obj.Int32Bar)
+
+		obj = FooBarStructForInt32Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Int64":
+		obj := FooBarStructForInt64Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, int64(0), obj.Int64Foo)
+		assert.Equal(t, int64(-12), obj.Int64Bar)
+
+		obj = FooBarStructForInt64Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint":
+		obj := FooBarStructForUintType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, uint(0x0), obj.UintFoo)
+		assert.Equal(t, uint(0xc), obj.UintBar)
+
+		obj = FooBarStructForUintType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint8":
+		obj := FooBarStructForUint8Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, uint8(0x0), obj.Uint8Foo)
+		assert.Equal(t, uint8(0xc), obj.Uint8Bar)
+
+		obj = FooBarStructForUint8Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint16":
+		obj := FooBarStructForUint16Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, uint16(0x0), obj.Uint16Foo)
+		assert.Equal(t, uint16(0xc), obj.Uint16Bar)
+
+		obj = FooBarStructForUint16Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint32":
+		obj := FooBarStructForUint32Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, uint32(0x0), obj.Uint32Foo)
+		assert.Equal(t, uint32(0xc), obj.Uint32Bar)
+
+		obj = FooBarStructForUint32Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Uint64":
+		obj := FooBarStructForUint64Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, uint64(0x0), obj.Uint64Foo)
+		assert.Equal(t, uint64(0xc), obj.Uint64Bar)
+
+		obj = FooBarStructForUint64Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Float32":
+		obj := FooBarStructForFloat32Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, float32(0.0), obj.Float32Foo)
+		assert.Equal(t, float32(-12.34), obj.Float32Bar)
+
+		obj = FooBarStructForFloat32Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Float64":
+		obj := FooBarStructForFloat64Type{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, float64(0.0), obj.Float64Foo)
+		assert.Equal(t, float64(-12.34), obj.Float64Bar)
+
+		obj = FooBarStructForFloat64Type{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Bool":
+		obj := FooBarStructForBoolType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.False(t, obj.BoolFoo)
+		assert.True(t, obj.BoolBar)
+
+		obj = FooBarStructForBoolType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = JSON.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Slice":
+		obj := FooStructForSliceType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, []int{1, 2}, obj.SliceFoo)
+
+		obj = FooStructForSliceType{}
+		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)
+		assert.Error(t, err)
+	case "SliceMap":
+		obj := FooStructForSliceMapType{}
+		err := b.Bind(req, &obj)
+		assert.Error(t, err)
+	case "Ptr":
+		obj := FooStructForStringPtrType{}
+		err := b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Nil(t, obj.PtrFoo)
+		assert.Equal(t, "test", *obj.PtrBar)
+
+		obj = FooStructForStringPtrType{}
+		obj.PtrBar = new(string)
+		err = b.Bind(req, &obj)
+		assert.NoError(t, err)
+		assert.Equal(t, "test", *obj.PtrBar)
+
+		objErr := FooStructForMapPtrType{}
+		err = b.Bind(req, &objErr)
+		assert.Error(t, err)
+
+		obj = FooStructForStringPtrType{}
+		req = requestWithBody(method, badPath, badBody)
+		err = b.Bind(req, &obj)
+		assert.Error(t, err)
+	}
+}
+
+func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Query
+	assert.Equal(t, "query", b.Name())
+
+	obj := FooBarStruct{}
+	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, "bar", obj.Foo)
+	assert.Equal(t, "foo", obj.Bar)
+}
+
+func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Query
+	assert.Equal(t, "query", b.Name())
+
+	obj := FooStructForMapType{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) {
+	b := Query
+	assert.Equal(t, "query", b.Name())
+
+	obj := FooStructForBoolType{}
+	req := requestWithBody(method, path, body)
+	if method == "POST" {
+		req.Header.Add("Content-Type", MIMEPOSTForm)
+	}
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, name, b.Name())
+
+	obj := FooStruct{}
+	req := requestWithBody("POST", path, body)
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	assert.Equal(t, "bar", obj.Foo)
+
+	obj = FooStruct{}
+	req = requestWithBody("POST", badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, name, b.Name())
+
+	obj := FooStructUseNumber{}
+	req := requestWithBody("POST", path, body)
+	EnableDecoderUseNumber = true
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	// we hope it is int64(123)
+	v, e := obj.Foo.(json.Number).Int64()
+	assert.NoError(t, e)
+	assert.Equal(t, int64(123), v)
+
+	obj = FooStructUseNumber{}
+	req = requestWithBody("POST", badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, name, b.Name())
+
+	obj := FooStructUseNumber{}
+	req := requestWithBody("POST", path, body)
+	EnableDecoderUseNumber = false
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	// it will return float64(123) if not use EnableDecoderUseNumber
+	// maybe it is not hoped
+	assert.Equal(t, float64(123), obj.Foo)
+
+	obj = FooStructUseNumber{}
+	req = requestWithBody("POST", badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, name, b.Name())
+
+	obj := FooStruct{}
+	req := requestWithBody("POST", path, body)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+	assert.Equal(t, "", obj.Foo)
+
+	obj = FooStruct{}
+	req = requestWithBody("POST", badPath, badBody)
+	err = JSON.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, name, b.Name())
+
+	obj := protoexample.Test{}
+	req := requestWithBody("POST", path, body)
+	req.Header.Add("Content-Type", MIMEPROTOBUF)
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	assert.Equal(t, "yes", *obj.Label)
+
+	obj = protoexample.Test{}
+	req = requestWithBody("POST", badPath, badBody)
+	req.Header.Add("Content-Type", MIMEPROTOBUF)
+	err = ProtoBuf.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+type hook struct{}
+
+func (h hook) Read([]byte) (int, error) {
+	return 0, errors.New("error")
+}
+
+func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, name, b.Name())
+
+	obj := protoexample.Test{}
+	req := requestWithBody("POST", path, body)
+
+	req.Body = ioutil.NopCloser(&hook{})
+	req.Header.Add("Content-Type", MIMEPROTOBUF)
+	err := b.Bind(req, &obj)
+	assert.Error(t, err)
+
+	obj = protoexample.Test{}
+	req = requestWithBody("POST", badPath, badBody)
+	req.Header.Add("Content-Type", MIMEPROTOBUF)
+	err = ProtoBuf.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+	assert.Equal(t, name, b.Name())
+
+	obj := FooStruct{}
+	req := requestWithBody("POST", path, body)
+	req.Header.Add("Content-Type", MIMEMSGPACK)
+	err := b.Bind(req, &obj)
+	assert.NoError(t, err)
+	assert.Equal(t, "bar", obj.Foo)
+
+	obj = FooStruct{}
+	req = requestWithBody("POST", badPath, badBody)
+	req.Header.Add("Content-Type", MIMEMSGPACK)
+	err = MsgPack.Bind(req, &obj)
+	assert.Error(t, err)
+}
+
+func requestWithBody(method, path, body string) (req *http.Request) {
+	req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
+	return
+}
+
+func TestCanSet(t *testing.T) {
+	type CanSetStruct struct {
+		lowerStart string `form:"lower"`
+	}
+
+	var c CanSetStruct
+	assert.Nil(t, mapForm(&c, nil))
+}

+ 60 - 24
binding/default_validator.go

@@ -9,15 +9,24 @@ import (
 	"net/url"
 	"reflect"
 	"strconv"
+	"strings"
 	"time"
 )
 
 // MapForm form values map to struct
 func MapForm(ptr interface{}, values url.Values) error {
-	return mapForm(ptr, values)
+	return mapFormByTag(ptr, values, "form")
+}
+
+func mapURI(ptr interface{}, m map[string][]string) error {
+	return mapFormByTag(ptr, m, "uri")
 }
 
 func mapForm(ptr interface{}, form map[string][]string) error {
+	return mapFormByTag(ptr, form, "form")
+}
+
+func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
 	typ := reflect.TypeOf(ptr).Elem()
 	val := reflect.ValueOf(ptr).Elem()
 	for i := 0; i < typ.NumField(); i++ {
@@ -28,14 +37,31 @@ func mapForm(ptr interface{}, form map[string][]string) error {
 		}
 
 		structFieldKind := structField.Kind()
-		inputFieldName := typeField.Tag.Get("form")
+		inputFieldName := typeField.Tag.Get(tag)
+		inputFieldNameList := strings.Split(inputFieldName, ",")
+		inputFieldName = inputFieldNameList[0]
+		var defaultValue string
+		if len(inputFieldNameList) > 1 {
+			defaultList := strings.SplitN(inputFieldNameList[1], "=", 2)
+			if defaultList[0] == "default" {
+				defaultValue = defaultList[1]
+			}
+		}
 		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)
+				err := mapFormByTag(structField.Addr().Interface(), form, tag)
 				if err != nil {
 					return err
 				}
@@ -43,8 +69,13 @@ func mapForm(ptr interface{}, form map[string][]string) error {
 			}
 		}
 		inputValue, exists := form[inputFieldName]
+
 		if !exists {
-			continue
+			if defaultValue == "" {
+				continue
+			}
+			inputValue = make([]string, 1)
+			inputValue[0] = defaultValue
 		}
 
 		numElems := len(inputValue)
@@ -57,16 +88,16 @@ func mapForm(ptr interface{}, form map[string][]string) error {
 				}
 			}
 			val.Field(i).Set(slice)
-		} else {
-			if _, isTime := structField.Interface().(time.Time); isTime {
-				if err := setTimeField(inputValue[0], typeField, structField); err != nil {
-					return err
-				}
-				continue
-			}
-			if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
+			continue
+		}
+		if _, isTime := structField.Interface().(time.Time); isTime {
+			if err := setTimeField(inputValue[0], typeField, structField); err != nil {
 				return err
 			}
+			continue
+		}
+		if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
+			return err
 		}
 	}
 	return nil
@@ -102,6 +133,12 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
 		return setFloatField(val, 64, structField)
 	case reflect.String:
 		structField.SetString(val)
+	case reflect.Ptr:
+		if !structField.Elem().IsValid() {
+			structField.Set(reflect.New(structField.Type().Elem()))
+		}
+		structFieldElem := structField.Elem()
+		return setWithProperType(structFieldElem.Kind(), val, structFieldElem)
 	default:
 		return errors.New("Unknown type")
 	}
@@ -138,7 +175,7 @@ func setBoolField(val string, field reflect.Value) error {
 	if err == nil {
 		field.SetBool(boolVal)
 	}
-	return nil
+	return err
 }
 
 func setFloatField(val string, bitSize int, field reflect.Value) error {
@@ -155,7 +192,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error {
 func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
 	timeFormat := structField.Tag.Get("time_format")
 	if timeFormat == "" {
-		return errors.New("Blank time format")
+		timeFormat = time.RFC3339
 	}
 
 	if val == "" {
@@ -168,6 +205,14 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
 		l = time.UTC
 	}
 
+	if locTag := structField.Tag.Get("time_location"); locTag != "" {
+		loc, err := time.LoadLocation(locTag)
+		if err != nil {
+			return err
+		}
+		l = loc
+	}
+
 	t, err := time.ParseInLocation(timeFormat, val, l)
 	if err != nil {
 		return err
@@ -176,12 +221,3 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
 	value.Set(reflect.ValueOf(t))
 	return nil
 }
-
-// Don't pass in pointers to bind to. Can lead to bugs. See:
-// https://github.com/codegangsta/martini-contrib/issues/40
-// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
-func ensureNotPointer(obj interface{}) {
-	if reflect.TypeOf(obj).Kind() == reflect.Ptr {
-		panic("Pointers are not accepted as binding models")
-	}
-}

+ 19 - 4
binding/json.go

@@ -5,14 +5,18 @@
 package binding
 
 import (
+	"bytes"
+	"fmt"
+	"io"
 	"net/http"
 
 	"encoding/json"
 )
 
-var (
-	EnableDecoderUseNumber = false
-)
+// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
+// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
+// interface{} as a Number instead of as a float64.
+var EnableDecoderUseNumber = false
 
 type jsonBinding struct{}
 
@@ -21,7 +25,18 @@ func (jsonBinding) Name() string {
 }
 
 func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
-	decoder := json.NewDecoder(req.Body)
+	if req == nil || req.Body == nil {
+		return fmt.Errorf("invalid request")
+	}
+	return decodeJSON(req.Body, obj)
+}
+
+func (jsonBinding) BindBody(body []byte, obj interface{}) error {
+	return decodeJSON(bytes.NewReader(body), obj)
+}
+
+func decodeJSON(r io.Reader, obj interface{}) error {
+	decoder := json.NewDecoder(r)
 	if EnableDecoderUseNumber {
 		decoder.UseNumber()
 	}

+ 18 - 0
binding/msgpack.go

@@ -0,0 +1,18 @@
+// Copyright 2018 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+type uriBinding struct{}
+
+func (uriBinding) Name() string {
+	return "uri"
+}
+
+func (uriBinding) BindURI(m map[string][]string, obj interface{}) error {
+	if err := mapURI(obj, m); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 238 - 0
binding/validate_test.go

@@ -0,0 +1,238 @@
+// Copyright 2014 Manu Martinez-Almeida.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+/*
+import (
+	"bytes"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"gopkg.in/go-playground/validator.v8"
+)
+
+type testInterface interface {
+	String() string
+}
+
+type substructNoValidation struct {
+	IString string
+	IInt    int
+}
+
+type mapNoValidationSub map[string]substructNoValidation
+
+type structNoValidationValues struct {
+	substructNoValidation
+
+	Boolean bool
+
+	Uinteger   uint
+	Integer    int
+	Integer8   int8
+	Integer16  int16
+	Integer32  int32
+	Integer64  int64
+	Uinteger8  uint8
+	Uinteger16 uint16
+	Uinteger32 uint32
+	Uinteger64 uint64
+
+	Float32 float32
+	Float64 float64
+
+	String string
+
+	Date time.Time
+
+	Struct        substructNoValidation
+	InlinedStruct struct {
+		String  []string
+		Integer int
+	}
+
+	IntSlice           []int
+	IntPointerSlice    []*int
+	StructPointerSlice []*substructNoValidation
+	StructSlice        []substructNoValidation
+	InterfaceSlice     []testInterface
+
+	UniversalInterface interface{}
+	CustomInterface    testInterface
+
+	FloatMap  map[string]float32
+	StructMap mapNoValidationSub
+}
+
+func createNoValidationValues() structNoValidationValues {
+	integer := 1
+	s := structNoValidationValues{
+		Boolean:            true,
+		Uinteger:           1 << 29,
+		Integer:            -10000,
+		Integer8:           120,
+		Integer16:          -20000,
+		Integer32:          1 << 29,
+		Integer64:          1 << 61,
+		Uinteger8:          250,
+		Uinteger16:         50000,
+		Uinteger32:         1 << 31,
+		Uinteger64:         1 << 62,
+		Float32:            123.456,
+		Float64:            123.456789,
+		String:             "text",
+		Date:               time.Time{},
+		CustomInterface:    &bytes.Buffer{},
+		Struct:             substructNoValidation{},
+		IntSlice:           []int{-3, -2, 1, 0, 1, 2, 3},
+		IntPointerSlice:    []*int{&integer},
+		StructSlice:        []substructNoValidation{},
+		UniversalInterface: 1.2,
+		FloatMap: map[string]float32{
+			"foo": 1.23,
+			"bar": 232.323,
+		},
+		StructMap: mapNoValidationSub{
+			"foo": substructNoValidation{},
+			"bar": substructNoValidation{},
+		},
+		// StructPointerSlice []noValidationSub
+		// InterfaceSlice     []testInterface
+	}
+	s.InlinedStruct.Integer = 1000
+	s.InlinedStruct.String = []string{"first", "second"}
+	s.IString = "substring"
+	s.IInt = 987654
+	return s
+}
+
+func TestValidateNoValidationValues(t *testing.T) {
+	origin := createNoValidationValues()
+	test := createNoValidationValues()
+	empty := structNoValidationValues{}
+
+	assert.Nil(t, validate(test))
+	assert.Nil(t, validate(&test))
+	assert.Nil(t, validate(empty))
+	assert.Nil(t, validate(&empty))
+
+	assert.Equal(t, origin, test)
+}
+
+type structNoValidationPointer struct {
+	substructNoValidation
+
+	Boolean bool
+
+	Uinteger   *uint
+	Integer    *int
+	Integer8   *int8
+	Integer16  *int16
+	Integer32  *int32
+	Integer64  *int64
+	Uinteger8  *uint8
+	Uinteger16 *uint16
+	Uinteger32 *uint32
+	Uinteger64 *uint64
+
+	Float32 *float32
+	Float64 *float64
+
+	String *string
+
+	Date *time.Time
+
+	Struct *substructNoValidation
+
+	IntSlice           *[]int
+	IntPointerSlice    *[]*int
+	StructPointerSlice *[]*substructNoValidation
+	StructSlice        *[]substructNoValidation
+	InterfaceSlice     *[]testInterface
+
+	FloatMap  *map[string]float32
+	StructMap *mapNoValidationSub
+}
+
+func TestValidateNoValidationPointers(t *testing.T) {
+	//origin := createNoValidation_values()
+	//test := createNoValidation_values()
+	empty := structNoValidationPointer{}
+
+	//assert.Nil(t, validate(test))
+	//assert.Nil(t, validate(&test))
+	assert.Nil(t, validate(empty))
+	assert.Nil(t, validate(&empty))
+
+	//assert.Equal(t, origin, test)
+}
+
+type Object map[string]interface{}
+
+func TestValidatePrimitives(t *testing.T) {
+	obj := Object{"foo": "bar", "bar": 1}
+	assert.NoError(t, validate(obj))
+	assert.NoError(t, validate(&obj))
+	assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj)
+
+	obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}}
+	assert.NoError(t, validate(obj2))
+	assert.NoError(t, validate(&obj2))
+
+	nu := 10
+	assert.NoError(t, validate(nu))
+	assert.NoError(t, validate(&nu))
+	assert.Equal(t, 10, nu)
+
+	str := "value"
+	assert.NoError(t, validate(str))
+	assert.NoError(t, validate(&str))
+	assert.Equal(t, "value", str)
+}
+
+// structCustomValidation is a helper struct we use to check that
+// custom validation can be registered on it.
+// The `notone` binding directive is for custom validation and registered later.
+type structCustomValidation struct {
+	Integer int `binding:"notone"`
+}
+
+// notOne is a custom validator meant to be used with `validator.v8` library.
+// The method signature for `v9` is significantly different and this function
+// would need to be changed for tests to pass after upgrade.
+// See https://github.com/gin-gonic/gin/pull/1015.
+func notOne(
+	v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
+	field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
+) bool {
+	if val, ok := field.Interface().(int); ok {
+		return val != 1
+	}
+	return false
+}
+
+func TestValidatorEngine(t *testing.T) {
+	// This validates that the function `notOne` matches
+	// the expected function signature by `defaultValidator`
+	// and by extension the validator library.
+	engine, ok := Validator.Engine().(*validator.Validate)
+	assert.True(t, ok)
+
+	err := engine.RegisterValidation("notone", notOne)
+	// Check that we can register custom validation without error
+	assert.Nil(t, err)
+
+	// Create an instance which will fail validation
+	withOne := structCustomValidation{Integer: 1}
+	errs := validate(withOne)
+
+	// Check that we got back non-nil errs
+	assert.NotNil(t, errs)
+	// Check that the error matches expectation
+	assert.Error(t, errs, "", "", "notone")
+}
+// */

+ 10 - 1
binding/xml.go

@@ -5,7 +5,9 @@
 package binding
 
 import (
+	"bytes"
 	"encoding/xml"
+	"io"
 	"net/http"
 )
 
@@ -16,7 +18,14 @@ func (xmlBinding) Name() string {
 }
 
 func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
-	decoder := xml.NewDecoder(req.Body)
+	return decodeXML(req.Body, obj)
+}
+
+func (xmlBinding) BindBody(body []byte, obj interface{}) error {
+	return decodeXML(bytes.NewReader(body), obj)
+}
+func decodeXML(r io.Reader, obj interface{}) error {
+	decoder := xml.NewDecoder(r)
 	if err := decoder.Decode(obj); err != nil {
 		return err
 	}

+ 35 - 0
binding/yaml.go

@@ -0,0 +1,35 @@
+// Copyright 2018 Gin Core Team.  All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+
+	"gopkg.in/yaml.v2"
+)
+
+type yamlBinding struct{}
+
+func (yamlBinding) Name() string {
+	return "yaml"
+}
+
+func (yamlBinding) Bind(req *http.Request, obj interface{}) error {
+	return decodeYAML(req.Body, obj)
+}
+
+func (yamlBinding) BindBody(body []byte, obj interface{}) error {
+	return decodeYAML(bytes.NewReader(body), obj)
+}
+
+func decodeYAML(r io.Reader, obj interface{}) error {
+	decoder := yaml.NewDecoder(r)
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 1 - 1
tcp/server.go

@@ -39,7 +39,7 @@ func NewTCPServer(addr string, cb func(*net.TCPConn)) {
 			conn, err := listen.AcceptTCP()
 			if err != nil {
 				errc <- err
-				log.Fatalf("accept error: %v", err)
+				logger.Fatalf("accept error: %v", err)
 				break
 			}
 			go cb(conn)

+ 0 - 1
unionpay/charging.go

@@ -4,7 +4,6 @@ package unionpay
 
 // EntrustCharge 代扣费用
 func EntrustCharge(mi *MerchantInfo, oi OrderInfo, ci CustomerInfo) (res ReqRespose, err error) {
-
 	var body string
 	args := newParams(oi, ci)
 	args.TxnType = "11"    // 交易类型, 取值:11