Browse Source

gin binding

ls 5 years ago
parent
commit
e1dc8d4345

+ 21 - 0
binding/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Manuel Martínez-Almeida
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 155 - 0
binding/binding.go

@@ -0,0 +1,155 @@
+// 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 (
+	"net/http"
+	//validator "gopkg.in/go-playground/validator.v8"
+	//validator "gopkg.in/go-playground/validator.v8"
+)
+
+// Content-Type MIME of the most common data formats.
+const (
+	MIMEJSON              = "application/json"
+	MIMEHTML              = "text/html"
+	MIMEXML               = "application/xml"
+	MIMEXML2              = "text/xml"
+	MIMEPlain             = "text/plain"
+	MIMEPOSTForm          = "application/x-www-form-urlencoded"
+	MIMEMultipartPOSTForm = "multipart/form-data"
+	MIMEPROTOBUF          = "application/x-protobuf"
+	MIMEMSGPACK           = "application/x-msgpack"
+	MIMEMSGPACK2          = "application/msgpack"
+	MIMEYAML              = "application/x-yaml"
+)
+
+// 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 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.
+	// If the received type is a struct or pointer to a struct, the validation should be performed.
+	// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
+	// Otherwise nil must be returned.
+	ValidateStruct(interface{}) 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{}
+	Form          = formBinding{}
+	Query         = queryBinding{}
+	FormPost      = formPostBinding{}
+	FormMultipart = formMultipartBinding{}
+	ProtoBuf      = protobufBinding{}
+	MsgPack       = msgpackBinding{}
+	YAML          = yamlBinding{}
+	URI           = uriBinding{}
+)
+
+// 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
+	}
+
+	switch contentType {
+	case MIMEJSON:
+		return JSON
+	case MIMEXML, MIMEXML2:
+		return XML
+	case MIMEPROTOBUF:
+		return ProtoBuf
+	case MIMEMSGPACK, MIMEMSGPACK2:
+		return MsgPack
+	case MIMEYAML:
+		return YAML
+	default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
+		return Form
+	}
+}
+
+func validate(obj interface{}) error {
+	return nil
+	/*
+		if Validator == nil {
+			return nil
+		}
+
+		return Validator.ValidateStruct(obj)
+		// */
+}
+
+// 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 := 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"))
+}
+
+func filterFlags(content string) string {
+	for i, char := range content {
+		if char == ' ' || char == ';' {
+			return content[:i]
+		}
+	}
+	return content
+}

+ 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))
+}

+ 52 - 0
binding/default_validator.go

@@ -0,0 +1,52 @@
+// Copyright 2017 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
+
+//"gopkg.in/go-playground/validator.v8"
+
+/*
+type defaultValidator struct {
+	once     sync.Once
+	validate *validator.Validate
+}
+
+var _ StructValidator = &defaultValidator{}
+
+func (v *defaultValidator) ValidateStruct(obj interface{}) error {
+	if kindOfData(obj) == reflect.Struct {
+		v.lazyinit()
+		if err := v.validate.Struct(obj); err != nil {
+			buf := bytes.NewBufferString("")
+			for _, v := range err.(validator.ValidationErrors) {
+				buf.WriteString(fmt.Sprintf("%s %s %s", v.Name, v.Tag, v.Param))
+				buf.WriteString(";")
+			}
+			return errors.New(buf.String()) //error(err)
+		}
+	}
+	return nil
+}
+
+func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error {
+	v.lazyinit()
+	return v.validate.RegisterValidation(key, fn)
+}
+
+func (v *defaultValidator) lazyinit() {
+	v.once.Do(func() {
+		config := &validator.Config{TagName: "validate"}
+		v.validate = validator.New(config)
+	})
+}
+
+func kindOfData(data interface{}) reflect.Kind {
+	value := reflect.ValueOf(data)
+	valueType := value.Kind()
+	if valueType == reflect.Ptr {
+		valueType = value.Elem().Kind()
+	}
+	return valueType
+}
+//*/

+ 58 - 0
binding/form.go

@@ -0,0 +1,58 @@
+// 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 (
+	"net/http"
+)
+
+const defaultMemory = 32 * 1024 * 1024
+
+type formBinding struct{}
+type formPostBinding struct{}
+type formMultipartBinding struct{}
+
+func (formBinding) Name() string {
+	return "form"
+}
+
+func (formBinding) Bind(req *http.Request, obj interface{}) error {
+	if err := req.ParseForm(); err != nil {
+		return err
+	}
+	req.ParseMultipartForm(defaultMemory)
+	if err := mapForm(obj, req.Form); err != nil {
+		return err
+	}
+	return validate(obj)
+}
+
+func (formPostBinding) Name() string {
+	return "form-urlencoded"
+}
+
+func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
+	if err := req.ParseForm(); err != nil {
+		return err
+	}
+	if err := mapForm(obj, req.PostForm); err != nil {
+		return err
+	}
+	return validate(obj)
+}
+
+func (formMultipartBinding) Name() string {
+	return "multipart/form-data"
+}
+
+func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
+	if err := req.ParseMultipartForm(defaultMemory); err != nil {
+		return err
+	}
+	if err := mapForm(obj, req.MultipartForm.Value); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 223 - 0
binding/form_mapping.go

