From 5be2123c1a4407f41062d33500db4de65e05bab1 Mon Sep 17 00:00:00 2001 From: Harindu Perera Date: Thu, 23 Feb 2017 15:08:37 +0100 Subject: [PATCH] Added support for MessagePack binding and rendering (#808) Added deps to vendor.json and fixed rendering bug --- binding/binding.go | 5 +++++ binding/binding_test.go | 45 +++++++++++++++++++++++++++++++++++++++-- binding/msgpack.go | 28 +++++++++++++++++++++++++ render/msgpack.go | 31 ++++++++++++++++++++++++++++ render/render.go | 2 ++ render/render_test.go | 23 +++++++++++++++++++++ vendor/vendor.json | 6 ++++++ 7 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 binding/msgpack.go create mode 100644 render/msgpack.go diff --git a/binding/binding.go b/binding/binding.go index dc7397f1..d3a2c97e 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -15,6 +15,8 @@ const ( MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" + MIMEMSGPACK = "application/x-msgpack" + MIMEMSGPACK2 = "application/msgpack" ) type Binding interface { @@ -40,6 +42,7 @@ var ( FormPost = formPostBinding{} FormMultipart = formMultipartBinding{} ProtoBuf = protobufBinding{} + MsgPack = msgpackBinding{} ) func Default(method, contentType string) Binding { @@ -53,6 +56,8 @@ func Default(method, contentType string) Binding { return XML case MIMEPROTOBUF: return ProtoBuf + case MIMEMSGPACK, MIMEMSGPACK2: + return MsgPack default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: return Form } diff --git a/binding/binding_test.go b/binding/binding_test.go index 72f60152..cf005948 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -12,17 +12,18 @@ import ( "github.com/gin-gonic/gin/binding/example" "github.com/golang/protobuf/proto" + "github.com/ugorji/go/codec" "github.com/stretchr/testify/assert" ) type FooStruct struct { - Foo string `json:"foo" form:"foo" xml:"foo" binding:"required"` + Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` } type FooBarStruct struct { FooStruct - Bar string `json:"bar" form:"bar" xml:"bar" binding:"required"` + Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } func TestBindingDefault(t *testing.T) { @@ -43,6 +44,9 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, Default("POST", MIMEPROTOBUF), ProtoBuf) assert.Equal(t, Default("PUT", MIMEPROTOBUF), ProtoBuf) + + assert.Equal(t, Default("POST", MIMEMSGPACK), MsgPack) + assert.Equal(t, Default("PUT", MIMEMSGPACK2), MsgPack) } func TestBindingJSON(t *testing.T) { @@ -121,6 +125,26 @@ func TestBindingProtoBuf(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"}`) @@ -213,6 +237,23 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba assert.Error(t, err) } +func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), 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, obj.Foo, "bar") + + 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 new file mode 100644 index 00000000..69367175 --- /dev/null +++ b/binding/msgpack.go @@ -0,0 +1,28 @@ +// 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 { + //var decoder *codec.Decoder = codec.NewDecoder(req.Body, &codec.MsgpackHandle) + //if err := decoder.Decode(&obj); err != nil { + return err + } + return validate(obj) + +} diff --git a/render/msgpack.go b/render/msgpack.go new file mode 100644 index 00000000..e6c13e58 --- /dev/null +++ b/render/msgpack.go @@ -0,0 +1,31 @@ +// 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 render + +import ( + "net/http" + + "github.com/ugorji/go/codec" +) + +type MsgPack struct { + Data interface{} +} + +var msgpackContentType = []string{"application/msgpack; charset=utf-8"} + +func (r MsgPack) WriteContentType(w http.ResponseWriter) { + writeContentType(w, msgpackContentType) +} + +func (r MsgPack) Render(w http.ResponseWriter) error { + return WriteMsgPack(w, r.Data) +} + +func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { + writeContentType(w, msgpackContentType) + var h codec.Handle = new(codec.MsgpackHandle) + return codec.NewEncoder(w, h).Encode(obj) +} diff --git a/render/render.go b/render/render.go index 7e997374..46291421 100644 --- a/render/render.go +++ b/render/render.go @@ -22,6 +22,8 @@ var ( _ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLProduction{} _ Render = YAML{} + _ Render = MsgPack{} + _ Render = MsgPack{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index c814ff6d..c48235c3 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -5,17 +5,40 @@ package render import ( + "bytes" "encoding/xml" "html/template" "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", + } + + 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, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") +} + func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ diff --git a/vendor/vendor.json b/vendor/vendor.json index e754a3fd..00115c50 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -47,6 +47,12 @@ "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, + { + "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", + "path": "github.com/ugorji/go/codec", + "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", + "revisionTime": "2017-02-15T20:11:44Z" + }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7",