Added support for MessagePack binding and rendering (#808)

Added deps to vendor.json and fixed rendering bug
This commit is contained in:
Harindu Perera 2017-02-23 15:08:37 +01:00 committed by Bo-Yi Wu
parent 863248034b
commit 5be2123c1a
7 changed files with 138 additions and 2 deletions

View File

@ -15,6 +15,8 @@ const (
MIMEPOSTForm = "application/x-www-form-urlencoded" MIMEPOSTForm = "application/x-www-form-urlencoded"
MIMEMultipartPOSTForm = "multipart/form-data" MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf" MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
) )
type Binding interface { type Binding interface {
@ -40,6 +42,7 @@ var (
FormPost = formPostBinding{} FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{} FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{} ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
) )
func Default(method, contentType string) Binding { func Default(method, contentType string) Binding {
@ -53,6 +56,8 @@ func Default(method, contentType string) Binding {
return XML return XML
case MIMEPROTOBUF: case MIMEPROTOBUF:
return ProtoBuf return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
return Form return Form
} }

View File

@ -12,17 +12,18 @@ import (
"github.com/gin-gonic/gin/binding/example" "github.com/gin-gonic/gin/binding/example"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/ugorji/go/codec"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
type FooStruct struct { 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 { type FooBarStruct struct {
FooStruct 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) { 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("POST", MIMEPROTOBUF), ProtoBuf)
assert.Equal(t, Default("PUT", 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) { func TestBindingJSON(t *testing.T) {
@ -121,6 +125,26 @@ func TestBindingProtoBuf(t *testing.T) {
string(data), string(data[1:])) 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) { func TestValidationFails(t *testing.T) {
var obj FooStruct var obj FooStruct
req := requestWithBody("POST", "/", `{"bar": "foo"}`) 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) 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) { func requestWithBody(method, path, body string) (req *http.Request) {
req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) req, _ = http.NewRequest(method, path, bytes.NewBufferString(body))
return return

28
binding/msgpack.go Normal file
View File

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

31
render/msgpack.go Normal file
View File

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

View File

@ -22,6 +22,8 @@ var (
_ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLDebug{}
_ HTMLRender = HTMLProduction{} _ HTMLRender = HTMLProduction{}
_ Render = YAML{} _ Render = YAML{}
_ Render = MsgPack{}
_ Render = MsgPack{}
) )
func writeContentType(w http.ResponseWriter, value []string) { func writeContentType(w http.ResponseWriter, value []string) {

View File

@ -5,17 +5,40 @@
package render package render
import ( import (
"bytes"
"encoding/xml" "encoding/xml"
"html/template" "html/template"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec"
) )
// TODO unit tests // TODO unit tests
// test errors // 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) { func TestRenderJSON(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := map[string]interface{}{ data := map[string]interface{}{

6
vendor/vendor.json vendored
View File

@ -47,6 +47,12 @@
"revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506",
"revisionTime": "2016-09-25T22:06:09Z" "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=", "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=",
"comment": "release-branch.go1.7", "comment": "release-branch.go1.7",