diff --git a/.travis.yml b/.travis.yml index 2fd9c8a217..93aa670050 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,10 @@ matrix: env: GO111MODULE=on - go: 1.12.x env: GO111MODULE=on + - go: 1.12.x + env: + - GO111MODULE=on + - TESTTAGS=nomsgpack - go: master env: GO111MODULE=on diff --git a/Makefile b/Makefile index 51a6b9162c..16b3c288d4 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/ | grep -v /examples/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples) +TESTTAGS ?= "" all: install @@ -14,7 +15,7 @@ install: deps test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ - $(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + $(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ diff --git a/binding/binding.go b/binding/binding.go index 520c510976..8c55f46c8b 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +// +build !nomsgpack + package binding import "net/http" diff --git a/binding/binding_body_msgpack_test.go b/binding/binding_body_msgpack_test.go new file mode 100644 index 0000000000..10e7fa2a10 --- /dev/null +++ b/binding/binding_body_msgpack_test.go @@ -0,0 +1,42 @@ +// +build !nomsgpack + +package binding + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" +) + +func TestBindingMsgPackBody(t *testing.T) { + for _, tt := range []struct { + name string + binding BindingBody + body string + want string + }{ + { + name: "MsgPack binding", + binding: MsgPack, + body: msgPackBody(t), + }, + } { + 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() +} diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go index 901d429c55..38a60a3252 100644 --- a/binding/binding_body_test.go +++ b/binding/binding_body_test.go @@ -1,14 +1,12 @@ package binding import ( - "bytes" "io/ioutil" "testing" "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" - "github.com/ugorji/go/codec" ) func TestBindingBody(t *testing.T) { @@ -31,11 +29,6 @@ func TestBindingBody(t *testing.T) { FOO `, }, - { - name: "MsgPack binding", - binding: MsgPack, - body: msgPackBody(t), - }, { name: "YAML binding", binding: YAML, @@ -51,14 +44,6 @@ func TestBindingBody(t *testing.T) { } } -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"), diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go new file mode 100644 index 0000000000..8706449e5e --- /dev/null +++ b/binding/binding_msgpack_test.go @@ -0,0 +1,57 @@ +// 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. + +// +build !nomsgpack + +package binding + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" +) + +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 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 TestBindingDefaultMsgPack(t *testing.T) { + assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) + assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) +} diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go new file mode 100644 index 0000000000..bfe6eab49b --- /dev/null +++ b/binding/binding_nomsgpack.go @@ -0,0 +1,110 @@ +// 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. + +// +build nomsgpack + +package binding + +import "net/http" + +// 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" + 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{} + 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 MIMEYAML: + return YAML + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: + return Form + } +} + +func validate(obj interface{}) error { + if Validator == nil { + return nil + } + return Validator.ValidateStruct(obj) +} diff --git a/binding/binding_test.go b/binding/binding_test.go index ee788225b8..3a7199886f 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -21,7 +21,6 @@ import ( "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 { @@ -207,9 +206,6 @@ func TestBindingDefault(t *testing.T) { 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)) } @@ -754,26 +750,6 @@ func TestBindingProtoBufFail(t *testing.T) { 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"}`) @@ -1433,23 +1409,6 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body 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 diff --git a/binding/msgpack.go b/binding/msgpack.go index b7f7319771..a5bc2ad297 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +// +build !nomsgpack + package binding import ( diff --git a/render/msgpack.go b/render/msgpack.go index dc681fcf87..be2d45c51c 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +// +build !nomsgpack + package render import ( @@ -10,6 +12,10 @@ import ( "github.com/ugorji/go/codec" ) +var ( + _ Render = MsgPack{} +) + // MsgPack contains the given interface object. type MsgPack struct { Data interface{} diff --git a/render/render.go b/render/render.go index abfc79fcd6..bcd568bfba 100644 --- a/render/render.go +++ b/render/render.go @@ -27,7 +27,6 @@ var ( _ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLProduction{} _ Render = YAML{} - _ Render = MsgPack{} _ Render = Reader{} _ Render = AsciiJSON{} _ Render = ProtoBuf{} diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go new file mode 100644 index 0000000000..e439ac48ed --- /dev/null +++ b/render/render_msgpack_test.go @@ -0,0 +1,43 @@ +// 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. + +// +build !nomsgpack + +package render + +import ( + "bytes" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" +) + +// TODO unit tests +// test errors + +func TestRenderMsgPack(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + (MsgPack{data}).WriteContentType(w) + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) + + err := (MsgPack{data}).Render(w) + + assert.NoError(t, err) + + h := new(codec.MsgpackHandle) + assert.NotNil(t, h) + buf := bytes.NewBuffer([]byte{}) + assert.NotNil(t, buf) + err = codec.NewEncoder(buf, h).Encode(data) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), string(buf.Bytes())) + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) +} diff --git a/render/render_test.go b/render/render_test.go index 76e29eeb23..78db139bef 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -5,7 +5,6 @@ package render import ( - "bytes" "encoding/xml" "errors" "html/template" @@ -17,7 +16,6 @@ import ( "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" - "github.com/ugorji/go/codec" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) @@ -25,30 +23,6 @@ import ( // TODO unit tests // test errors -func TestRenderMsgPack(t *testing.T) { - w := httptest.NewRecorder() - data := map[string]interface{}{ - "foo": "bar", - } - - (MsgPack{data}).WriteContentType(w) - assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) - - err := (MsgPack{data}).Render(w) - - assert.NoError(t, err) - - h := new(codec.MsgpackHandle) - assert.NotNil(t, h) - buf := bytes.NewBuffer([]byte{}) - assert.NotNil(t, buf) - err = codec.NewEncoder(buf, h).Encode(data) - - assert.NoError(t, err) - assert.Equal(t, w.Body.String(), string(buf.Bytes())) - assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) -} - func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{