@@ -0,0 +1,223 @@
+// 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 (
+	"errors"
+	"net/url"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// MapForm form values map to struct
+func MapForm(ptr interface{}, values url.Values) error {
+	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++ {
+		typeField := typ.Field(i)
+		structField := val.Field(i)
+		if !structField.CanSet() {
+			continue
+		}
+
+		structFieldKind := structField.Kind()
+		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 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 := mapFormByTag(structField.Addr().Interface(), form, tag)
+				if err != nil {
+					return err
+				}
+				continue
+			}
+		}
+		inputValue, exists := form[inputFieldName]
+
+		if !exists {
+			if defaultValue == "" {
+				continue
+			}
+			inputValue = make([]string, 1)
+			inputValue[0] = defaultValue
+		}
+
+		numElems := len(inputValue)
+		if structFieldKind == reflect.Slice && numElems > 0 {
+			sliceOf := structField.Type().Elem().Kind()
+			slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
+			for i := 0; i < numElems; i++ {
+				if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
+					return err
+				}
+			}
+			val.Field(i).Set(slice)
+			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
+}
+
+func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
+	switch valueKind {
+	case reflect.Int:
+		return setIntField(val, 0, structField)
+	case reflect.Int8:
+		return setIntField(val, 8, structField)
+	case reflect.Int16:
+		return setIntField(val, 16, structField)
+	case reflect.Int32:
+		return setIntField(val, 32, structField)
+	case reflect.Int64:
+		return setIntField(val, 64, structField)
+	case reflect.Uint:
+		return setUintField(val, 0, structField)
+	case reflect.Uint8:
+		return setUintField(val, 8, structField)
+	case reflect.Uint16:
+		return setUintField(val, 16, structField)
+	case reflect.Uint32:
+		return setUintField(val, 32, structField)
+	case reflect.Uint64:
+		return setUintField(val, 64, structField)
+	case reflect.Bool:
+		return setBoolField(val, structField)
+	case reflect.Float32:
+		return setFloatField(val, 32, structField)
+	case reflect.Float64:
+		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")
+	}
+	return nil
+}
+
+func setIntField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	intVal, err := strconv.ParseInt(val, 10, bitSize)
+	if err == nil {
+		field.SetInt(intVal)
+	}
+	return err
+}
+
+func setUintField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	uintVal, err := strconv.ParseUint(val, 10, bitSize)
+	if err == nil {
+		field.SetUint(uintVal)
+	}
+	return err
+}
+
+func setBoolField(val string, field reflect.Value) error {
+	if val == "" {
+		val = "false"
+	}
+	boolVal, err := strconv.ParseBool(val)
+	if err == nil {
+		field.SetBool(boolVal)
+	}
+	return err
+}
+
+func setFloatField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0.0"
+	}
+	floatVal, err := strconv.ParseFloat(val, bitSize)
+	if err == nil {
+		field.SetFloat(floatVal)
+	}
+	return err
+}
+
+func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
+	timeFormat := structField.Tag.Get("time_format")
+	if timeFormat == "" {
+		timeFormat = time.RFC3339
+	}
+
+	if val == "" {
+		value.Set(reflect.ValueOf(time.Time{}))
+		return nil
+	}
+
+	l := time.Local
+	if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
+		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
+	}
+
+	value.Set(reflect.ValueOf(t))
+	return nil
+}

+ 47 - 0
binding/json.go

@@ -0,0 +1,47 @@
+// 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"
+	"fmt"
+	"io"
+	"net/http"
+
+	"encoding/json"
+)
+
+// 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{}
+
+func (jsonBinding) Name() string {
+	return "json"
+}
+
+func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
+	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()
+	}
+	if err := decoder.Decode(obj); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 25 - 0
binding/msgpack.go

@@ -0,0 +1,25 @@
+// Copyright 2017 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 (
+	"net/http"
+	//"github.com/ugorji/go/codec"
+)
+
+type msgpackBinding struct{}
+
+func (msgpackBinding) Name() string {
+	return "msgpack"
+}
+
+func (msgpackBinding) Bind(req *http.Request, obj interface{}) error {
+	/*
+		if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil {
+			return err
+		}
+		// */
+	return validate(obj)
+}

+ 35 - 0
binding/protobuf.go

@@ -0,0 +1,35 @@
+// 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 (
+	"net/http"
+	//"github.com/golang/protobuf/proto"
+)
+
+type protobufBinding struct{}
+
+func (protobufBinding) Name() string {
+	return "protobuf"
+}
+
+func (protobufBinding) Bind(req *http.Request, obj interface{}) error {
+	/*
+		buf, err := ioutil.ReadAll(req.Body)
+		if err != nil {
+			return err
+		}
+
+		if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil {
+			return err
+		}
+
+		//Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct
+		//which automatically generate by gen-proto
+		//return nil
+		return validate(obj)
+		// */
+	return validate(obj)
+}

+ 23 - 0
binding/query.go

@@ -0,0 +1,23 @@
+// Copyright 2017 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 (
+	"net/http"
+)
+
+type queryBinding struct{}
+
+func (queryBinding) Name() string {
+	return "query"
+}
+
+func (queryBinding) Bind(req *http.Request, obj interface{}) error {
+	values := req.URL.Query()
+	if err := mapForm(obj, values); err != nil {
+		return err
+	}
+	return validate(obj)
+}

+ 18 - 0
binding/uri.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")
+}
+// */

+ 33 - 0
binding/xml.go

@@ -0,0 +1,33 @@
+// 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/xml"
+	"io"
+	"net/http"
+)
+
+type xmlBinding struct{}
+
+func (xmlBinding) Name() string {
+	return "xml"
+}
+
+func (xmlBinding) Bind(req *http.Request, obj interface{}) error {
+	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
+	}
+	return validate(obj)
+}

+ 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)
+}